# -*- 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.70 2005/06/22 01:44:20 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 add_adapter (via a call to adapter_init_node).  Also
# list_adapters accesses sysfs directly.
adapter_init_node ()
{
    local type="$1"
    local bus_info="$2"

    case "$bus_info" in
	(*/*/*) return 0 ;;
    esac

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

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

    local adapter_bus_dir
    set_adapter_bus_dir "$type" "$bus_info"

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

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

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

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

pci_get_config_filename ()
{
    local pci_addr="$1"

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

pci_get_vendor_id ()
{
    # Sets: vendor_id

    local adapter_bus_dir="$1"

    vendor_id="ffff"

    local f="${adapter_bus_dir}/vendor"
    if [ -f "$f" ] ; then
	read vendor_id <"$f"
	vendor_id="${vendor_id#0x}"
    fi
}

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

sysfs_list_adapter_functions=""

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

    local f
    for f in $sysfs_list_adapter_functions ; do
	$f
    done
}


sysfs_list_adapter_functions="${sysfs_list_adapter_functions} \
    sysfs_list_adapters_pci"

sysfs_list_adapters_pci ()
{
    # Sets: adapter_list
    local types="scsi ethernet ide usb display serial sound"

    local devices_dir="${sysfs_dir}/bus/pci/devices"

    local n
    for n in "${devices_dir}/"* ; do
	local pci_addr="${n#${devices_dir}/}"

	local pci_class
	sysfs_set_pci_class "${sysfs_dir}/bus/pci/devices/${pci_addr}"
	[ -n "$pci_class" ] || continue

	local t
	for t in $types ; do
	    if { pci_check_class "$t" "$pci_class" &&\
		! sysfs_pci_quirks_exclude "$t" "$pci_addr" "$pci_class"; } ||\
		sysfs_pci_quirks_include "$t" "$pci_addr" "$pci_class" ; then

		local adapter_info="${t}@pci/${pci_addr}"
		adapter_list="${adapter_list} ${adapter_info}"
		sysfs_list_adapter_pci_channels "$t" "$adapter_info" "$n"
		break
	    fi
	done
    done
}

sysfs_list_adapter_pci_channels ()
{
    # Sets: adapter_list


    local type="$1"
    local adapter_info="$2"
    local node="$3"

    local os_prefix
    adapter_set_os_prefix "$type"

    local p="${node}/${os_prefix}"
    local channels=""
    local c
    for c in "$p"* ; do
	if [ -d "$c" ] ; then
	    # Need to be careful due to check below.
	    [ -n "$channels" ] && channels="${channels} "
	    channels="${channels}${adapter_info}/${c#${p}}"
	fi
    done

    # Only add channels if there is more than 1.
    case "$channels" in
	(*\ *) adapter_list="${adapter_list} ${channels}"          ;;
	(*)    sysfs_last_chance_pci_channels_hook "$adapter_info" ;;
    esac
}

sysfs_last_chance_pci_channels_hooks=""
sysfs_last_chance_pci_channels_hook ()
{
    local adapter_info="$1"

    local h
    for h in $sysfs_last_chance_pci_channels_hooks ; do
	$h "$adapter_info" && return
    done
}

make_multiplexed sysfs_pci_quirks_include
make_multiplexed sysfs_pci_quirks_exclude

sysfs_pci_quirks_include_DEFAULT ()
{
    # Don't include arbitrary things.
    return 1
}

sysfs_pci_quirks_exclude_DEFAULT ()
{
    # Don't exclude arbitrary things.
    return 1
}

sysfs_pci_quirks_include_ide ()
{
    local pci_addr="$1"
    local pci_class="$2"

    { pci_check_class "unknown" "$pci_class" && \
	[ -L "${sysfs_dir}/bus/pci/drivers/ide-pmac/${pci_addr}" ] ; } ||
    [ "$pci_class" = "0180" -a \
	-L "${sysfs_dir}/bus/pci/drivers/Promise IDE/${pci_addr}"  ]
}

sysfs_pci_quirks_include_scsi ()
{
    local pci_addr="$1"
    local pci_class="$2"

    pci_check_class "ide" "$pci_class" && \
	sysfs_pci_adapter_is_sata "$pci_addr"
}

sysfs_pci_quirks_exclude_ide ()
{
    # No need to check class - if not IDE we wouldn't be here!
    sysfs_pci_adapter_is_sata "$pci_addr"
}

sysfs_pci_adapter_is_sata ()
{
    local pci_addr="$1"

    sysfs_sata_drivers="sata_svw ata_piix"

    local s
    for s in $sysfs_sata_drivers ; do
	[ -L "${sysfs_dir}/bus/pci/drivers/${s}/${pci_addr}" ] && \
	return 0
    done

    return 1
}

#---------------------------------------------------------------------

sysfs_list_adapter_functions="${sysfs_list_adapter_functions} \
    sysfs_list_adapters_scsi_debug"

sysfs_list_adapters_scsi_debug ()
{
    # Sets: adapter_list

    local n
    for n in "${sysfs_dir}/bus/pseudo/devices/"adapter*/host* ; do
	local t="${n#${sysfs_dir}/bus/pseudo/devices/}"
	local adapter_info="scsi@pseudo/${t%/host*}"
	adapter_list="${adapter_list} ${adapter_info}"
    done
}

sysfs_list_adapter_functions="${sysfs_list_adapter_functions} \
    sysfs_list_adapters_ide_misc"

sysfs_list_adapters_ide_misc ()
{
    # Sets: adapter_list

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

#---------------------------------------------------------------------

sysfs_list_adapter_functions="${sysfs_list_adapter_functions} \
    sysfs_list_adapters_ibmveth"

sysfs_list_adapters_ibmveth ()
{
    # Sets: adapter_list

    sysfs_list_adapters_by_driver "ethernet" "vdevice" "ibmveth"
}

sysfs_list_adapter_functions="${sysfs_list_adapter_functions} \
    sysfs_list_adapters_ibmvscsi"

sysfs_list_adapters_ibmvscsi ()
{
    # Sets: adapter_list

    sysfs_list_adapters_by_driver "scsi" "vdevice" "ibmvscsi"
}

sysfs_list_adapters_by_driver ()
{
    # Sets: adapter_list

    local type="$1"
    local bus_type="$2"
    local driver="$3"

    local sysfs_bus_type
    get_sysfs_bus_type "$bus_type"

    local n
    for n in "${sysfs_dir}/bus/${sysfs_bus_type}/drivers/${driver}"/* ; do
	local adapter_info="${type}@${bus_type}/${n##*/}"
	adapter_list="${adapter_list} ${adapter_info}"
    done
}

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

adapter_set_ax_osname_hook ()
{
    # Sets: ax, osname
    osname=""

    local type="$1"
    local bus_info="$2"

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

    local sysfs_node=""
    set_sysfs_node_hook "$type" "${bus_type}/${bus_addr}"

    if [ -n "$sysfs_node" ] ; then

	local channel=""
	case "$bus_addr" in
	    (*/*) channel="${bus_addr#*/}" ;;
	esac

	local os_prefix
	adapter_set_os_prefix "$type"

	sysfs_node="${sysfs_node%/${os_prefix}*}"

	# FIXME: limit of 100000 adapters!  :-)
	cd -P "$sysfs_node"
	local nums=""
	local i n maybe any
	local last=-2
	local count=0
	for i in "$os_prefix"? "$os_prefix"?? "$os_prefix"??? \
	    "$os_prefix"???? "$os_prefix"????? ; do
	    if [ -d "$i" ] ; then
		n="${i#${os_prefix}}"
		if [ $n -eq $(($last + 1)) ] ; then
		    nums="${nums%-*}-"
		else
		    [ -n "$nums" ] && nums="${nums},"
		fi
		nums="${nums}${n}"
		last=$n
		[ "$count" = "$channel" ] && \
		    maybe="$n"
		any="$n"
		count=$(($count + 1))
	    fi
	done
	cd "$OLDPWD"

	if [ $count -eq 1 ] ; then
	    osname="${os_prefix}${any}"
	    [ -n "$channel" ] && osname="${osname}:${channel}"
	elif [ $count -gt 1 ] ; then
	    if [ -n "$maybe" ] ; then
		osname="${os_prefix}${maybe}"
	    else
		osname="${os_prefix}${nums}"
	    fi
	else
	    # Try the sysfs class node.
	    local bus_alias
	    bus_alias_get "$bus_info" "sysfs-class"
	    local t="$bus_alias"
	
	    if [ -n "$t" ] ; then
		local sysfs_adapter_class
		set_sysfs_adapter_class "$type"
		t="${t#${sysfs_dir}/class/${sysfs_adapter_class}/}"
		osname="${t%/device}"
	    fi

	    # FIXME: channels?
	fi
    fi

    if [ -n "$osname" ] ; then
	adapter_set_ax_from_osname_hook
    else
	adapter_set_ax_osname_hook_basic "$type" "$bus_info"
    fi
}

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

set_adapter_bus_dir ()
{
    # Sets: adapter_bus_dir

    local type="$1"
    local bus_info="$2"

    local bus_alias
    bus_alias_get "$bus_info"
    adapter_bus_dir="$bus_alias"

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

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

	    [ -n "$adapter_bus_dir" ] && \
		bus_alias_set "$bus_info" "${adapter_bus_dir#${db_bus_dir}/}"
	fi
    fi

}

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

unset -f add_adapter_ide

adapter_extra_vpd_hooks="${adapter_extra_vpd_hooks} sysfs_fw_version_vpd_hook"

sysfs_fw_version_vpd_hook ()
{
    local type="$1"
    local bus_info="$2"
    local adapter_bus_dir="$3"
    local vpd_dir="$4"

    local sysfs_class_attribute
    for i in "fw_version" "fwrev" ; do
	get_sysfs_class_attribute "$type" "$bus_info" "$i"
	if [ -n "$sysfs_class_attribute" ] ; then
	    vpd_field_override_link "$vpd_dir" "RM" "$sysfs_class_attribute"
	    break
	fi
    done
}

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

# adapter_set_yl()
#
# Uses: adapter_bus_dir
# Sets: yl

adapter_set_yl_DEFAULT ()
{
    local t
    read t <"${adapter_bus_dir}/lsvpd,bus-info"

    yl="${t#*/}"

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

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

adapter_set_ds_scsi ()
{
    # Sets: ds

    local adapter_bus_dir="$1"
    local adapter_is_a_channel="$2"

    local pci_class
    sysfs_set_pci_class "$adapter_bus_dir"
    case "$pci_class" in
	0[Cc]04)
	    ds="Fibre Channel Adapter"
	    $adapter_is_a_channel && ds="${ds} Channel"
	    ;;
	0101)
	    ds="Serial ATA Adapter"   # FIXME?  Hmmm...
	    $adapter_is_a_channel && ds="${ds} Channel"
	    ;;
	*)
	    adapter_set_ds_DEFAULT \
		"scsi" "$adapter_bus_dir" $adapter_is_a_channel
	    ;;
    esac
}

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

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

    local type="$1"

    case "$type" in
	(scsi)     sysfs_adapter_class="scsi_host" ;;
	(ethernet) sysfs_adapter_class="net"	    ;;
	(usb)      sysfs_adapter_class="usb_host"  ;;
    esac
}

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

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

    local bus_alias
    bus_alias_get "$bus_info" "sysfs-device"
    sysfs_node="$bus_alias"

    if [ -z "$sysfs_node" ] ; then
	local channel=""
	case "$bus_addr" in
	    (*/*) channel="${bus_addr#*/}" ;;
	esac

	local sysfs_bus_type
	get_sysfs_bus_type "$bus_type"

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

	if [ -L "$t" ] && cd -P "$t" ; then
	    sysfs_node="$PWD"
	    local os_prefix
	    adapter_set_os_prefix "$type"
	    # FIXME: limit of 100000 adapters!  :-)
	    local i
	    local n=0
	    for i in "$os_prefix"? "$os_prefix"?? "$os_prefix"??? \
		"$os_prefix"???? "$os_prefix"????? ; do
		if [ -d "$i" -a "$n" = "$channel" ] ; then
		    sysfs_node="${sysfs_node}/${i}"
		    break
		elif [ -z "$channel" -a $n -gt 1 ] ; then
		    # Optimisation.
		    break
		fi
		n=$(($n + 1))
	    done

	    # Handle single channel with no-channel-given case.
	    [ "$sysfs_node" = "$PWD" -a -z "$channel" -a $n -eq 1 ] && \
		sysfs_node="${sysfs_node}/${i}"

	    local sysfs_adapter_class
	    set_sysfs_adapter_class "$type"

	    if [ -n "$sysfs_adapter_class" ] ; then
		if [ "$sysfs_node" != "$PWD" ] ; then
		    cd "$OLDPWD"
		    local d="${sysfs_dir}/class/${sysfs_adapter_class}/$i"
		    [ -d "$d" ] && \
			bus_alias_set "$bus_info" "$d" "sysfs-class"
		else
		    cd "$OLDPWD"

		    local d="${sysfs_dir}/class/${sysfs_adapter_class}"
		    for i in "${d}/"* ; do
			t="${i}/device"
			if [ -L "$t" ] && cd -P "$t" ; then
			    if [ "$sysfs_node" = "$PWD" ] ; then
				bus_alias_set "$bus_info" "$i" "sysfs-class"
				cd "$OLDPWD"
				break
			    elif [ "${PWD#${sysfs_node}/}" != "$PWD" ] ; then
				# Optimisation.
				cd "$OLDPWD"
				break
			    fi
			    cd "$OLDPWD"
			fi
		    done
		fi
	    fi
	elif [ -d "$t" ] ; then
	    sysfs_node="$t"
	fi
    
	[ -d "$sysfs_node" ] && \
	    bus_alias_set "$bus_info" "$sysfs_node" "sysfs-device"
    fi
}

