# -*- shell-script -*-

# 00minimal - Default hardware database scanning routines and variables.

# 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: 00minimal,v 1.22 2004/02/19 03:28:06 martins Exp $

# Default and common functions.

# This module is always loaded.
true || return 0

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

db_initialise_functions="db_initialise_basic db_initialise_bus"

function db_initialise ()
{
    for f in $db_initialise_functions ; do
	$f
    done
}

function db_initialise_bus ()
{
    mkdir -p "${db_bus_dir}"
}

function db_initialise_basic ()
{
    mkdir -p "$db"

    db_set_subdir_vars_hook

    db_initialise_misc
}

function db_initialise_misc ()
{
    mkdir -p "$db_misc_dir"

    db_initialise_misc_uname
    db_initialise_misc_proc
}

# It might be possible to reparse the output of "uname -a", but this
# is likely to be much more systematic.
function db_initialise_misc_uname ()
{
    mkdir -p "$db_uname_dir"

    for i in "kernel-name" "kernel-release" "kernel-version" \
	"machine" "nodename" ; do
      # Hey, there're 2 completely different versions of uname!
      local flag
      case "$i" in
	  kernel-name)    flag="-s"     ;;
	  kernel-release) flag="-r"     ;;
	  kernel-version) flag="-v"     ;;
	  *)              flag="--${i}" ;;
      esac
      uname "$flag" > "${db_uname_dir}/${i}"
    done
}

function db_initialise_misc_proc ()
{
    mkdir -p "$db_proc_dir"

    for i in "partitions" "cpuinfo" ; do
	[ -f "/proc/${i}" ] && cat "/proc/${i}" >"${db_proc_dir}/${i}"
    done
}

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

function get_pci_config_filename ()
{
    local dom="$1"
    local bus="$2"
    local dev="$3"
    local fun="$4"

    local t="/proc/bus/pci"
    local conf=$(printf "${t}/%04x:%02x/%02x.%x" "$dom" "$bus" "$dev" "$fun")
    if [ ! -f "$conf" ] ; then
	if [ -z "$dom" -o $(($dom)) -eq 0 ] ; then
	    conf=$(printf "${t}/%02x/%02x.%x" "$bus" "$dev" "$fun")
	fi
    fi

    [ -f "$conf" ] && echo "$conf"
}

# This is a bit of a hack.  lsvpd needs a -m option to use the FC
# field in VPD to mark as global or partition-private.  We'll
# implement it here, and then filter in lsvpd if the option isn't
# used.
function do_fc_field ()
{
    local dir="$1"

    # By default, VPD is marked global.
    local delimiter='********'
    # VPD is partition-local if device-tree has ibm,partition-no property...
    if [ -f "${db_bus_dir}/ibm,partition-no" ] ; then
	# ... and is not built-in, or is built-in and has ibm,vpd....
	local bi="${dir}/built-in"
	if [ ! -f "$bi" -o \( -f "$bi" -a -f "${dir}/ibm,vpd" \) ] ; then
	    # ... and this or parent PCI node has ibm,my-drc-index property.
	    local d="$dir"
	    while [ "$delimiter" = '********' -a \
		-n "$d" -a "$d" != "/" -a "$d" != "$db_bus_dir" ] ; do
	      local b=$(basename "$d")
	      case "$b" in
		  (pci@*) [ -f "${d}/ibm,my-drc-index" ] && \
		      delimiter='========' ;;
	      esac
	      d=$(dirname "$d")
	    done
	fi
    fi
    echo "*FC ${delimiter}"
}

function render_ibm_vpd ()
{
    local dir="$1"

    local i="${dir}/ibm,vpd"
    local l="${dir}/linux,vpd"

    if [ -f "$i" -a ! -f "$l" ] ; then
	if debug_cmd ibm_vpd_render "$i" >"$l" ; then
	    local fc=$(do_fc_field "$dir")
	    sed -n \
		-e "s@^\*FC .*@${fc}@" "$l" \
		-e 's/^\*.. [[:print:]][[:print:]]*$/&/p' > "${l}.tmp"
	    mv "${l}.tmp" "$l"
	else
	    rm -f "$l"
	    return 1
	fi
    fi
}

