# -*- 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, 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: 30device-tree,v 1.43 2004/10/22 07:22:43 martins Exp $

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

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

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

if [ -f "${source_device_tree}/device_type" ] ; then
    read device_tree_device_type <"${source_device_tree}/device_type"
fi

db_subdir_vars_hooks="${db_subdir_vars_hooks} db_set_subdir_vars_dt"
db_set_subdir_vars_dt

db_initialise_bus ()
{
    # FIXME: Try testing with this disabled to test "hotplug" code.
    ensure_directory "$db_bus_dir"
    cp -a "$source_device_tree" "$db_bus_dt_dir"
}

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

# Adapters.

dt_adapter_init_hooks=""

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

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

    if [ -z "$d" ] ; then
	debug "adapter_init_node: bus directory not found for \"${bus_info}\""
	return 1
    fi

    # Sanity check.
    if [ "$d" == "${d#${db_bus_dt_dir}}" ] ; then
	debug "adapter_init_node: bus node not under \"${db_bus_dir}\""
	return 1
    fi

    # Find how much of the device tree is missing?  Above tests
    # guarantee loop terminatation.
    while [ ! -d "$d" -a "$d" != "$db_bus_dt_dir" ] ; do
	d="${d%/*}" # dirname
    done

    if [ "$d" != "$db_bus_dt_dir" ] ; then
	# Copy over the missing part of the device tree.
	s="${source_device_tree}${d#${db_bus_dt_dir}}"
	if [ -d "$s" ] ; then
	    ensure_directory "$d"
	    cp -a "${s}/." "${d}/"
	else
	    debug "adapter_init_node: missing device-tree node \"$s\""
	fi
    fi

    local i
    for i in $dt_adapter_init_hooks ; do
	$i "$d"
    done

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

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

