#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2003,2007 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
#"@(#)26   1.22   src/csm/install/setbootdisk.perl, setup, csm_rfish, rfishs001b 10/30/06 04:14:53"

=item setbootlist
  setbootlist: set the target hard disk in linux to be the first boot
  device in openfirmware
=cut

use Getopt::Std;
use strict;

# Commands
$::LSNODE  = "/opt/csm/bin/lsnode";
$::GREP    = "/bin/grep";
$::AWK     = "/bin/awk";
$::TAIL    = "/usr/bin/tail";
$::WC      = "/usr/bin/wc";
$::MV      = "/bin/mv";
$::SED     = "/bin/sed";
$::CAT     = "/bin/cat";
$::SERVICE = "/sbin/service";
$::LS      = "/bin/ls";
$::CUT     = "/usr/bin/cut";
$::SCSIDEV = "/bin/scsidev";
$::STRINGS = "/usr/bin/strings";
$::HEAD    = "/usr/bin/head";
$::SORT    = "/usr/bin/sort";
$::LSPCI   = "/sbin/lspci";
$::HEXDUMP = "/usr/bin/hexdump";
$::DIRNAME = "/usr/bin/dirname";
$::FIND    = "/usr/bin/find";

# Variables
$::NVRAM          = "/dev/nvram";
$::NVRAMTMP       = "/tmp/tmpnvram";
$::OFPATH         = "";
$::KERNEL_VERSION = "";

sub usage
{
	print "Usage:  setbootdisk [-h] [-v|-V] <scsi hard disk>\n";
	exit 1;
}

sub getArgs
{
	my @nodeList;
	if (!getopts('vVh')) { &usage; }    # Handle flags.
	if ($::opt_h)        { &usage }
	$::VERBOSE = $::opt_v || $::opt_V;
	if (scalar(@ARGV) != 1) { &usage; }

	unless (-b $ARGV[0]) { &usage; }

}

#This function is for linux 2.4
sub check_bus_file
{
	my $busfile = $_[0];
	my $bus     = $_[1];
	my $cmd     =
	  "$::HEXDUMP -C $busfile | $::HEAD -n 1 | $::AWK '{ print \$5 \":\" \$9\":\";}'";
	my $res = `$cmd`;
	my ($a1, $a2) = split(":", $res);
	$a1 = hex("$a1");
	$a2 = hex("$a2");
	if ($bus == $a1)
	{
		return 2;    #get it
	}
	elsif (($bus >= $a1) and ($bus <= $a2))
	{
		return 1;    #in these range
	}
	else
	{
		return 0;
	}
}

