# -*- shell-script -*-

# 30device-tree - Hardware database scanning routines and variables for
#                 systems with device-tree, with or without ibm,vpd properties.

# This file is part of the Linux lsvpd package.

# (C) Copyright IBM Corp. 2002, 2003

# 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: 30device-tree,v 1.17 2004/02/19 07:52:11 martins Exp $

source_device_tree="/proc/device-tree"
[ -f "${source_device_tree}/system-id" ] || return 0

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

if [ -f "${source_device_tree}/device_type" ] ; then
    device_tree_device_type=$(cat "${source_device_tree}/device_type")
fi

function db_initialise_bus ()
{
    cp -a "$source_device_tree" "${db_bus_dir}"
}

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

# This overrides a multiplexed function.

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

    find "${db_bus_dir}/." -name "${type}@*" -print
}

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

# This overrides a multiplexed function.

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

    # Get PCI dom, bus, dev, fun.
    local pci_data=$(get_pci_info "$type" "$name")

    # Find device-tree node.
    if [ -n "$pci_data" ] ; then
	local bus_dir=$(dt_find_adapter $pci_data)

	if [ -n "$bus_dir" ] ; then
	    local subdir=$(get_adapter_subdir "$type")
	    local os_dir="${db_os_dir}/${subdir}/${name}"

	    mkdir -p "$os_dir"
	    cross_link "$bus_dir" "$os_dir"
	else
	    debug "match_adapter: no device-tree node for ${pci_data}"
	fi
    else
	debug "match_adapter: no PCI info for \"${name}\""
    fi
}

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

function do_device_hook_scsi ()
{
    local adapter_os_dir adapter_bus_dir os_dir
    dt_do_device_hook_common

    if [ -n "$adapter_bus_dir" ] ; then
	# Check for multi-channel card.
	# Don't really need dec2hex, since only small numbers?
	local bus_hex=$(dec2hex "$bus")
	local maybe="${adapter_bus_dir}/scsi@${bus_hex}"
	if [ -d "$maybe" ] ; then
	    adapter_bus_dir="$maybe"
	fi

	dt_do_device_hook_post \
	    "$device_type" $(dec2hex "$target") $(dec2hex "$lun")
    else
	debug "do_device_hook_scsi: no device-tree node for $adapter"
	return 1
    fi
}

function do_device_hook_ide ()
{
    local adapter_os_dir adapter_bus_dir
    dt_do_device_hook_common

    if [ -n "$adapter_bus_dir" ] ; then
	local dt_type
	case "$device_type" in
	    cdrom) dt_type="disk"         ;;
	    *)     dt_type="$device_type" ;;
	esac

	# FIXME!!! This is a guess.  May be @ 2 * channel + unit.
	# Same for location below.
	dt_do_device_hook_post \
	    "$dt_type" $(dec2hex "$channel") $(dec2hex "$unit")
    else
	debug "do_device_hook_ide: no device-tree node for $adapter"
	return 1
    fi
}

function dt_do_device_hook_common ()
{
    local subdir=$(get_adapter_subdir "$type")
    adapter_os_dir="${db_os_dir}/${subdir}/${adapter}"
    local f="${adapter_os_dir}/bus-node"
    if [ -L "$f" ] ; then
	local l=$(readlink "$f")

	if [ -n "$l" ] ; then
	    adapter_bus_dir="${adapter_os_dir}/${l}"
	fi
    fi
}

