# -*- 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, 2005

# 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.79 2005/11/10 01:24:29 martins Exp $

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

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

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

source_root="$sysfs_dir"

list_adapters ()
{
    # Sets: adapter_list
    adapter_list=""

    local bus_type
    for bus_type in "pci" "pseudo" "vio" ; do
	sysfs_list_adapters_by_bus "$bus_type"
    done

    sysfs_list_adapters_ide_misc
}

sysfs_list_adapters_by_bus ()
{
    # Sets: adapter_list

    local bus_type="$1"

    local devices_dir="${sysfs_dir}/bus/${bus_type}/devices"
    local n
    for n in "${devices_dir}/"* ; do
	local bus_addr="${n#${devices_dir}/}"
	adapter_list="${adapter_list} ${bus_type}/${bus_addr}"
	
	# FIXME: have lost channels!
    done
}

make_multiplexed adapter_get_subtype

adapter_get_subtype_DEFAULT ()
{
    # Sets: subtype
    subtype=""

    local bus_type="$1"
    local bus_addr="$2"

    case "${bus_type}/${bus_addr}" in
	(unknown/ide*) subtype="ide" ;;
    esac

    if [ -z "$subtype" ] ; then
	local driver
	sysfs_adapter_get_driver "$bus_type" "$bus_addr"

	subtype="$driver"
    fi
}

adapter_get_subtype_pci ()
{
    # Sets: subtype
    subtype=""

    local bus_addr="$1"

    local source_node
    adapter_get_source_node "$bus_type" "$bus_addr" "unknown"

    local value driver
    pci_get_field_value "$source_node" "class"
    sysfs_adapter_get_driver "pci" "$bus_addr"
    
    case "${value}:${driver}" in
	# Special cases for various drivers.
	(*:ide-pmac)        subtype="ide"      ;;
	(*:Promise[\ _]IDE) subtype="ide"      ;;
	(*:sata_svw)        subtype="sata"     ;;
	(*:ata_piix)        subtype="sata"     ;;
	(*:pata_pdc2027x)   subtype="ata"      ;;
	(*:qla4xxx)         subtype="iscsi"    ;;
	# Defaults for various PCI classes.
	(0180:*)            subtype="oms"      ;; # Unknown mass storage.
	(0100:*)            subtype="scsi"     ;;
	(0104:*)            subtype="raid"     ;;
	(0[Cc]04:*)         subtype="fibre"    ;;
	(0200:*)            subtype="ethernet" ;;
	(0101:*)            subtype="ide"      ;;
	(0[Cc]03:*)         subtype="usb"      ;;
	(0300:*)            subtype="display"  ;;
	(0700:*)            subtype="serial"   ;;
	(0401:*)            subtype="sound"    ;;
	# NO!  Need to do this by driver?
	# (0280:*)          subtype="iscsi"    ;;
	### FIXME! (ff*:*)           subtype="unknown"  ;;
    esac
}

sysfs_adapter_get_driver ()
{
    # Sets: driver
    driver=""

    local bus_type="$1"
    local bus_addr="$2"

    local f="${sysfs_dir}/bus/${bus_type}/devices/${bus_addr}/driver"

    if [ -L "$f" ] && cd -P "$f" ; then
	driver="${PWD##*/}"  # basename
	cd "$OLDPWD"
    else
	# "driver" link used above only exists after about 2.6.10...
	for f in "${sysfs_dir}/bus/${bus_type}/drivers/"*"/${bus_addr}" ; do
	    driver="${f%/*}"  # dirname
	    driver="${driver##*/}" # basename
	    break
	done
    fi
}

sysfs_list_adapters_ide_misc ()
{
    # Sets: adapter_list

    local n
    for n in "${sysfs_dir}/devices/"ide* ; do
	local adapter_info="unknown/${n#${sysfs_dir}/devices/}"
	adapter_list="${adapter_list} ${adapter_info}"
    done
}

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

unset -f get_source_node_adapter
unset -f get_source_node_device

get_source_node_DEFAULT ()
{
    # Sets: source_node
    source_node=""

    local meta_type="$1"
    local node_type="$2"
    local bus_info="$3"
    local subtype="$4"

    local sysfs_device_node
    get_sysfs_device_node "${bus_info%/*}" "${bus_info#*/}"

    source_node="$sysfs_device_node"
}

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