#This function is for linux 2.4
sub findOFPathForLinux24
{
	my ($bus, $dev, $fun, $channel, $id, $lun) = &getDevInfo($_[0]);
	my ($cmd, $res);
	print
	  "Setbootdisk: Find OF Path for bus:$bus,dev:$dev,fun:$fun,channel:$channel,id:$id,lun:$lun\n"
	  if $::VERBOSE;
	my $phfilename;

	if (-f "/proc/device-tree/rtas/ibm,read-pci-config")
	{
		if (-f "/proc/device-tree/pci/linux,global-number")
		{
			$phfilename = "linux,global-number";
		}
		elsif (-f "/proc/device-tree/pci/linux,phbnum")
		{
			$phfilename = "linux,phbnum";
		}
		else
		{

			#should not happen?
			#do nothing.
		}
	}

	#find the top lever in device-tree
	print "Setbootdisk: find top lever in device-tree ...\n" if $::VERBOSE;
	my $topdir;
	if (defined $phfilename)
	{
		$cmd = "$::LS /proc/device-tree/pci@*/$phfilename";
		my @resfiles = `$cmd`;
		foreach my $resfile (@resfiles)
		{
			chomp($resfile);
			$cmd =
			  "$::HEXDUMP -C $resfile | $::HEAD -n 1 | $::AWK '{ print \$2 \":\" \$3 \":\"\$4 \":\"\$5\":\";}'";
			$res = `$cmd`;
			my ($a1, $a2, $a3, $a4) = split(":", $res);
			$a1 = hex("$a1");
			$a2 = hex("$a2");
			$a3 = hex("$a3");
			$a4 = hex("$a4");
			my $aa = $a1 * 4096 + $a2 * 256 + $a3 * 16 + $a4;
			my $bb = ($bus >> 8) & 0xffffff;

			if ($aa == $bb)
			{
				$topdir = `$::DIRNAME $resfile`;
				chomp($topdir);
				$bus = ($bus & 0xff);
				last;
			}

		}
	}
	else
	{
		$cmd = "$::LS /proc/device-tree/pci@*/bus-range";
		my @resfiles = `$cmd`;
		foreach my $resfile (@resfiles)
		{
			chomp($resfile);
			if (&check_bus_file($resfile, $bus))
			{
				$topdir = `$::DIRNAME $resfile`;
				chomp($topdir);
				last;
			}
		}
	}

	if (defined $topdir)
	{
		print "Setbootdisk: find out top lever: $topdir\n" if $::VERBOSE;
		my $notfind = 1;
		my $pcidir  = $topdir;

		if (&check_bus_file("$pcidir/bus-range", $bus) == 2)
		{
			$notfind = 0;
		}
		while ($notfind)
		{
			$cmd = "$::LS -d $pcidir/pci@*";
			my @resfiles = `$cmd`;
			my $godeeper = 0;
			foreach my $resfile (@resfiles)
			{
				chomp($resfile);
				my $busrangefile = "$resfile/bus-range";
				my $ret = &check_bus_file("$resfile/bus-range", $bus);
				if ($ret == 2)
				{

					#get it
					$pcidir  = $resfile;
					$notfind = 0;
					print "Setbootdisk: find the bus in : $pcidir\n"
					  if $::VERBOSE;
					last;
				}
				elsif ($ret == 1)
				{

					#in these range
					$pcidir   = $resfile;
					$godeeper = 1;
					last;
				}
				else
				{
					next;
				}

				#check the bus is correct or not
			}
			if (($notfind == 1) and ($godeeper == 0))
			{
				print "Setbootdisk: Failed to find out the OF Path\n";
				return 1;
			}

		}
		$cmd = "$::LS -d $pcidir/?*\@$dev";
		if ($fun > 0) { $cmd .= ",$fun"; }

		#$cmd.=" | head -1";
		$res = `$cmd`;
		chomp($res);
		if (-d "$res/scsi\@$channel")
		{
			$res = "$res/scsi\@$channel";
		}
		if ($res =~ /\/proc\/device-tree(.+)$/)
		{
			$::OFPATH = sprintf("boot-device=$1/sd\@%x,%x", $id, $lun);
			print "Setbootdisk: Get the OF Path: $::OFPATH\n" if $::VERBOSE;
			return 0;
		}
		else
		{

			#should not happen
			print "Setbootdisk: Get no result for $cmd\n";
			return 1;
		}
	}
	else
	{
		print "Setbootdisk: Failed to find out the top lever for OF Path\n";
		return 1;
	}
}

#This function is for linux 2.4
sub getRealBus
{
	my ($bus, $dev, $fun, $irq) = @_;
	print "Setbootdisk: get real bus for bus:$bus, dev:$dev,fun:$fun,irq:$irq\n"
	  if $::VERBOSE;
	my $tmpstr = sprintf("%02x:%02x.%d", $bus, $dev, $fun);
	my $cmd    = "$::LSPCI -vn | grep -B 2 \"IRQ $irq\" | grep \"$tmpstr\"";
	my $res    = `$cmd`;

	#print "$cmd\n";
	#print "$res";
	if ($res =~ /^([0-9a-fA-F]*):[0-9a-fA-F][0-9a-fA-F].*/)
	{
		$bus = hex($1);
		return $bus;
	}
	return -1;

}