function do_pci22_vpd ()
{
    local dom="$1"
    local bus="$2"
    local dev="$3"
    local fun="$4"
    local dir="$5"

    local i="${dir}/ibm,vpd"
    local l="${dir}/linux,vpd"

    if [ ! -f "$i" -a ! -f "$l" ] ; then
	local conf=$(get_pci_config_filename "$dom" "$bus" "$dev" "$fun")
	if [ -f "$conf" ]  ; then
	    if debug_cmd pci_vpd_cap_grab "$conf" >"$i" ; then
		render_ibm_vpd "$dir"
	    else
		rm -f "$i"
	    fi
	fi
    fi
}

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

# This will allow a calls like:
#
#   do_device ide /dev/hda
#
# which will try to call
#
#   do_device_ide /dev/hda
#
# if it exists.  If not, the next attempt will be
#
#   do_device_DEFAULT ide /dev/hda
#
# If the DEFAULT function doesn't exist, failure is silent.

make_multiplexed adapter_setup
make_multiplexed list_os_adapters
make_multiplexed match_adapter
make_multiplexed list_db_adapters
make_multiplexed do_adapter
make_multiplexed do_adapter_hook

make_multiplexed device_setup
make_multiplexed list_devices
make_multiplexed do_device
make_multiplexed do_device_hook

make_multiplexed get_adapter_os_prefix
make_multiplexed get_adapter_output_prefix
make_multiplexed get_adapter_subdir
make_multiplexed get_adapter_ds

make_multiplexed get_pci_info
make_multiplexed get_pci_class

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

# This group of functions is horrible.  However, under Linux 2.4, on
# bignum machines (such as larger ppc64), PCI bus numbers are handled
# inconsistently and sometimes you only see the bottom 8 bits.  This
# code does extra matching on the class and the IRQ.
function get_bus_dev_fun_irq_class_list ()
{
    local busdevfun class irq

    /sbin/lspci -nv |
    while read line ; do
	case "$line" in
	    *Class\ [0-9a-f][0-9a-f][0-9a-f][0-9a-f]:*)
		busdevfun="${line%% *}"
		class="${line#*Class }"
		class="${class%%:*}"
		;;
	    *IRQ\ [0-9]*)
		if [ -n "$busdevfun" ] ; then
		    irq="${line#*IRQ }"
		    irq="${irq%%,*}"
		    echo "${busdevfun}-${irq}-${class}"
		fi
		;;
	esac
    done
}

function get_real_bus ()
{
    local type="$1"
    local unreal="$2"
    local ret

    local class=$(get_pci_class "$type")
    unreal="${unreal}-${class}"

    # This variable should not be local!
    if [ -z "$bus_dev_fun_irq_class_list" ] ; then
	bus_dev_fun_irq_class_list=$(get_bus_dev_fun_irq_class_list)
    fi

    declare -i n=0
    for i in $bus_dev_fun_irq_class_list ; do
	case "$i" in
	    (*${unreal}) ret="${ret} ${i%%:*}" ; n=n+1 ;;
	esac
    done

    if [ $n -ne 1 ] ; then
	debug "get_real_bus: failed to find real PCI bus number for \"${unreal}\""
	return 1
    fi

    hex2dec $ret
}

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

function print_adapter_vpd_hook ()
{
    do_fc_field "$node"  # Watch this carefully!

    echo "*DS ${ds}"
    echo "*AX ${ax}"

    [ -n "$mf" ] && echo "*MF ${mf}"
    [ -n "$tm" ] && echo "*TM ${tm}"
    [ -n "$cd" ] && echo "*CD ${cd}"
    [ -n "$rl" ] && echo "*RL ${rl}"
    [ -n "$rm" ] && echo "*RM ${rm}"
    [ -n "$yl" ] && echo "*YL ${yl}"
}

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

# Expects $node, $type to be set by enclosing function.  Directory
# $node should contain a link called linux,os-node (dropped there by
# match_adapter) whose basename is the operating system's name for the
# adapter.  $num should be a "global" variable suitable for
# manufacturing adapter names in sequence, and will be incremented if
# used.
function adapter_set_ax_osname_hook ()
{
    local os_prefix=$(get_adapter_os_prefix "$type")
    local out_prefix=$(get_adapter_output_prefix "$type")

    if [ -L "${node}/linux,os-node" ] ; then
	osname=$(basename $(readlink "${node}/linux,os-node"))
	ax="$osname"
	[ "$os_prefix" != "$out_prefix" ] && \
	    ax="${out_prefix}${ax#${os_prefix}}"
    else
	# Not known by OS, use sequence number.
	osname=""
	ax="${out_prefix}${num}"
	num=$(($num + 1))
    fi
}