add_adapter_pci_hook ()
{
    # Sets: ds, mf, tm, cd

    local node="$1"

    local vf="${node}/vendor"
    local df="${node}/device"
    if [ -f "$vf" -a -f "$df" ] ; then
	local vendor device
	read vendor <"$vf" ; vendor="${vendor#0x}"
	read device <"$df" ; device="${device#0x}"
	
	local subvendor subdevice
	local svf="${node}/subsystem_vendor"
	local sdf="${node}/subsystem_device"
	if [ -f "$svf" -a -f "$sdf" ] ; then
	    read subvendor <"$svf" ; subvendor="${subvendor#0x}"
	    read subdevice <"$sdf" ; 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_set_pci_class ()
{
    # Sets: pci_class
    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])
	    pci_class="${x:2:4}"
	    ;;
	esac
    fi
}

get_sysfs_class_attribute ()
{
    # Sets: sysfs_class_attribute
    sysfs_class_attribute=""

    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 bus_alias
	bus_alias_get "$bus_info" "sysfs-class"
	if [ -n "$bus_alias" ] ; then
	    local a="${bus_alias}/${attribute}"
	    [ -r "$a" ] && sysfs_class_attribute="$a"
	fi
    fi
}

get_sysfs_bus_type ()
{
    # Sets: sysfs_bus_type

    local bus_type="$1"

    sysfs_bus_type="$bus_type"
    case "$bus_type" in
	(vdevice) sysfs_bus_type="vio" ;;
    esac
}