do_adapter_crosslink ()
{
    local type="$1"
    local bus_info="$2"
    local name="$3"

    local adapter_bus_dir adapter_os_dir

    do_adapter_crosslink_basic "$type" "$bus_info" "$name"

    # channels: fun for all the family...  :-)
    local i ch
    for i in "${adapter_bus_dir}/${type}@"* ; do
	local ch=$((0x${i##*@}))
	local d="${adapter_os_dir}:${ch}"
	ensure_directory "$d"
	cross_link "$i" "$d"
    done
}

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

make_multiplexed dt_list_adapters_setup
make_multiplexed dt_adapter_crosslink

list_adapters_DEFAULT ()
{
    local type="$1"

    # Could do a single find for all adapter types (in
    # db_initialise_bus) and create a map, but BusyBox's find doesn't
    # support "-o", so we might as well do a find per type in this
    # function.
    local n bus_info subdir

    dt_list_adapters_setup "$type"

    for n in $(find "${source_device_tree}" -name "${type}@*" -print) ; do
	local bus_info=""
	dt_set_bus_info_hook "$n"
	if [ -n "$bus_info" ] ; then
	    local relnode="${db_bus_dt_subdir}/${n#${source_device_tree}/}"
	    bus_alias_set "$bus_info" "$relnode"
            # Has channel?  Also alias without channel.
	    case "$bus_info" in
		*/*/*)
		    bus_alias_set "${bus_info%/*}" "${relnode%/*}" # dirname
		    ;;
	    esac

	    dt_adapter_crosslink "$type" "$bus_info" "$n"

	    echo "$bus_info"
	else
	    debug "list_adapters: failed to get bus_info for \"${n}\""
	fi
    done
}

list_adapters_scsi ()
{
    list_adapters_DEFAULT "scsi"
    list_adapters_DEFAULT "fibre-channel"
    list_adapters_DEFAULT "v-scsi"
}

list_adapters_ethernet ()
{
    list_adapters_DEFAULT "ethernet"
    list_adapters_DEFAULT "l-lan"
}

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

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

    bus_alias_get "$bus_info"
}

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

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

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

    if [ ! -n "$node" ] ; then
	debug "do_adapter_DEFAULT: failed to get node for \"$bus_info\""
	return
    fi

    [ ! -d "$node" ] && \
	adapter_init_node "$type" "$bus_info"

    local ds yl
    do_adapter_hook "$type"

    local vpd_dir

    # Assumption: if $node is for a channel in a multi-channel
    # adapter, it will not contain a "class-code" property.
    local have_parent_vpd=false
    local pci_node="$node"
    if [ ! -f "${node}/class-code" ] ; then
	# Attempt to do parent VPD.
	case "$bus_info" in
	    */*/*)
		do_adapter "$type" "${bus_info%/*}"
		have_parent_vpd=true
		;;
	esac

	# If the parent is built-in, then the channel is too.
	[ -f "${node%/*}/built-in" ] && >"${node}/built-in"
    fi

    # If there's already VPD, ensure YL exists, run extra hooks, and
    # we're done.
    vpd_dir_set_hook "$node"
    if [ -e "$vpd_dir" ] ; then
	vpd_field_ensure "$vpd_dir" "YL" "$yl"
	$have_parent_vpd || \
	    adapter_extra_vpd_hook "$type" "$bus_info" "$node"
	return 0
    fi

    # Process PCI vendor/device only if parent has no useful VPD.
    local mf tm cd
    if ! $have_parent_vpd ; then
	case "$bus_info" in
	    (pci/*) dt_do_pci_adapter_hook "$node" ;;
	esac
    fi

    # Render.
    local rl rm sn

    vpd_create_hook "$node"

    $have_parent_vpd || \
	adapter_extra_vpd_hook "$type" "$bus_info" "$node"
}

do_adapter_display ()
{
    local bus_info="$1"

    dt_do_adapter_fake "display" "$bus_info"
}

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

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

unset -f do_adapter_hook_ide

do_adapter_hook_DEFAULT ()
{
    ds=$(get_adapter_ds "$type")

    dt_manufacture_yl_hook "$node"
}

do_adapter_hook_scsi ()
{
    do_adapter_hook_DEFAULT

    # Horrible hack.
    case "$node" in
	(*fibre-channel@*) ds="Fibre Channel Adapter" ;;
    esac

    if [ -f "${node}/ibm,fw-adapter-name" ] ; then
	local t
	read t <"${node}/ibm,fw-adapter-name"
	ds="${t} ${ds}"
	if [ -f "${node}/wide" ] ; then
	    ds="Wide/${ds}"
	fi
    fi
}

do_adapter_hook_ethernet ()
{
    do_adapter_hook_DEFAULT

    if [ -f "${node}/ibm,fw-adapter-name" ] ; then
	read ds <"${node}/ibm,fw-adapter-name"
    fi
}

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

# Devices.

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

    local adapter_bus_dir

    if ! add_device_hook_common ; then
	debug "add_device_hook_scsi: no device-tree node for $adapter"
	return 1
    fi

    local host bus target lun
    scsi_parse_addr_hook "${bus_info#*/}"
    
    # Check for multi-channel card.
    local bus_hex=$(dec2hex "$bus")
    local maybe="${adapter_bus_dir}/scsi@${bus_hex}"
    if [ -d "$maybe" ] ; then
	adapter_bus_dir="$maybe"
    fi
    
    local id="$target"
    local id_hex=$(dec2hex "$id")
    local subid="$lun"
    local subid_hex=$(dec2hex "$subid")
    device_bus_dir="${adapter_bus_dir}/${device_type}@${id_hex}"
    if [ $lun -ne 0 ] ; then
	device_bus_dir="${device_bus_dir},${subid_hex}"
    fi

    dt_add_device_hook_post
}

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

    local adapter_bus_dir

    if ! add_device_hook_common ; then
	debug "add_device_hook_ide: no device-tree node for $adapter"
	return 1
    fi

    local dt_type
    case "$device_type" in
	cdrom) dt_type="disk"         ;;
	*)     dt_type="$device_type" ;;
    esac

    local id="$number"
    local subid=""
    local id_hex=$(dec2hex "$id")
    local subid_hex=""
    device_bus_dir="${adapter_bus_dir}/${dt_type}@${id_hex}"
    
    dt_add_device_hook_post
}

dt_add_device_hook_post ()
{
    # Uses: bus_info, device_bus_dir, id, subid, id_hex, subid_hex
    # Sets: yl

    local t=$(pseudo_realpath "$device_bus_dir")
    bus_alias_set "$bus_info" "${t#${db_bus_dir}/}"

    if [ -f "${device_bus_dir}/ibm,loc-code" ] ; then
	dt_manufacture_yl_hook "$device_bus_dir"
    else
	dt_manufacture_yl_hook "$adapter_bus_dir"
	dt_device_yl_hook
    fi
}