#This function is for linux 2.4
sub getDevInfo
{
	my $device = $_[0];

	#get host channel id lun from sg_map;
	my $cmd =
	  "/usr/bin/sg_map -x | $::GREP -m1 $device\$ | $::AWK  '{ print \$2 \":\"  \$3 \":\" \$4 \":\" \$5 \":\" }'";

	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}

	my $res = `$cmd`;
	chomp($res);
	if ($res eq "")
	{
		print "Setbootdisk:  failed to get host channel id and lun \n";
		exit 1;
	}
	my ($host, $channel, $id, $lun) = split(":", $res);
	if ($::VERBOSE)
	{
		print "Setbootdisk:  Get host: $host,  id: $id, lun: $lun\n";
	}

	#get bus infomatin from /proc/scsi/*/$host
	my ($driver, $bus, $dev, $fun);
	$cmd = "$::LS /proc/scsi/*/$host | head -n 1";
	my $hostfile = `$cmd`;
	chomp($hostfile);
	$host =~ s/^(\s)*//;
	chomp($host);
	if ($hostfile =~ /\/proc\/scsi\/([0-9a-zA-Z]*)\/$host/)
	{
		$driver = $1;
	}
	else
	{

		#can not found out the driver
		print "Setbootdisk: Cannot find out driver\n";
		exit 1;
	}
	if ($driver eq "sym53c8xx")
	{

		#for sym53c8xx driver
		$cmd = "grep \"PCI bus\" $hostfile";

		$res = `$cmd`;
		if ($res =~
			/PCI bus ([0-9]+), device ([0-9]+), function ([0-9]+), IRQ ([0-9]+)/
		   )
		{
			$dev = $2;
			$fun = $3;
			$bus = getRealBus($1, $2, $3, $4);
			if ($bus == -1)
			{

				#error, out
				print
				  "Setbootdisk: Cannot find out real bus for bus:$1, dev:$2,fun:$3,irq:$4\n";
				exit 1;
			}
		}
		else
		{

			#driver output can not support? should not happen
			print "Setbootdisk: Cannot read bus info from  $hostfile\n";
			exit 1;
		}
	}
	elsif (($driver eq "ibmsis") || ($driver eq "ipr"))
	{

		#for ibmsis driver
		$cmd = "$::GREP \"Host Address\" $hostfile ";
		$res = `$cmd`;
		if ($res =~
			/([0-9a-fA-F]*[0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])/
		   )
		{
			$bus = hex($1);
			$dev = hex($2);
			$fun = hex($3);
		}
		else
		{
			print "Setbootdisk: Cannot read bus info from  $hostfile\n";
			exit 1;
		}
	}
	else
	{
		print
		  "Setbootdisk:  Can not support the current scsi disk driver now\n";
	}

	return ($bus, $dev, $fun, $channel, $id, $lun);
}

#old function need remove
#This function is for linux 2.4
sub getOFPath
{

	my ($channel, $lum, $loc_code, $id, $cmd, $tmpstr, $devpath);

	my $devpath = $_[0];

	$cmd = "$::SCSIDEV -s 2>&1 1>/dev/null";
	system($cmd);

	$cmd = "$::LS -l $devpath | $::CUT -b 37-42";

	#$cmd="ls -l $DEVPATH | awk '{ print \$5 \"   \" \$6}'";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	$tmpstr = `$cmd`;
	chop($tmpstr);

	if ($tmpstr eq "")
	{
		print "Setbootdisk: $devpath is not a valid disk path\n";
		return 1;
	}
	else
	{
		if ($::VERBOSE)
		{
			print "Setbootdisk: Get number ($tmpstr)\n";
		}
	}

	$cmd =
	  "$::LS -l --time-style=iso /dev/scsi/sd* | $::GREP \"$tmpstr\" | $::AWK '{ print \$9 }' ";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	$tmpstr = `$cmd`;

	chop($tmpstr);
	if ($tmpstr =~ /.+-([\da-z]+)c(\d)i(\d)l(\d)/)
	{
		$tmpstr  = $1;
		$channel = $2;
		$id      = $3;
		$lum     = $4;

		if ($::VERBOSE)
		{
			print
			  "Setbootdisk: Get device CHANNEL: $channel, ID: $id, LUM: $lum, i/o str: $tmpstr \n";
		}
	}
	else
	{
		print "Setbootdisk: Failed to get CHANNEL, ID and LUM \n";
		return 1;
	}

	$cmd =
	  "$::GREP -B 3 $tmpstr /proc/pci |  $::HEAD -n 1 | $::AWK '{ print \$4;}'";
	$loc_code = `$cmd`;
	chop($loc_code);
	if ($loc_code ne "")
	{
		if ($::VERBOSE)
		{
			print "Setbootdisk: Run command : $cmd\n";
			print "Setbootdisk: Get IBM location code: $loc_code \n";
		}
	}
	else
	{
		print "Setbootdisk: Failed to get IBM location code \n";
		return 1;
	}
	$cmd =
	  "$::GREP $loc_code /proc/device-tree/pci*/ -ra | $::SORT -nr | $::HEAD -n 1";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	$tmpstr = `$cmd`;
	chop($tmpstr);

	if ($tmpstr =~ /\/proc\/device-tree(.+)\/ibm,loc-code:.+/)
	{
		$tmpstr = $1;

		if ($::VERBOSE)
		{
			print "Setbootdisk: Get IBM location code: $loc_code \n";
		}
	}
	else
	{
		print
		  "Setbootdisk: Failed to find IBM location code in /proc/device-tree \n";
		return 1;
	}

	$::OFPATH = "boot-device=$tmpstr/sd$id,$lum";

	return 0;
}