get_names_adapter ()
{
    # Sets: names
    names=""

    local bus_type="$1"
    local bus_addr="$2"

    local subtype
    retrieve_subtype "$bus_type" "$bus_addr"
    [ -n "$subtype" ] || return 1 ### !!!
    
    local subdirs
    get_sysfs_device_node_subdirs "$bus_type" "$bus_addr" "$subtype" || \
	return 1  ### !!!

    if [ -n "$subdirs" ] ; then
	names="$subdirs"
    else
	# Some adapters, like ethernet won't have named
	# subdirectories.  The only way to find the name is to find
	# the class node.

	# FIXME: This code replaces code that finds the first exact
	# match for class node's device link.  This code finds any
	# match and then checks it is exact.  I think the 2 cases will
	# be identical in practice, but I'm not 100% sure.  If this
	# turns out not to be true, copy the code from
	# get_sysfs_class_node here and change the prefix match to an
	# exact match.
	#
	# We might want to change this to process all the possible
	# class nodes.

	local sysfs_device_node
	get_sysfs_device_node "$bus_type" "$bus_addr"
	[ -n "$sysfs_device_node" ] || return 1  ### !!!

	local sysfs_class_node
	get_sysfs_class_node "$bus_type" "$bus_addr" "$subtype"

	if [ -n "$sysfs_class_node" ] ; then
	    local t="${sysfs_class_node}/device"
	    if [ -L "$t" ] && cd -P "$t" ; then
		[ "$PWD" = "${sysfs_device_node}" ] && \
		    names="${sysfs_class_node##*/}"  # basename
		cd "$OLDPWD"
	    fi
	fi
    fi
}

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

pci_get_field_value ()
{
    # Sets: value
    value=""

    local node="$1"
    local field="$2"

    local f x
    f="${node}/${field}"
    [ -r "$f" ] && read x <"$f"
    if [ -n "$x" ] ; then
	case "$field" in
	    vendor|device|subsystem_vendor|subsystem_device)
		value="${x#0x}"
		[ "$value" = "0000" ] && value=""
		;;
	    class)
		case "$x" in
		    0x[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])
			value="${x:2:4}"
			;;
		esac
		;;
	esac
    fi
}

pci_get_config_filename ()
{
    # Sets: pci_config
    pci_config=""

    local pci_addr="$1"

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

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

adapter_extra_vpd_hooks="${adapter_extra_vpd_hooks} sysfs_fw_version_vpd_hook"

sysfs_fw_version_vpd_hook ()
{
    # Sets: vpd_function key val
    vpd_function="" ; key="" ; val=""

    local bus_type="$1"
    local bus_addr="$2"
    local subtype="$3"

    local sysfs_class_node
    get_sysfs_class_node "$bus_type" "$bus_addr" "$subtype"

    if [ -n "$sysfs_class_node" ] ; then
	local a
	for a in "fw_version" "fwrev" ; do
	    local a="${sysfs_class_node}/${a}"
	    if [ -r "$a" ] ; then
		vpd_function=vpd_field_override_link
		key="RM"
		val="$a"
		break
	    fi
	done
    fi
}

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

adapter_retrieve_logicals_vpd ()
{
    local bus_type="$1"
    local bus_addr="$2"
    local node="$3"

    local subtype
    retrieve_subtype_basic "$node"
    [ -n "$subtype" ] || return 1  ### !!!
    
    local subdirs
    get_sysfs_device_node_subdirs "$bus_type" "$bus_addr" "$subtype" || \
	return 1  ### !!!

    # If there's more than one subdirectory, remember the details of
    # channels and subdirectories in lsvpd,logicals.
    if [ "$subdirs" != "${subdirs#* }" ] ; then
	local num=0
	local f="${node}/lsvpd,logicals"
	>"$f"
	local i
	for i in $subdirs ; do
	    echo "${num} ${i}" >>"$f"
	    num=$(($num + 1))
	done
    fi

    adapter_retrieve_logicals_common \
	"$bus_type" "$bus_addr" "$subtype" "$node" "$subdirs"
}

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

get_yl_adapter ()
{
    # Sets: yl

    local bus_type="$1"
    local bus_addr="$2"
    local source_node="$3"
    local node="$4"

    yl="$bus_addr"

    case "$source_node" in
	(*/pseudo*)      yl="~${yl}" ;;
	(*/devices/ide*) yl="?${yl}" ;;
    esac
}

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

get_sysfs_device_node ()
{
    # Sets: sysfs_device_node

    local bus_type="$1"
    local bus_addr="$2"

    local bus_alias
    bus_alias_get "${bus_type}/${bus_addr}" "sysfs-device"
    sysfs_device_node="$bus_alias"

    [ -n "$sysfs_device_node" ] && return 0 ### !!!

    local t
    case "$bus_type" in
	(unknown) t=""                 ;;
	(*)       t="/bus/${bus_type}" ;;
    esac
    t="${sysfs_dir}${t}/devices/${bus_addr}"

    if [ -L "$t" ] && cd -P "$t" ; then
	sysfs_device_node="$PWD"
	cd "$OLDPWD"
    elif [ -d "$t" ] ; then
	sysfs_device_node="$t"
    fi

    if [ -n "$sysfs_device_node" -a -d "$sysfs_device_node" ] ; then
	case "$sysfs_device_node" in
	    "${sysfs_dir}/devices/"*)
		bus_alias_set "${bus_type}/${bus_addr}" \
		    "$sysfs_device_node" "sysfs-device"
		;;
	    *)
		: "Unknown device path format."
		sysfs_device_node=""
		;;
	esac
    fi
}

