#! /bin/bash

# Copyright (c) 2004 International Business Machines
#
# 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
# of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Author Nathan Fontenot <nfont@linux.vnet.ibm.com>

#
# ofpathname - This utility provides a mechanism for converting a logical
# device name to an open firmware device path, and vice versa.
#
# TODO: This script doesn't handle floppy drives and token ring devices,
#       perhaps they should be added in at some point.
#

OFPATHNAME="ofpathname"
VERSION="0.5"
FIND=/usr/bin/find
CAT=/bin/cat
PSERIES_PLATFORM=$(dirname $0)/pseries_platform

# Find out what platfrom we are running on.  Hopefully this
# list will get expanded with time.
PLATFORM=$(sed /proc/cpuinfo -ne "s/^machine\t*: \(.*\)/\1/p")
case $PLATFORM in
    EFIKA5K2\ *)	PLATFORM=efika ;;
    PowerBook*)		PLATFORM=mac ;;
    PowerMac*)		PLATFORM=mac ;;
esac

# Usage statemnet
usage()
{
    echo "Usage: $OFPATHNAME [OPTION] DEVICE"
    echo "Provide logical device names <==> Open Firmware Device Path Conversion"
    echo ""
    echo "Optional arguments."
    echo "  -l               Convert Open Firmware device pathname to"
    echo "                   logical device name."
    echo "  -a               Find matching Open Firmware device alias[es]."
    echo "  -q, --quiet      Do not report failures, exit quietly"
    echo "  -V, --version    Display version information and exit"
    echo "  -h, --help       Display this help information and exit"
    echo ""
}

show_version()
{
    echo "$OFPATHNAME: Version $VERSION"
    echo "Written by: Nathan Fontenot <nfont@linux.vnet.ibm.com>"
}

#
# err
# Common routine to print error messages for ofpathname.  Since most of the
# error messages can be generated in multiple places, we put all the text
# here to avoid errors in duplicating the messages.
#
# The first and only parameteris the error message number, all of which
# are defined below as ERR_*.
#
ERR_NO_OFPATH=1
ERR_NO_SYSFS=2
ERR_NO_SYSFS_DEVINFO=3
ERR_NOT_CONFIG=4
ERR_NO_LOGDEV=5
err()
{
    local emsg=$1

    if [[ -n $be_quiet ]]; then
        exit 1
    fi

    case $emsg in
        1)  echo "$OFPATHNAME: Could not retrieve Open Firmware device path" 1>&2
            echo "            for logical device \"$DEVNAME_ARG\"." 1>&2 ;;

        2)  echo "$OFPATHNAME: sysfs (/sys) is needed and does not appear" 1>&2
            echo "            to be mounted on this system." 1>&2 ;;

        3)  echo "$OFPATHNAME: Could not find sysfs information for logical" 1>&2
            echo "            device \"$DEVNAME_ARG\"." 1>&2 ;;

        4)  echo "$OFPATHANME: Logical device \"$DEVNAME_ARG\" does not appear" 1>&2
            echo "            to be configured." 1>&2 ;;

        5)  echo "$OFPATHNAME: Could not retrieve logical device name for" 1>&2
            echo "            Open Firmware path \"$DEVNAME_ARG\"." 1>&2 ;;
    esac

    exit 1
}