dt_device_yl_hook ()
{
    # Uses: yl, id, subid, id_hex, subid_hex
    # Sets: yl
    if [ -n "$yl" ] ; then
	yl="${yl}-A${id_hex}"
	if [ -n "$subid_hex" -a "$subid_hex" != "0" ] ; then
	    yl="${yl},${subid_hex}"
	fi
    fi
}

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

set_dt_pci_domain_prop_hook ()
{
    local d="${source_device_tree}/pci"
    local big_bus_num_flag="${source_device_tree}/rtas/ibm,read-pci-config"

    # Under 2.6, linux,pci-domain is always used.
    if [ -f "${d}/linux,pci-domain" ] ; then
	dt_pci_domain_prop="linux,pci-domain"
	return
    fi

    # Under 2.4, only take notice of these on big-bus-num machines.
    if [ -f "$big_bus_num_flag" ] ; then
	local i
	for i in "linux,phbnum" "linux,global-number" ; do
	    if [ -f "${d}/${i}" ] ; then
		dt_pci_domain_prop="$i"
		return
	    fi
	done
    fi
    
    # Sanity check.
    [ -f "$big_bus_num_flag" ] && \
	: "set_dt_pci_domain_prop_hook:" \
	"unable to determine PCI domain property name"
}

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

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

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

    if [ ! -n "$node" ] ; then
	debug "do_adapter_DEFAULT: failed to get node for \"$bus_info\""
	return
    fi

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

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

dt_manufacture_yl_hook ()
{
    # Sets: yl
    yl=""

    local d="$1"
    while [ ! -r "$d/ibm,loc-code" -a "$d" != "$db_bus_dt_dir" ] ; do
	d="${d%/*}" # dirname
    done

    [ -r "$d/ibm,loc-code" ] && { read yl <"${d}/ibm,loc-code" ; true ; }
}

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

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

    local node="$1"

    local subvendor subdevice
    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

    pci_set_mf_tm_hook
}

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

dt_set_bus_info_hook ()
{
    local node="$1"

    local bus_type dom bus dev fun channel

    # Sanity check to ensure the loop below will terminate.
    [ "${node#${source_device_tree}}" = "$node" ] && return

    # Get property name for PCI domain on IBM pSeries big-bus-num machines.
    [ -z "$dt_pci_domain_prop" ] && set_dt_pci_domain_prop_hook

    local d="$node"

    while [ "$d" != "$source_device_tree" ] ; do
	local device_type=""
	[ -f "${d}/device_type" ] && read device_type <"${d}/device_type"

	case "$device_type" in
	    pci)
	        # The range bottom of the 1st PCI bus-range we hit is the bus#.
		if [ -z "$bus" ] ; then
		    set -- $(tdump "${d}/bus-range")
		    bus_type="pci"
		    bus="$4"
		fi
		
	        # Found a PCI domain property: remember it & we're done!
		if [ -n "$dt_pci_domain_prop" -a \
		    -f "${d}/${dt_pci_domain_prop}" ] ; then
		    dom=$(tdump "${d}/${dt_pci_domain_prop}")
		    dom="${dom// /}"
		    break
		fi
		;;
	    vdevice)
		bus_type="vdevice"
		break
		;;
	    *)
	        # Channel possibly only in original basename.
		[ -z "$channel" ] && channel="$dev"
		
	        # Adapter node?  Put dev[,fun] into $dev. 
		local bn="${d##*/}" # basename
		dev="${bn#*@}"
		;;	    
	esac

	d="${d%/*}" # dirname
    done

    local result

    case "$bus_type" in
	pci)
	    if [ -n "$bus" -a -n "$dev" ] ; then
		[ -n "$dom" ] || dom=0
		
		fun=0
		case "$dev" in
		    # Note: order is important here.  :-)
		    (*,*) fun="${dev#*,}" ; dev="${dev%,*}" ;;
		esac
		
		result=$(printf "%04x:%02x:%02x.%x" \
		    "0x${dom}" "0x${bus}" "0x${dev}" "0x${fun}")
		
		[ -n "$channel" ] && result="${result}/${channel}"
	    fi
	    ;;
	vdevice)
	    result="$dev"
	    ;;
    esac

    [ -n "$bus_type" -a -n "$result" ] && \
	bus_info="${bus_type}/${result}"
}