get_sysfs_device_node_subdirs ()
{
    # Sets: subdirs
    subdirs=""

    local bus_type="$1"
    local bus_addr="$2"
    local subtype="$3"

    local sysfs_device_node
    get_sysfs_device_node "$bus_type" "$bus_addr"
    [ -n "$sysfs_device_node" ] ||  return 1 ### !!!
    
    local os_prefix
    adapter_set_os_prefix "$subtype"
    [ -n "$os_prefix" ] || return 1  ### !!!
    
    case "$bus_type" in
	unknown)
	    sysfs_device_node="${sysfs_device_node%/*}"  # dirname
	    ;;
    esac

    # FIXME: limit of 100000 adapters!  :-)
    cd -P "$sysfs_device_node"
    local i
    for i in "$os_prefix"? "$os_prefix"?? "$os_prefix"??? \
	"$os_prefix"???? "$os_prefix"????? ; do
	[ -d "$i" ] && \
	    subdirs="${subdirs} ${i}"
    done
    subdirs="${subdirs# }" # Remove "accidently" added leading space.
    cd "$OLDPWD"
}

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

set_sysfs_adapter_class ()
{
    # Sets: sysfs_adapter_class
    sysfs_adapter_class=""

    local subtype="$1"

    case "$subtype" in
	scsi|sata|ata|iscsi|fibre|raid|ibmvscsi)
	    sysfs_adapter_class="scsi_host"
	    ;;
	ethernet|ibmveth)
	    sysfs_adapter_class="net"
	    ;;
	(usb)      sysfs_adapter_class="usb_host"  ;;
	(sound)    sysfs_adapter_class="sound"     ;;
	# How about "display" and "serial"?
    esac
}

# FIXME: For current purposes this is OK, but it only really gets the
# first loose match for the sysfs_device_node.
get_sysfs_class_node ()
{
    # Sets: sysfs_class_node

    local bus_type="$1"
    local bus_addr="$2"
    local subtype="$3"

    local bus_alias
    bus_alias_get "${bus_type}/${bus_addr}" "sysfs-class"
    sysfs_class_node="$bus_alias"

    [ -n "$sysfs_class_node" ] && return 0 ### !!!

    local sysfs_device_node
    get_sysfs_device_node "$bus_type" "$bus_addr"

    local sysfs_adapter_class
    set_sysfs_adapter_class "$subtype"
    
    if [ -n "$sysfs_adapter_class" ] ; then
	local d="${sysfs_dir}/class/${sysfs_adapter_class}"
	local i
	for i in "${d}/"* ; do
	    local t="${i}/device"
	    if [ -L "$t" ] && cd -P "$t" ; then
		if [ "$PWD" != "${PWD#${sysfs_device_node}}" ] ; then
		    # $sysfs_device_node is a prefix of $PWD.
		    sysfs_class_node="$i"
		    cd "$OLDPWD"
		    break
		fi
		cd "$OLDPWD"
	    fi
	done
    fi

    [ -d "$sysfs_class_node" ] && \
	bus_alias_set "${bus_type}/${bus_addr}" "$sysfs_class_node" "sysfs-class"
}