function pci_set_mf_tm_hook ()
{
    # Uses variables: vendor, device, ss_vendor, ss_device

    # This is to workaround a broken adapter.  We're much more likely
    # to see the Ethernet adapter from AMD (1022) than the display
    # adapter from Trident (1023).
    if [ "$vendor" = "1023" -a "$device" = "2000" ] ; then
	vendor="1022"
    fi

    if [ -n "$vendor" -a -n "$device" ] ; then
	if [ -n "$subvendor" -a -n "$subdevice" ] ; then
	    tm=$(pci_lookup "${libdir}/pci.ids" \
		"$vendor" "$device" "$subvendor $subdevice")
	    if [ -n "$tm" ] ; then
		mf=$(pci_lookup "${libdir}/pci.ids" "$subvendor")
		[ -z "$mf" ] && mf="Unknown (0x${subvendor})"
	    fi
	fi

	if [ -z "$mf" -a -z "$tm" ] ; then
	    tm=$(pci_lookup "${libdir}/pci.ids" "$vendor" "$device")
	    mf=$(pci_lookup "${libdir}/pci.ids" "$vendor")
	fi

	[ -z "$mf" ] && mf="Unknown (0x${vendor})"
	[ -z "$tm" ] && tm="Unknown (0x${device})"
    fi
}

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

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

    :
}

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

function add_field () {
    local vpdprop="$1"
    local field="$2"
    local value="$3"

    case "$field" in
	AX)
	    sed -e "/^*DS /a\\
*${field} ${value}" "$vpdprop" > "${vpdprop}.tmp"
	    mv "${vpdprop}.tmp" "$vpdprop"
	    ;;
	*)
	    echo "*${field} ${value}" >>"$vpdprop"
    esac
}

function ensure_field () {
    local vpdprop="$1"
    local field="$2"
    local value="$3"

    if grep -q "^*${field} " "$vpdprop" ; then
	return 1
    else
	add_field "$vpdprop" "$field" "$value"
	return 0
    fi
}

function override_field () {
    local vpdprop="$1"
    local field="$2"
    local value="$3"

    if grep -q "^*${field} " "$vpdprop" ; then
	    sed -e "s/^\*${field} .*/*${field} ${value}/" \
		"$vpdprop" > "${vpdprop}.tmp"
	    mv "${vpdprop}.tmp" "$vpdprop"
	
    else
	add_field "$vpdprop" "$field" "$value"
    fi
}

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

function get_dot_dots ()
{
    local d="$1"
    local t="$2"

    if [ "${d}" = "${d#${t}}" ] ; then
	debug "get_dot_dots: ${t} is not a prefix of ${d}"
	return 1
    fi

    local dot_dots=".."
    while [ -n "$d" -a "$d" != "$t" ] ; do
	dot_dots="${dot_dots}/.."
	local d=$(dirname "$d")
    done

    echo "$dot_dots"
}

function cross_link ()
{
    local bus_d=$(pseudo_realpath "$1")
    local os_d=$(pseudo_realpath "$2")

    local bus_path="${bus_d#${db_bus_dir}/}"
    local os_path="${os_d#${db_os_dir}/}"

    local bus_dds=$(get_dot_dots "$bus_d" "$db_bus_dir")
    local os_dds=$(get_dot_dots "$os_d" "$db_os_dir")

    ln -s "${bus_dds}/${db_os_subdir}/${os_path}" "${bus_d}/linux,os-node"
    ln -s "${os_dds}/${db_bus_subdir}/${bus_path}" "${os_d}/bus-node"
}

function pseudo_realpath ()
{
    local f="$1"

    case "$f" in
	(/*) :                   ;;
	(*)  f="${PWD}/${f}"     ;;
    esac

    local out=""
    local IFS="/"
    for i in $f ; do
	case "$i" in
	    (.|"")  : ;;
	    (..) out="${out%/*}" ;;
	    (*)  out="${out}/${i}"     ;;
	esac
    done
    [ -z "$out" ] && out="/"
    echo "$out"
}