function dt_do_device_hook_post ()
{
    local device_type="$1"
    local maj_hex="$2"
    local min_hex="$3"

    local bus_dir="${adapter_bus_dir}/${device_type}@${maj_hex}"
    if [ "$min_hex" -ne 0 ] ; then
	bus_dir="${bus_dir},${min_hex})"
    fi

    local os_dir="${adapter_os_dir}/${name}"
    mkdir -p "$bus_dir"
    mkdir -p "$os_dir"
    cross_link "$bus_dir" "$os_dir"

    if [ -f "${bus_dir}/ibm,loc-code" ] ; then
	# Subshell strips NULs.
	yl=$(cat "${bus_dir}/ibm,loc-code")
    else
	# Subshell strips NULs.
	yl=$(cat "${adapter_bus_dir}/ibm,loc-code")
	if [ -n "$yl" ] ; then
	    yl="${yl}-A${maj_hex}"
	    if [ "$min_hex" != "0" ] ; then
		yl="${yl},${min_hex}"
	    fi
	fi
    fi

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

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

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

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

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

    # If this is a multi-channel adapter, the information for the PCI
    # adapter will be one level up...  and there won't be a bus-range
    # prop.  This might only apply to SCSI, but let's try it for
    # everything.
    # FIXME: is this logic right?
    local old="$node"
    if [ ! -f "${node}/../bus-range" ] ; then 
	node=$(dirname "$node")
    fi

    local ax osname
    adapter_set_ax_osname_hook

    if [ "$node" != "$old" ] ; then
	# If "$old" already has VPD property, ensure it has AX.
	if [ -f "${node}/linux,vpd" ] ; then
	    ensure_field "${node}/linux,vpd" "AX" "$ax"
	    local prm=$(get_adapter_firmware_version "$node")
	    [ -n "$prm" ] && override_field "${node}/linux,vpd" "RM" "$prm"
	fi

	channel=$(basename "$old" |
	    sed -n -e "s/${type}@//" -e 's/^\([0-9]*\)$/\1/p')
	[ -n "$channel" ] && ax="${ax}:${channel}"
    fi

    # If there's VPD prop, we're nearly done.
    if [  -f "$vpdprop" ] ; then
	[ -n "$rm" ] && override_field "$vpdprop" "RM" "$rm"
	return 0
    fi

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

    if [ "$node" != "$old" -a -f "${node}/linux,vpd" ] ; then
	# Already have useful VPD for parent.  No need to repeat PCI info.
	print_adapter_vpd_hook >"$vpdprop"
    else
	dt_do_pci_adapter_hook
    fi
}

function do_adapter_display ()
{
    dt_do_adapter_fake "$@"
}

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

unset -f do_adapter_hook_ide

function do_adapter_hook_DEFAULT ()
{
    ds=$(get_adapter_ds "$type")
    yl=$(manufacture_yl "$node")
}

function do_adapter_hook_scsi ()
{
    do_adapter_hook_DEFAULT

    if [ -f "${node}/ibm,fw-adapter-name" ] ; then
	ds="$(cat ${node}/ibm,fw-adapter-name) ${ds}"
	if [ -f "${node}/wide" ] ; then
	    ds="Wide/${ds}"
	fi
    fi

    rm=$(get_adapter_firmware_version "$node")
}

function do_adapter_hook_ethernet ()
{
    do_adapter_hook_DEFAULT

    if [ -f "${node}/ibm,fw-adapter-name" ] ; then
	ds="$(cat ${node}/ibm,fw-adapter-name)"
    fi
}

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

function check_bus_range ()
{
    local bus_range_file="$1"
    local bus_num=$(hex2dec "$2")

    # bus-range contains 2 (network order) longs, but only the bottom
    # byte can be non-zero.
    set -- $(tdump "$bus_range_file")
    local min=$(hex2dec "$4")
    local max=$(hex2dec "$8")
    if [ "$min" -le "$bus_num" -a "$bus_num" -le "$max" ] ; then
	return 0
    else
	return 1
    fi
}

function check_bus_num ()
{
    local bus_range_file="$1"
    local bus_num=$(hex2dec "$2")

    # bus-range contains 2 (network order) longs, but only the bottom
    # byte can be non-zero.
    set -- $(tdump "$bus_range_file")
    local min=$(hex2dec "$4")

    # Only the minimum bus number in the range on a PCI-PCI bridge is
    # for the things hanging off that bridge.  The other (higher)
    # numbers in the range are for buses behind PCI-PCI bridges that
    # are behind this one.
    if [ "$min" -eq "$bus_num" ] ; then
	return 0
    else
	return 1
    fi
}

function check_pci_domain ()
{
    local pci_domain_file="$1"
    local dom=$(hex2dec "$2")

    # $pci_domain contains an unsigned long in network order.
    set -- $(tdump "$pci_domain_file")
    local t=$((0x$1 * 4096 + 0x$2 * 256 + 0x$3 * 16 + 0x$4))
    if [ $t -eq $dom ] ; then
	return 0
    else
	return 1
    fi
}