# is_hbtl
# return true if the link is in HBTL (Host:Bus:Target ID:LUN) format
is_hbtl()
{
        local ln_name=$1;
        local tmp="${ln_name//[^:]}"
        if [[ ${#tmp} = 3 ]]; then
              echo 1
        else
              echo 0
	fi
}
#
# get_link
# return the directory path that a link points to.
# The only parameter is the link name.
#
get_link()
{
    local ln_name=$1;
    echo `ls -l $ln_name 2>/dev/null | awk -F"->" '{print $2}'`
}

#
# get_hbtl
# Given a path that ends in an HBTL (Host:Bus:Target ID:LUN), break it apart
# into its constituent parts in the global vars HOST, BUS, TARGET and LUN
#
# #1 path ending in HBTL
#
get_hbtl()
{
    local hbtl

    HBTL=${1##*/}
    hbtl=$HBTL

    HOST=${hbtl%%:*}
    hbtl=${hbtl#*:}
    BUS=`printf '%x' "${hbtl%%:*}"`
    hbtl=${hbtl#*:}
    ID=`printf '%x' "${hbtl%%:*}"`
    LUN=`printf '%x' "${hbtl#*:}"`
}

#
# get_scsi_disk_no
# Given a path that ends in an HBTL, convert the HBTL values into a
# virtual disk number (not sure what the real terminology is for it).
# To do the conversion, the HBTL (A:B:C:D) is split apart and
# calculated as;
#	no = (0x1000000 | C << 16 | D)
#
# $1 path ending in HBTL
get_scsi_disk_no()
{
    get_hbtl $1

    local C D

    C=$((0x$ID << 16))
    D=$((0x$LUN))

    local vdiskno vdisk
    typeset -i vdiskno
    vdiskno=$((0x1000000 | $C | $D ))
    vdisk=`printf '%X' "${vdiskno#-}"`

    local extrazeroes="00000000"
    echo $vdisk$extrazeroes
}

#
# get_vdisk_no
# Given a path that ends in an HBTL, convert the HBTL values into a
# virtual disk number (not sure what the real terminology is for it).
# To do the conversion, the HBTL (A:B:C:D) is split apart and
# calculated as;
#     no = (0x8000 | B << 8 | C << 5 | D) * 1000000000000
#
# $1 path ending in HBTL
#
get_vdisk_no()
{
    get_hbtl $1

    local B C D
    typeset -i B C D

    B=$((0x$ID << 8))
    C=$((0x$BUS << 5))
    D=$((0x$LUN))

    local vdiskno vdisk
    typeset -i vdiskno
    vdiskno=$((0x8000 | $B | $C | $D ))
    vdisk=`printf '%X' "${vdiskno#-}"`

    local extrazeroes="000000000000"
    echo $vdisk$extrazeroes
}

#
# get_usb_vdisk_no
# Given a path that ends in an HBTL, convert the HBTL values into a
# virtual disk number (not sure what the real terminology is for it).
# To do the conversion, the HBTL (A:B:C:D) is split apart and
# calculated as;
#     no = (0x1000000 | (usb_port << 16) | D);
#
# $1 path ending in HBTL
#
get_usb_vdisk_no()
{
        get_hbtl $1
        local usb_port=$2
        local B

        B=$((0x$usb_port << 16))

        local vdiskno vdisk
        vdiskno=$((0x1000000 | $B | $LUN ))
        vdisk=`printf '%X' "${vdiskno#-}"`

        local extrazeroes="00000000"
        echo $vdisk$extrazeroes
}

#
# get_usb_storage_no
# Get usb device storage (port) number which is captured in
# devpath file
#
# $1 starting directory to look for devpath file
get_usb_storage_no()
{
    for dir in `$FIND /sys -name $1`; do
        # Move up until we find one with a devpath link
        goto_dir $dir "devpath" 0
        if [ $? -eq 0 ]; then
            break;
        fi
    done;

    if [ -f $PWD/devpath ]; then
	echo `$CAT $PWD/devpath`
    else
        err $ERR_NOT_CONFIG
    fi

}

#
# goto_dir
# This looks for a given file in a given directory or any parents of the
# given directory.
#
# $1 starting directory
# $2 file to search for
# $3 on_exit behavior on error
goto_dir()
{
    local start_dir=$(readlink -f $1)
    local fname=$2
    local found=0
    local on_exit=1

    if [[ $# -eq 3 ]]; then
	on_exit=$3
    fi

    cd $start_dir
    while [[ $PWD != "/" ]]; do
	ls $fname >/dev/null 2>&1
        if [[ $? -eq 0 ]]; then
            found=1
	    break
        fi
        cd ..
    done

    if [[ $found -eq 0 ]]; then
	if [[ $on_exit -eq 1 ]]; then
	    err $ERR_NO_SYSFS_DEVINFO
	else
	    return 1
	fi
    fi
}

#
# find_dir
# This looks for a given file in a given directory or any parents of the
# given directory.  This differs from goto_dir in that we don't actually try
# to cd to the directories, we just return the directory name if found.
#
# $1 starting directory
# $2 file to search for
# $3 on_exit behavior on error
find_dir()
{
    local dir=$1
    local fname=$2

    while [[ -n $dir ]]; do
	/bin/ls $dir/$fname >/dev/null 2>&1
	if [[ $? -eq 0 ]]; then
	    echo $dir
	    return
	fi

	dir=${dir%/*}
    done
}

#
# is_pata_dev
# Check to see if this is a PATA device
#
is_pata_dev()
{
    local this_dir=$PWD
    local sysfs_dir
    local udev_path
    local udevinfo="/usr/bin/udevinfo"
    local udevadm="/usr/bin/udevadm"

    if [[ -a $udevadm ]]; then
        udev_path=`$udevadm info --query=path --name=$DEVNAME`
    elif [[ -a $udevinfo ]]; then
        udev_path=`$udevinfo -q path -n $DEVNAME`
    else
        echo "no"
        return
    fi

    if [[ -z $udev_path ]]; then
        echo "no"
    else
	sysfs_dir=`get_link -f /sys/$udev_path/device`
	if [[ ! -d $sysfs_dir ]]; then
	    echo "no"
	else
	    goto_dir $sysfs_dir devspec
	    DEVTYPE=$(cat /proc/device-tree/$(cat $PWD/devspec)/device_type)

	    if [[ $DEVTYPE = "ata" ]]; then
	        echo "yes"
	    else
	        echo "no"
	    fi
	fi
    fi

    cd $this_dir
}

#
# print_aliases
# Print the aliases from /proc/device-tree/aliases for the specified device
#
print_aliases()
{
    dev=$1
    local found=0

    shopt -s nullglob
    for i in /proc/device-tree/aliases/*; do
        if sed -e "s/\x00$//g" $i | grep -qx "$dev" ; then
	    echo ${i##*/}
	    found=1
	fi
    done

    if [[ $found = "0" ]]; then
        echo "No aliases found."
	exit 1
    else
	exit 0
    fi
}

get_slave()
{
	cd /sys/class/*/$1

	while [[ -n "`ls slaves 2> /dev/null`" ]]; do
		cd `echo slaves/* | head -n1 | cut -d " " -f1`;
	done

	$FIND /dev -name "`basename $PWD`"
}

#
# is_net_interface
# Check to see if this is a network interface
#
is_net_interface()
{
    local res

    res=`$FIND /sys/class/net -name $1`
    if [[ ${#res} = 0 ]]; then
	echo "no"
    else
	echo "yes"
    fi
}

#
# logical_to_ofpathname
# Conversion for logical device name to an Open Firmware device path
#
logical_to_ofpathname()
{
    local is_cdrom

    # follow any links to the real device name
    while [[ -L $DEVNAME ]]; do
        DEVNAME=`get_link $DEVNAME`
    done

    while [[ -L /dev/$DEVNAME ]]; do
        DEVNAME=`get_link /dev/$DEVNAME`
    done

    DEVICE=${DEVNAME##*/}
    DEVNODE=${DEVICE%%[0-9]*}

    # try to determine if this is a cdrom device
    if [[ ${DEVNAME_ARG##*/} = cdrom ]]; then
        is_cdrom=yes
    elif [[ `get_link /dev/cdrom` = /dev/$DEVICE ]]; then
        is_cdrom=yes
    else
        is_cdrom=no
    fi

    case $DEVICE in
        eth*)       l2of_ethernet ;;
        hf*)        l2of_hfi ;;
        sd* | sr*)  # PATA devices appear as sd*, but should be converted
		    # using the ide logic
		    is_pata=$(is_pata_dev $DEVNAME)
		    if [[ $is_pata = "yes" ]]; then
		        l2of_ide
		    else
			l2of_scsi
		    fi ;;
        hd*)        l2of_ide ;;
	vd*)	    l2of_vd ;;
        fd*)        echo "no fd support yet" ;;
        mpath*)
                    DEVNAME=$(printf "dm-%d" `stat -c "%T" /dev/mapper/$DEVICE`)
                    logical_to_ofpathname
                    ;;
        dm-*)
                    DEVNAME=`get_slave $DEVICE`
                    logical_to_ofpathname
                    exit
                    ;;
	nvme*)	    l2of_nvme ;;
	*)         # check if the device is a network interface
                   is_net=$(is_net_interface $DEVICE)
                   if [[ $is_net = "yes" ]]; then
                      l2of_ethernet
                   fi ;;
    esac

    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi

    if [[ $is_cdrom = yes ]]; then
        OF_PATH=$OFPATH:1\\ppc\\bootinfo.txt
    fi

    if [[ $do_alias = "1" ]]; then
        print_aliases $OF_PATH
    else
        echo $OF_PATH
    fi
}