#This function is for linux 2.4
sub setOFBootForLinux24
{

	my ($oldbootdevice, $bootdevice, $cmd);
	$cmd = "$::STRINGS $::NVRAM | $::GREP boot-device";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	$oldbootdevice = `$cmd`;
	chop($oldbootdevice);

	if ($oldbootdevice eq "")
	{
		print "Setbootdisk: Failed to get the old OF boot device \n$cmd\n";
		exit 1;
	}

	if ($::VERBOSE)
	{
		print "Setbootdisk: Get the old OF boot device : $oldbootdevice \n";
	}
	$bootdevice = $::OFPATH;

	$oldbootdevice =~ s/\\/\\\\/g;
	$bootdevice    =~ s/\\/\\\\/g;
	$oldbootdevice =~ s/\//\\\//g;
	$bootdevice    =~ s/\//\\\//g;

	$cmd = "$::SED 's/$oldbootdevice/$bootdevice/' $::NVRAM > $::NVRAMTMP";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	system($cmd);
	`$::CAT "$::NVRAMTMP" > "$::NVRAM"`;

	if ($::VERBOSE)
	{
		print "Setbootdisk: Success to set the OF boot device \n";
	}

	return 0;
}

#This function is for linux 2.6
#Note this function will return 0 when failing
sub setOFBootForLinux26
{
	my $cmd;
	$cmd = "nvram --update-config boot-device=$::OFPATH -p common ";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	system($cmd);
	$cmd = "nvram --update-config auto-boot?=true -p common ";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	system($cmd);
	$cmd = "nvram --update-config menu?=false -p common ";
	if ($::VERBOSE)
	{
		print "Setbootdisk: Run command : $cmd\n";
	}
	system($cmd);
	if ($::VERBOSE)
	{
		print "Setbootdisk: Success to set the OF boot device \n";
	}
	return 1;
}

#This function is for linux 2.6
#Note this function will return 0 when failing
sub getLink
{
	my ($linkName) = @_;
	my $lsRes = `$::LS -l $linkName 2>/dev/nul`;
	chomp $lsRes;
	my ($linkObj) = ($lsRes =~ /$linkName\s->\s(.*)$/);
	return $linkObj;
}

#This function is for linux 2.6
#Note this function will return 0 when failing
sub findFileInDirAndParents
{
	my ($file, $dir) = @_;
	if (!-d $dir)
	{
		return 0;
	}
	my $isFound = 0;
	while ($isFound == 0 && $dir ne '/')
	{
		if (-f "$dir/$file" || -l "$dir/$file")
		{
			$isFound = 1;
		}
		else
		{
			($dir) = ($dir =~ /^(.*)\/[^\/]*$/);
		}
	}
	if ($isFound)
	{
		return $dir;
	}
	else
	{
		return 0;
	}
}

#This function is for linux 2.6
#Note this function will return 0 when failing
sub getHBTL
{
	my ($file) = @_;
	my ($host, $bus, $target, $lun) =
	  ($file =~ /\/([^\/]*):([^\/]*):([^\/]*):([^\/]*)$/);

    $bus = sprintf("%x",$bus);
	return ($host, $bus, $target, $lun);
}

#This function is for linux 2.6
#Note this function will return 0 when failing
sub getVDiskNo
{
	my ($file) = @_;
	my ($host, $bus, $target, $lun) = &getHBTL($file);
	$bus    = $bus << 5;
	$target = $target << 8;
	my $vDiskNo = 0x8000 | $bus | $target | $lun;
	$vDiskNo = sprintf "%1x000000000000", $vDiskNo;
	return $vDiskNo;
}