function dt_find_adapter ()
{
    # These are hexadecimal.
    local dom="$1"
    local bus="$2"
    local dev="$3"
    local fun="$4"

    local phb=""

    # If this is a bugnum machine, do we have PCI domain properties?
    # FIXME: Perhaps this should error if the loop doesn't set
    # $pci_domain_prop?
    local pci_domain_prop i
    if [ -f "${db_bus_dir}/rtas/ibm,read-pci-config" ] ; then
	for i in "linux,pci-domain" "linux,global-number" "linux,phbnum" ; do
	    if [ -f "${db_bus_dir}/pci/${i}" ] ; then
		pci_domain_prop="$i"
		break
	    fi
	done
    fi

    # Find the top-level PCI host bus directory.
    if [ -n "$pci_domain_prop" ] ; then
	# This is a bignum machine.
	for i in ${db_bus_dir}/pci@*/${pci_domain_prop} ; do
	    if check_pci_domain "$i" "$dom" ; then
		phb=$(dirname $i)
		# There will only be one match.
		break
	    fi
	done
    else
	# Normal machine.
	for i in ${db_bus_dir}/pci@*/bus-range ; do
	    if check_bus_range "$i" "$bus" ; then
		phb=$(dirname $i)
		# There will only be one match.
		break
	    fi
	done
    fi

    if [ -z "$phb" ] ; then
	debug "$0: failed to find PHB for ${dom}:${bus}:${dev}.${fun}"
	return 1
    fi

    # Search for bus number.
    local dir="$phb"
    while : ; do
	local br="${dir}/bus-range"
	if check_bus_num "$br" "$bus" ; then
	    # Got it!  Removed any leading "0x".
	    local devfun=$(printf "%x" "$dev")
	    # ",$func" gets appended if $func is non-zero.
	    if [ $(hex2dec "$fun") -ne 0 ] ; then
		devfun="${devfun},"$(printf "%x" "$fun")
	    fi
	    echo "$dir/"?*"@${devfun}"
	    break
	else
	    # Find the next candidate.
	    for i in "${dir}/pci@"* ; do
		br="${i}/bus-range"
		if [ -f "$br" ] && check_bus_range "$br" "$bus" ; then
		    dir="$i"
		    continue 2
		fi
	    done

	    # No next candidate?  Something's gone horribly wrong!
	    debug "dt_find_adapter: Internal error - could not find PCI bus $bus in $phb"
	    return 1
	fi
    done

    return 0
}

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

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

    local f="${node}/linux,vpd"

    local n=$(basename $(dirname "$f"))
    n="${n%@*}"
    n="${n:0:4}"

    if [ -f "$f" ] ; then
	if ensure_field "$f" "AX" "${n}${num}" ; then
	    num=$(($num + 1))
	fi
    fi
}

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

function manufacture_yl ()
{
    local d="$1"
    while [ ! -r "$d/ibm,loc-code" -a "$d" != "$db_bus_dir" ] ; do
	d=$(dirname "$d")
    done
    if [ -r "$d/ibm,loc-code" ] ; then
	# Strip NUL, ensure newline.
	echo $(cat "${d}/ibm,loc-code")
    fi
}

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

function dt_do_pci_adapter_hook () {

    local subvendor subdevice cd
    if [ -f "${node}/subsystem-vendor-id" -a -f "${node}/subsystem-id" ] ; then
	set -- $(tdump "${node}/subsystem-vendor-id")
	local b1="$3"
	local b2="$4"
	set -- $(tdump "${node}/subsystem-id")
	local b3="$3"
	local b4="$4"

	subvendor="${b1}${b2}"
	subdevice="${b3}${b4}"
	cd="${subvendor}${subdevice}"

	local dscd="${b2}${b1}${b4}${b3}"
	[ -n "$dscd" ] && ds="${ds} (${dscd})"
    fi

    local vendor device
    if [ -f "${node}/vendor-id" -a -f "${node}/device-id" ] ; then
	set -- $(tdump "${node}/vendor-id")
	local b1="$3"
	local b2="$4"
	set -- $(tdump "${node}/device-id")
	local b3="$3"
	local b4="$4"

	vendor="${b1}${b2}"
	device="${b3}${b4}"
    fi

    local mf tm
    pci_set_mf_tm_hook

    print_adapter_vpd_hook >"$vpdprop"
}