#
# l2of_ide
# Conversion routine for logical => OF path of ide devices
#
l2of_ide()
{
    cd /sys/block/$DEVICE
    local link=`get_link "device"`
    if [[ -z $link ]]; then
        err $ERR_NO_SYSFS_DEVINFO
    fi

    # partition number: N in sd*N
    local devpart="${DEVICE##*[a-z]}"
    if [[ $devpart = $DEVICE ]]; then
        devpart='' # no partition number
    fi

    cd $link

    # get the device number
    local devdir=${PWD##/*/}
    local channelno=${devdir%%\.*}
    local devno=${devdir##*\.}

    goto_dir $PWD "devspec"

    OF_PATH=`$CAT $PWD/devspec`
    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi

    # PCI ATA controller nodes (found on some Macs) have one child per IDE
    # channel.
    case `$CAT "/proc/device-tree/$OF_PATH/device_type"` in
        pci-ata | pci-ide)  OF_PATH=$OF_PATH/@$channelno ;;
    esac

    # On Efika, obtained devno "0:0:0:0" doesn't match actual device.
    # Note: according to vendor, "0,0" means primary master.  Secondary
    # channel is not present, and primary slave is rare enough that we
    # can reasonably ignore it.
    if [ "$PLATFORM" = "efika" ] ; then
        devno=0,0
    fi

    if [ "$PLATFORM" = "mac" ] ; then
        OF_PATH=$OF_PATH/@$devno
    else
        OF_PATH=$OF_PATH/disk@$devno
    fi
}

#
# l2of_vd
# Conversion routine for logical => OF path of virtio block devices
#
l2of_vd()
{
    local found=0

    # There may be many instances of DEVICE under /sys
    for dir in `$FIND /sys -name $DEVICE`; do
	# Move up until we find one with a device link
	goto_dir $dir "device" 0
	if [ $? -eq 0 ]; then
	    found=1;
	    break;
	fi
    done;

    if [ $found -eq 0 ]; then
        err $ERR_NOT_CONFIG
    fi

    local link=`get_link "device"`
    if [[ -z $link ]]; then
        err $ERR_NO_SYSFS_DEVINFO
    fi
    cd $link

    goto_dir $PWD "devspec"

    OF_PATH=`$CAT $PWD/devspec`
    SYS_PATH=$PWD
    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi

    # No partition specified.
    if [[ -z $devpart ]]; then
        return
    fi

    OF_PATH="${OF_PATH}:${devpart}"
}

#
# l2of_ethernet
# Conversion routine for logical => OF path of ethernet devices
#
l2of_ethernet()
{
    for syspath in `$FIND /sys -name $DEVICE 2> /dev/null`; do
        if [[ -e $syspath/device/devspec ]]; then
            OF_PATH=`$CAT $syspath/device/devspec`
            break
        fi
    done

    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi
}

#
# l2of_hfi
# Conversion routine for logical => OF path of HFI devices
#
l2of_hfi()
{
    local hfnum=${DEVICE##hf}
    local hfpath

    if [[ $hfnum = "0" || $hfnum = "2" ]]; then
        hfpath=`$FIND /proc/device-tree -name hfi-ethernet* | sort | head -n 1`
    elif [[ $hfnum = "1" || $hfnum = "3" ]]; then
        hfpath=`$FIND /proc/device-tree -name hfi-ethernet* | sort | tail -n 1`
    else
        err $ERR_NO_OFPATH
    fi

    OF_PATH=${hfpath##/proc/device-tree}
}

#
# l2of_nvme
# Conversion routine for logical => OF path of nvme devices
#
l2of_nvme()
{
    # OF path: <devspec>/namespace@<namespace-id>:<partition-number>

    # disk: nvmeX, nvmeXnY; not nvmeXnYpZ
    local devdisk="${DEVICE%p[0-9]*}"

    # namespace id: Y in nvmeXnY, nvmeXnYpZ
    local devnsid="${devdisk#nvme[0-9]*n}"
    if [[ $devnsid = $devdisk ]]; then
        devnsid='' # no namespace id
    fi

    # partition number: Z in nvmeXnYpZ
    local devpart="${DEVICE##*p}"
    if [[ $devpart = $DEVICE ]]; then
        devpart='' # no partition number
    fi

    # Get the device-tree device specification (devspec).
    local dir
    local found=0

    for dir in `$FIND /sys/devices -name "$DEVICE"`; do
        cd $dir

        goto_dir $PWD "device/devspec"

        devspec=`$CAT $PWD/device/devspec | tr -d '\000'`
        if [[ -n $devspec ]]; then
            found=1
            break
        fi
    done

    if [[ $found -eq 0 ]]; then
        err $ERR_NO_SYSFS_DEVINFO
    fi

    if [[ -z $devspec ]]; then
        err $ERR_NO_OFPATH
    fi

    # OF path: <devspec>/namespace@<namespace-id>:<partition-number>
    OF_PATH="$devspec"

    # No namespace id (nY) specified.
    if [[ -z $devnsid ]]; then
        return
    fi

    # Device type is usually 'namespace'.
    # Get it from device-tree just in case.
    devtype=`$CAT /proc/device-tree${devspec}/namespace/name | tr -d '\000'` 2> /dev/null
    if [[ -z $devtype ]]; then
        err $ERR_NO_OFPATH
    fi

    OF_PATH="$OF_PATH/$devtype@$devnsid"

    # No partition (pZ) specified.
    if [[ -z $devpart ]]; then
        return
    fi

    OF_PATH="${OF_PATH}:${devpart}"
}

#
# int_to_scsilun
# Conversion routine for SCSI HBTL LUN => SCSI LUN name
#
int_to_scsilun()
{
    local lunint=$1
    local A B C D

    A=$(( ($lunint >> 8) & 0xff ))
    B=$(($lunint & 0xff))
    C=$(( ($lunint >> 24) & 0xff ))
    D=$(( ($lunint >> 16) & 0xff ))

    local lunstr=$(printf "%02x%02x%02x%02x00000000" $A $B $C $D)
    lunstr=`echo $lunstr | sed 's/^[0]*//'`
    echo "$lunstr"
}

#
# scsilun_to_int
# Conversion routine for SCSI LUN name => SCSI HBTL LUN
#
scsilun_to_int()
{
    local lunstr=$1
    local A B C D L

    L=$(( 0x${lunstr/00000000} ))

    A=$(( ($L >> 8) & 0xff ))
    B=$(($L & 0xff))
    C=$(( ($L >> 24) & 0xff ))
    D=$(( ($L >> 16) & 0xff ))

    L=$(( (($A << 24) | ($B << 16) | ($C << 8) | $D) ))
    echo "$L"
}

get_fc_scsilun()
{
    local lun=$1
    local L=$(( 0x$lun ))

    local fc_lun=`int_to_scsilun $L`
    echo "$fc_lun"
}

get_fc_wwpn()
{
    local start_dir=$1

    for f in `$FIND -H $start_dir -maxdepth 2 -name port_name`; do
        local wwpn=`$CAT $f`
	break
    done

    # strip the leading 0x
    wwpn=${wwpn:2}

    echo "$wwpn"
}

#
# l2of_scsi
# Converion routine for logical => OF path of scsi devices
#
l2of_scsi()
{
    local found=0
    local devtype

    # There may be many instances of DEVICE under /sys
    for dir in `$FIND /sys/class/block -name $DEVICE`; do
	# Move up until we find one with a device link
	goto_dir $dir "device" 0
	if [ $? -eq 0 ]; then
	    found=1;
	    break;
	fi
    done;

    if [ $found -eq 0 ]; then
        err $ERR_NOT_CONFIG
    fi

    # partition number: N in sd*N
    local devpart="${DEVICE##*[a-z]}"
    if [[ $devpart = $DEVICE ]]; then
        devpart='' # no partition number
    fi

    # follow the 'device' link
    local link=`get_link "device"`
    if [[ -z $link ]]; then
	# device may not be a link
	link=device
    fi

    get_hbtl $link
    cd $link

    # save the name of the current directory, we may need it later...
    local device_dir=${PWD##/*/}
    local device_path=$PWD

    # move up directories until we find one with devspec information
    goto_dir $PWD "devspec"

    if [[ -e $PWD/devspec ]]; then
        OF_PATH=`$CAT $PWD/devspec`
    else
        err $ERR_NO_OFPATH
    fi

    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    elif [[ ! -e /proc/device-tree/$OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi

    local vdev=${OF_PATH%/*}
    local fc=${OF_PATH%@*}
    fc=${fc##/*/}

    if [[ -e /proc/device-tree$OF_PATH/device_type ]]; then
        devtype=`tr -d '\0' < /proc/device-tree$OF_PATH/device_type`;
        if [[ $devtype = "fcp" || $devtype = "scsi-fcp" ]]; then
                fc="fibre-channel";
        fi
    fi

    if [[ $fc = "usb" ]]; then
	 local hub_no storage_no disk_no

        storage_no=`get_usb_storage_no $DEVICE`
        if [[ $storage_no = *.* ]]; then
                hub_no=${storage_no%%.*}
                storage_no=${storage_no##*.}
        fi

        disk_no=`get_usb_vdisk_no $device_dir $storage_no`

        if [[ -z $hub_no ]]; then
                OF_PATH=$OF_PATH/storage\@$storage_no/disk\@$disk_no
        else
                OF_PATH=$OF_PATH/hub\@$hub_no/storage\@$storage_no/disk\@$disk_no
        fi

   elif [[ $fc = "fibre-channel" ]]; then
	local wwpn=`get_fc_wwpn "$device_path/../../fc_remote_ports*"`

        if [[ ! -e /proc/device-tree$OF_PATH/disk ]]; then
                for dir in `$FIND /proc/device-tree$OF_PATH -type d`; do
                        if [[ -e $dir/disk ]]; then
                                OF_PATH=${dir##/proc/device-tree}
                                break;
                        fi
                done
        fi

        OF_PATH=$(printf "%s/disk@%s" $OF_PATH $wwpn)

        if [[ $LUN != "0" ]]; then
                local fc_lun=`get_fc_scsilun $LUN`
                OF_PATH=$(printf "%s,%s" $OF_PATH $fc_lun)
        fi
    elif [[ $vdev = "/vdevice" ]]; then
        # get the v-device data
        local tmp=${OF_PATH//\/vdevice/}
        local vdevtype=${tmp%@*}

        if [[ $vdevtype = "/vfc-client" ]]; then
                local vfc_lun=`get_fc_scsilun $LUN`
		local wwpn=`get_fc_wwpn "$device_path/../../fc_remote_ports*"`
                OF_PATH=$(printf "%s/disk@%s,%s" $OF_PATH $wwpn $vfc_lun)
        else
                local i vdiskno
		goto_dir $device_path $device_dir
                vdiskno=`get_vdisk_no $device_dir`

                OF_PATH=$OF_PATH/disk\@$vdiskno
        fi
    elif [[ -d /proc/device-tree$OF_PATH/sas ]]; then
        local vendor_id sas_id

	vendor_id=`od -tx /proc/device-tree/$OF_PATH/vendor-id`
	vendor_id=`echo $vendor_id | cut -d " " -f 2`
	if [[ $vendor_id = "00001000" ]]; then
	    local dev_id
	    goto_dir $device_path "sas_end_device*"
	    dev_id=${PWD##*-}
	    sas_id=`cat /sys/class/sas_device/end_device-$dev_id/sas_address`
	    sas_id=${sas_id##0x}

	    OF_PATH=$(printf "%s/sas/disk@%s" $OF_PATH $sas_id)
	    if [[ $LUN != "0" ]]; then
                local LUNSTR=`int_to_scsilun $LUN`
                OF_PATH=$(printf "%s,%s" $OF_PATH $LUNSTR)
	    fi
	else
            local B T L
            local fwtype="0"

            if [[ -e /sys/class/scsi_host/host$HOST/fw_type ]]; then
                fwtype=`$CAT /sys/class/scsi_host/host$HOST/fw_type`
            fi

            if [[ $fwtype = "1" ]]; then
                goto_dir $device_path "device_id"
                sas_id=`$CAT $PWD/device_id`
                sas_id=${sas_id##0x}

	        OF_PATH=$(printf "%s/sas/disk@%s" $OF_PATH $sas_id)
                if [[ $LUN != "0" ]]; then
                    local LUNSTR=`int_to_scsilun $LUN`
                    OF_PATH=$(printf "%s,%s" $OF_PATH $LUNSTR)
                fi
            else
                B=$(( 0x$BUS ))
                T=$(( 0x$ID ))
                L=$(( 0x$LUN ))

                sas_id=$(( ($B << 16) | ($T << 8) | $L ))

	        OF_PATH=$(printf "%s/sas/disk@%x" $OF_PATH $sas_id)
                if [[ $LUN != "0" ]]; then
                    OF_PATH=$(printf "%s,%x" $OF_PATH $LUN)
                fi
            fi
	fi
    else

        plug_id=$(ls -dv $SYS_PATH/*/host* 2>/dev/null | grep -n "/host$HOST$")
        [ -z "$plug_id" ] && {
                plug_id=$(ls -dv $SYS_PATH/host* 2>/dev/null | grep -n "/host$HOST$")
        }
        plug_id=$((${plug_id%%:*}-1))

        # make sure the "scsi" information is on the end of the path
        local scsi_name=${OF_PATH##/*/}
        scsi_name=${scsi_name%%@*}
        if [[ $scsi_name != "scsi" && "$PLATFORM" != "mac" ]]; then
            scsi_name="scsi@$BUS"
            OF_PATH=$OF_PATH/$scsi_name
        elif [[ $scsi_name != "scsi" && "$PLATFORM" = "mac" && $devtype != "ata" ]]; then
            scsi_name="@$plug_id"
            OF_PATH=$OF_PATH/$scsi_name
        fi

        local modalias=""
        goto_dir $device_path "device"
        if [ $? -eq 0 ]; then
              modalias=`$CAT $PWD/modalias`
        fi

        if [[ $modalias =~ "virtio" ]]; then
             local diskno
             diskno=`get_scsi_disk_no $device_dir`
             OF_PATH=$OF_PATH/disk\@$diskno
        else
             if [ "$PLATFORM" = "mac" ] ; then
                 OF_PATH=$OF_PATH/@$ID
             else
                 OF_PATH=$OF_PATH/sd@$ID,$LUN
             fi
       fi
    fi

    # No partition specified.
    if [[ -z $devpart ]]; then
        return
    fi

    OF_PATH="${OF_PATH}:${devpart}"
}

#
# ofpathname_to_logical
# Conversion for Open Firmware device paths to logical device names
#
ofpathname_to_logical()
{
    DEVPATH=${DEVNAME%/*}
    DEVICE=${DEVNAME##/*/}
    DEVTYPE=${DEVICE%\@*}
    SAS=${DEVPATH##/*/}
    FC=${SAS%%\@*}

    if [[ $do_alias = "1" ]]; then
        print_aliases $OF_PATH
    fi

    if [[ $DEVTYPE = "disk" && $DEVICE != ${DEVICE%:*} ]]; then
        DEVTYPE=${DEVICE%:*}
    fi

    if [[ $DEVTYPE = "disk" && $FC = "v-scsi" ]]; then
        DEVTYPE="v-scsi"
    fi

    if [[ $DEVTYPE = "disk" && $FC = "scsi" ]]; then
        DEVTYPE="scsi"
    fi

    if [[ $DEVTYPE = "disk" && $SAS = "sas" ]]; then
        DEVTYPE="sas"
    fi

    if [[ $DEVTYPE = "disk" && $FC = "vfc-client" ]]; then
        DEVTYPE="vfc"
    fi

    if [[ $DEVTYPE = "disk" && $FC = "fibre-channel" ]]; then
        DEVTYPE="fc"
    fi

    if [[ $DEVTYPE = "disk" && $FC = "QLGC,qlc" ]]; then
        DEVTYPE="fc"
    fi

    if [[ $DEVTYPE = "disk" && $FC = "fp" ]]; then
        DEVTYPE="fc"
    fi

    if [[ $DEVTYPE = "disk" && $FC = "storage" ]]; then
        local devpath=$DEVPATH
        DEVPATH=${devpath%/*}
        DEVTYPE="usb"

        if [[ $DEVNAME = *hub* ]]; then
            devpath=$DEVPATH
            DEVPATH=${devpath%/*}
        fi
    fi

    if [[ $DEVTYPE = "namespace" ]]; then
        DEVTYPE="nvme"
    fi

    # Remove any possible cdrom data from DEVICE
    if [[ ${DEVICE##*,} = "\ppc\bootinfo.txt" ||
          ${DEVICE##*,} = \ppc\bootinfo.txt ]]; then
        DEVICE=${DEVICE%,*}
    fi

    # Remove any possible yaboot suffix from DEVICE
    if [[ ${DEVICE##*,} = "yaboot" ]]; then
	DEVICE=${DEVICE%,*}
    fi

    case $DEVTYPE in
        sd* | scsi*   )  of2l_scsi ;;
        sas           )  of2l_sas ;;
        vfc           )  of2l_vfc ;;
        fc            )  of2l_fc ;;
        v-scsi | disk )  of2l_vscsi
                         if [[ -z $LOGICAL_DEVNAME && $DEVTYPE = disk ]]; then
                             of2l_ide
                          fi ;;
        eth* | l-lan  )  of2l_ethernet ;;
	vnic          )  of2l_ethernet ;;
        hfi-ethernet* )  of2l_hfi ;;
        disk*         )  of2l_ide ;;
        usb           )  of2l_usb ;;
        nvme          )  of2l_nvme ;;
    esac

    if [[ -z $LOGICAL_DEVNAME ]]; then
        err $ERR_NO_LOGDEV
    fi

    # See if this device is the cdrom
    if [[ `get_link "/dev/cdrom"` = $LOGICAL_DEVNAME ]]; then
        LOGICAL_DEVNAME="cdrom"
    fi

    echo $LOGICAL_DEVNAME
}

#
# of2l_ide
# Conversion routine for OF path => logical name for ide devices
#
of2l_ide()
{
    local dir

    for dir in `$FIND /sys/block -name 'hd*'`; do
        # get devno
        local devno=${DEVICE##*@}
        devno=${devno%%:*}

        cd $dir
        local link=`get_link "device"`
        if [[ -n $link ]]; then
            cd $link

            # see if this is the correct device
            local this_devno=${PWD##*\.}
            if [[ $devno -eq $this_devno ]]; then
                goto_dir $PWD "devspec"
                local devspec=`$CAT ./devspec 2>/dev/null`

                if [[ $devspec = $DEVPATH ]]; then
                    LOGICAL_DEVNAME="${dir##*/}"
                    break
                fi
            fi
        fi
    done
}

#
# of2l_ethernet
# Conversion routine for OF path => logical names of ethernet devices
#
of2l_ethernet()
{
    local dir

    # strip off ip info if present
    local devname=${DEVNAME%%:*}
    local netdir="/sys/class/net"

    for dir in `ls $netdir`; do
	local devdir=`find_dir $netdir/$dir device`
	if [[ -z $devdir ]]; then
	    continue
	fi

	cd $devdir

        local link=`get_link "device"`
        if [[ -z $link ]]; then
            err $ERR_NO_SYSFS_DEVINFO
        fi

        cd $link
        local devspec=`$CAT ./devspec 2>/dev/null`

        if [[ $devspec = $devname ]]; then
            LOGICAL_DEVNAME="${dir##*/}"
            return
        fi
    done
}

#
# of2l_hfi
# Conversion routine for OF path => logical names of HFI devices
#
of2l_hfi()
{
    # strip off ip info if present
    local devname=${DEVNAME%%:*}
    local hfpath

    hfpath=`$FIND /proc/device-tree -name hfi-ethernet* | sort | head -n 1`
    hfpath=${hfpath##/proc/device-tree}

    if [[ $hfpath = $devname ]] ; then
        LOGICAL_DEVNAME="hf0"
    else
        hfpath=`$FIND /proc/device-tree -name hfi-ethernet* | sort | tail -n 1`
        hfpath=${hfpath##/proc/device-tree}

        if [[ $hfpath = $devname ]] ; then
            LOGICAL_DEVNAME="hf1"
        else
            err $ERR_NO_LOGDEV
        fi
    fi
}

#
# of2l_usb
# Conversion routine for OF path => logical names of usb devices
#
of2l_usb()
{
    DEV_HBTL_NO=${DEVICE##*\@}

    local dir
    for dir in `$FIND /sys/block -name 's[dr]*'`; do
        # go up to find directory with 'device' link
        local devdir=`find_dir $dir device`
        if [[ -z $devdir ]]; then
            continue
        fi

        cd $devdir

        local link=`get_link "device"`
        local vdisk_no

        if [[ -n $link ]]; then
           local port_no storage_no target

           target=${link##*/}
           port_no=`get_usb_storage_no $target`
           storage_no=${port_no##*.}

           vdisk_no=`get_usb_vdisk_no $target $storage_no`

           cd $link
           if [[ $vdisk_no = $DEV_HBTL_NO ]]; then
                goto_dir $PWD "devspec"
                local devspec=`$CAT ./devspec 2>/dev/null`

                if [[ $devspec = $DEVPATH ]]; then
                    LOGICAL_DEVNAME=${dir##/*/}
                    return
                fi
            fi
        fi
    done
}

#
# of2l_vscsi
# Conversion routine for OF path => logical names of virtual scsi devices
#
of2l_vscsi()
{
    DEV_HBTL_NO=${DEVICE##*\@}

    local dir
    for dir in `$FIND /sys/block -name 's[dr]*'`; do
        # go up to find directory with 'device' link
	local devdir=`find_dir $dir device`
	if [[ -z $devdir ]]; then
	    continue
	fi

	cd $devdir

        local link=`get_link "device"`
        if [[ -n $link ]]; then
            local vdiskno=`get_vdisk_no $link`

            cd $link
            if [[ $vdiskno = $DEV_HBTL_NO ]]; then
                goto_dir $PWD "devspec"
                local devspec=`$CAT ./devspec 2>/dev/null`

                if [[ $devspec = $DEVPATH ]]; then
                    LOGICAL_DEVNAME=${dir##/*/}
                    return
                fi
            fi
	fi
    done
}

#
# of2l_scsi
# Conversion routine for OF path => logical names of scsi devices
#
of2l_scsi()
{
    DEV_HBTL_NO=${DEVICE##*\@}
    DEV_TARGET=${DEVICE##*\@}
    DEV_TARGET=${DEV_TARGET%%,*}
    DEV_LUN=${DEVICE##*,}

    # At this point DEV_LUN may be in the form X:Y, we're only interested
    # in the X component.
    DEV_LUN=${DEV_LUN%%:*}

    local dir
    for dir in `$FIND /sys/block -name '[sv][dr]*'`; do
        # go up to find directory with 'device' link
	local devdir=`find_dir $dir device`
	if [[ -z $devdir ]]; then
	    continue
	fi

	cd $devdir

        local link=`get_link "device"`
        if [[ -z $link ]]; then
            err $ERR_NO_SYSFS_DEVINFO
        fi

        local hbtl=`is_hbtl $link`
        local diskno
        # Do not call get_hbtl for virtio block devices
        if [[ $hbtl = 1 ]]; then
               get_hbtl $link
               diskno=`get_scsi_disk_no $link`
	fi
        cd $link

        # save the name of the current directory, we may need it later...
        local device_dir=${PWD##/*/}

        if [[ $hbtl = 0 || $diskno = $DEV_HBTL_NO ||
		($ID = $DEV_TARGET && $LUN = $DEV_LUN) ]]; then
            goto_dir $PWD "devspec"
            local devspec=`$CAT ./devspec 2>/dev/null`

            # Handle virtio block devices
            if [[ $hbtl = 0 && $devspec = $DEVNAME ]]; then
                LOGICAL_DEVNAME="${dir##*/}"
                return
            fi

            if [[ $devspec = $DEVPATH ]]; then
                LOGICAL_DEVNAME="${dir##*/}"
                return
            fi

            local scsi_name=${devspec##/*/}
            scsi_name=${scsi_name%%@*}

            if [[ $scsi_name != "scsi" ]]; then
                scsi_name="scsi@$BUS"
                devspec=$devspec/$scsi_name

                if [[ $devspec = $DEVPATH ]]; then
                    LOGICAL_DEVNAME="${dir##*/}"
                    return
                fi
            fi
        fi
    done
}

#
# of2l_sas
# Conversion routine for OF path => logical names of sas devices
#
of2l_sas()
{
    local matchtype dir

    DEV_NAME=${DEVICE##*\@}
    DEV_ID=${DEV_NAME%%,*}

    LUN=${DEV_NAME/$DEV_ID}
    LUN=${LUN/,/}

    if [[ ${#LUN} = 0 ]]; then
        LUN="0"
    fi

    lunint=`scsilun_to_int $LUN`

    PCI_ID=${DEVPATH%%/sas*}
    vendor_id=`od -tx /proc/device-tree/$PCI_ID/vendor-id`
    vendor_id=`echo $vendor_id | cut -d " " -f 2`

    if [[ $vendor_id = "00001000" ]]; then
        matchtype="libsas"
    else
        matchtype="ipr32"
    fi

    for dir in `$FIND /sys/class/scsi_host -maxdepth 1 -name 'host*'`; do
        cd $dir
        local link=`get_link "device"`

        cd $link
        cd ..

        local devspec=`$CAT ./devspec 2>/dev/null`
        if [[ $devspec = $PCI_ID ]]; then
                # check for ipr64
                if [[ -e $dir/fw_type ]]; then
                        local fwtype=`$CAT $dir/fw_type`
                        if [[ $fwtype = "1" ]]; then
                                matchtype="ipr64"
                                break
                        fi
                fi

                break
        fi
    done

    for dir in `$FIND /sys/block -name 's[dr]*'`; do
        # go up to find directory with 'device' link
	local devdir=`find_dir $dir device`
	if [[ -z $devdir ]]; then
	    continue
	fi

	cd $devdir

        local link=`get_link "device"`
        if [[ -z $link ]]; then
            err $ERR_NO_SYSFS_DEVINFO
        fi

        get_hbtl $link
        cd $link

        # save the name of the current directory, we may need it later...
        local device_dir=$PWD

        local B T L
        B=$(( 0x$BUS ))
        T=$(( 0x$ID ))
        L=$(( 0x$LUN ))

        if [[ $matchtype = "ipr32" ]]; then
            local sas_id=$((($B << 16) | ($T << 8) | $L))
            sas_id=`printf '%x' "$sas_id"`

            if [[ $sas_id = $DEV_ID ]]; then
                goto_dir $PWD "devspec"
                local devspec=`$CAT ./devspec 2>/dev/null`

                if [[ $devspec/sas = $DEVPATH ]]; then
                    LOGICAL_DEVNAME="${dir##*/}"
                    return
                fi
            fi
        elif [[ $matchtype = "ipr64" ]]; then
            if [[ -e $devdir/device/device_id ]]; then
                local deviceid=`$CAT $devdir/device/device_id`
                deviceid=${deviceid##0x}

                if [[ $deviceid != $DEV_ID ]]; then
                    continue
                fi

                if [[ $L = $lunint ]]; then
                    goto_dir $PWD "devspec"
                    local devspec=`$CAT ./devspec 2>/dev/null`

                    if [[ $devspec/sas = $DEVPATH ]]; then
                        LOGICAL_DEVNAME="${dir##*/}"
                        return
                    fi
                fi
            fi
        elif [[ $matchtype = "libsas" ]]; then
            local dev_id
	    goto_dir $device_dir "sas_end_device*" 0
	    dev_id=${PWD##*-}

            if [[ ! -e /sys/class/sas_device/end_device-$dev_id/sas_address ]]; then
                continue
            fi

	    sas_id=`cat /sys/class/sas_device/end_device-$dev_id/sas_address`
	    sas_id=${sas_id##0x}

            if [[ $sas_id != $DEV_ID ]]; then
                continue
            fi

            if [[ $L = $lunint ]]; then
                goto_dir $PWD "devspec"
                local devspec=`$CAT ./devspec 2>/dev/null`

                if [[ $devspec/sas = $DEVPATH ]]; then
                    LOGICAL_DEVNAME="${dir##*/}"
                    return
                fi
            fi
        else
                err $ERR_NO_LOGDEV
        fi

    done
}

#
# of2l_vfc
# Conversion routine for OF path => logical names of vFC devices
#
of2l_vfc()
{
    DEV_ID=${DEVICE##*\@}
    OF_WWPN=${DEV_ID%%,*}
    OF_LUN=${DEV_ID##$OF_WWPN}
    OF_LUN=${OF_LUN#,}
    OF_LUN=`echo $OF_LUN | sed 's/^[0]*//'`

    local dir
    for dir in `$FIND /sys/block -name 's[dr]*'`; do
        # go up to find directory with 'device' link
	local devdir=`find_dir $dir device`
	if [[ -z $devdir ]]; then
	    continue
	fi

	cd $devdir

        local link=`get_link "device"`
        if [[ -z $link ]]; then
            continue
        fi

        get_hbtl $link
        cd $link
        link=$PWD

        local device_dir=${PWD##/*/}
        goto_dir $PWD "devspec"
        OF_PATH=`$CAT $PWD/devspec`
        if [[ -z $OF_PATH ]]; then
                err $ERR_NO_LOGDEV
        fi

        local vdev=${OF_PATH%/*}
        local tmp=${OF_PATH//\/vdevice/}
        local vdevtype=${tmp%@*}

        if [[ $vdevtype != "/vfc-client" ]]; then
                continue
        fi

        local vfc_lun=`get_fc_scsilun $LUN`
	local wwpn=`get_fc_wwpn "$link/../../fc_remote_ports*"`

        if [[ $vfc_lun = $OF_LUN && $wwpn = $OF_WWPN ]]; then
            if [[ $OF_PATH = $DEVPATH ]]; then
            LOGICAL_DEVNAME="${dir##*/}"
            return
            fi
        fi
    done
}

#
# of2l_fc
# Conversion routine for OF path => logical names of FC devices
#
of2l_fc()
{
    DEV_ID=${DEVICE##*\@}
    OF_WWPN=${DEV_ID%%,*}
    OF_LUN=${DEV_ID/$OF_WWPN}
    OF_LUN=${OF_LUN/,/}

    if [[ ${#OF_LUN} = 0 ]]; then
        OF_LUN="0"
    fi

    lunint=`scsilun_to_int $OF_LUN`

    local dir
    for dir in `$FIND /sys/block -name 's[dr]*'`; do
        # go up to find directory with 'device' link
	local devdir=`find_dir $dir device`
	if [[ -z $devdir ]]; then
	    continue
	fi

	cd $devdir

        local link=`get_link "device"`
        if [[ -z $link ]]; then
            continue
        fi

        get_hbtl $link
        cd $link
        link=$PWD

	# find device_path, not all dirs will have a fc_remote_ports
	goto_dir $PWD "fc_remote_ports*" 0
	if [[ $? -eq 1 ]]; then
	    continue
	fi
	device_path=$PWD

	cd $link
        local device_dir=${PWD##/*/}
        goto_dir $PWD "devspec"
        OF_PATH=`$CAT devspec`
        if [[ -z $OF_PATH ]]; then
                err $ERR_NO_LOGDEV
        fi

	local wwpn=`get_fc_wwpn "$device_path/fc_remote_ports*"`

        if [[ $wwpn = $OF_WWPN ]]; then
                local L=$(( 0x$LUN ))

                if [[ $L = $lunint ]]; then
                        LOGICAL_DEVNAME="${dir##*/}"
                        return
                fi
        fi
    done
}

#
# of2l_nvme
# Conversion routine for OF path => logical name for nvme devices
#
of2l_nvme()
{
    # get namespace id and partition number
    local nsid_part=${DEVICE##*@} # <namespace-id>[:partition-number]
    local nsid=${nsid_part%:*} # namespace id
    local part # partition number

    # set partition number only if ':' is present
    case "${nsid_part}" in
    *:*)
        part=${nsid_part#*:}
        ;;
    esac

    local dir
    local link

    for dir in `$FIND /sys/block -name "nvme*n$nsid"`; do
        cd $dir

        link=`get_link "device"` # points to nvme[0-9]+ (non-namespace)
        if [[ -n $link ]]; then
            cd $link
        else
            continue
        fi

        link=`get_link "device"` # points to pci address dir
        if [[ -n $link ]]; then
            cd $link
        else
            continue
        fi

        goto_dir $PWD "devspec"
        local devspec=`$CAT ./devspec 2>/dev/null`

        if [[ $devspec = $DEVPATH ]]; then
            LOGICAL_DEVNAME="${dir##*/}"
            break
        fi
    done

    if [[ -n $LOGICAL_DEVNAME ]] \
    && [[ -n $part ]]; then

        if [[ -d "/sys/block/${LOGICAL_DEVNAME}/${LOGICAL_DEVNAME}p${part}" ]]; then
            LOGICAL_DEVNAME="${LOGICAL_DEVNAME}p${part}"
        else
            LOGICAL_DEVNAME=''
        fi
    fi
}

#
# Main
#
. $PSERIES_PLATFORM
if [[ $platform = $PLATFORM_POWERNV ]]; then
	echo "$OFPATHNAME: is not supported on the $platform_name platform" 1>&2
	exit 1
fi

if [[ "$#" -eq 0 ]]; then
    usage
    exit 0
fi

# default: convert logical => OFpath
do_of2l=0

# default: do not do alias lookups
do_alias=0

getopt -o "l:Vqh" -l "help,version,quiet" $@ > /dev/null 2>&1
while [[ -n $1 ]]; do
    case "$1" in
	-a)		do_alias=1 ;;

        -l)             do_of2l=1
                        DEVNAME_ARG=$2
                        shift ;;

	-V | --version) show_version
                        exit 0 ;;

        -q | --quiet)   be_quiet=1 ;;

        -h | --help)    usage
                        exit 0 ;;
	*)              DEVNAME_ARG=$1 ;;
    esac

    shift
done

DEVNAME=$DEVNAME_ARG

# double check device name
if [[ -z $DEVNAME ]]; then
    usage
    exit 1
fi

# We need sysfs
if [[ ! -d "/sys" ]]; then
    err $ERR_NO_SYSFS
    exit 1
fi


if [[ $do_of2l = "0" ]]; then
    # logical devname => OF pathname
    logical_to_ofpathname
else
    # OF pathnmae => logical devname
    ofpathname_to_logical
fi

exit 0