#This function is for linux 2.6,
#Note this function will return 0 when failing
sub findOFPathForLinux26
{
	my ($logicalName) = @_;
	($logicalName) = ($logicalName =~ /([^\/]*)$/);
	my ($deviceDir) = `$::FIND /sys -name $logicalName`;
	chomp $deviceDir;
	$deviceDir = &findFileInDirAndParents("device", $deviceDir);
	if (!$deviceDir)
	{
		print "Cannot find a device $logicalName in directory /sys!\n";
		return 0;
	}
	my $linkObj = &getLink("$deviceDir/device");
	if (!$linkObj)
	{
		print "Cannot find the device link in directory $deviceDir!\n";
		return 0;
	}
	my $devSpecDir = &findFileInDirAndParents("devspec", "$deviceDir/$linkObj");
	if (!$devSpecDir)
	{
		print
		  "Cannot find the devspec file in directory $deviceDir/$linkObj!\n";
		return 0;
	}
	my ($OFPathname) = `$::CAT $devSpecDir/devspec`;
	chomp $OFPathname;
	if ($OFPathname =~ /^\/vdevice/)
	{
		#This device is a vitual SCSI device
		my $vDiskNo;
		if ( $linkObj =~ /\/([^\/]*):([^\/]*):([^\/]*):([^\/]*)$/)
		{
			$vDiskNo = &getVDiskNo( $linkObj);
		}
		else
		{
			my ($hbtlFile) = `$::FIND $devSpecDir/host* -name *:*:*:*`;
			if ($? != 0)
			{
				($hbtlFile) = `$::FIND $devSpecDir/target* -name *:*:*:*`;
				if ($? != 0)
				{
					print "Cannot find HBTL in diretory $devSpecDir/host* and $devSpecDir/target*!\n";
					return 0;
				}
			}

			chomp $hbtlFile;
			$vDiskNo = &getVDiskNo($hbtlFile);
		}
		$OFPathname = "$OFPathname/disk\@$vDiskNo";
	}
	elsif( $OFPathname =~ /\/ide@/ )
	{
		if ( $linkObj =~ /\/ide(\d)\//)
		{
			my $ideNo = $1;
			$OFPathname = "$OFPathname/disk\@$ideNo";
		}
		else
		{
			return 0;
		}
	}
	else
	{

		#This device is a normal SCSI device
		my ($host, $bus, $target, $lun) = &getHBTL($linkObj);
		if ($OFPathname !~ /\/scsi@/)
		{
			$OFPathname = $OFPathname . "/scsi\@$bus";
		}
		$OFPathname = sprintf("%s/sd\@%1x,%1x", $OFPathname, $target, $lun);
	}
	$::OFPATH = $OFPathname;
	return $OFPathname;
}

=item Main
  
 Main 
 
=cut 

&getArgs();
my ($junk, $junk, $install_driver) = split('/', $ARGV[0]);
if ((!$install_driver) || ($install_driver !~ /^[sh]d/))
{
	print "Setbootdisk: Please provide the full path of the disk device file ... \n";
	exit 0;   
}
if ($::VERBOSE)
{
	print "Setbootdisk: Get disk path $ARGV[0]\n";
}
$::KERNEL_VERSION = `/bin/uname -r`;
chomp $::KERNEL_VERSION;

if ($::KERNEL_VERSION =~ /^2\.6/)
{

	#Function findOFPathForLinux26 will return 0 when failing
	if (!&findOFPathForLinux26($ARGV[0]))
	{
		print "Setbootdisk: Failed to get OF path.\n";
		exit 1;
	}
	if (!&setOFBootForLinux26())
	{
		print "Setbootdisk: Failed to set OF boot device\n";
		exit 1;
	}

}
else
{

	if ( $install_driver !~ /^sd/)
	{
		print "Setbootdisk: For Linux 2.4, only scsi driver is supported";
		exit 1;
	}
	#Function findOFPathForLinux24 will return 1 when failing
	if (&findOFPathForLinux24($ARGV[0]))
	{
		print "Setbootdisk: Failed to get OF path.\n";
		exit 1;
	}
	if (&setOFBootForLinux24())
	{
		print "Setbootdisk: Failed to set OF boot device\n";
		exit 1;
	}

}

exit 0;

