#!/usr/bin/perl
#
# Name: hp_rescan
# Copyright: (c)Copyright 2008 Hewlett-Packard Development Company, L.P.
#
# Description: Sends the rescan signal to selected or all fibre channel HBAs
#  
# Modification History
#
# Chad Dupuis   09/16/08 Initial Development
# Chad Dupuis	07/13/09 Added support for Brocade HBAs
# Chad Dupuis	12/14/09 Added support for Emulex CNAs; Use lspci -D 
# Keith Wortman	02/25/11 Minor corrections for Emulex CNA's
# Keith Wortman 03/28/11 Corrected behavior with Brocade single-port devices
#

use Getopt::Long;

#
# Defines
#

$debug = 0;
$FALSE = 0;
$TRUE = 1;
$GOOD = 0;
$BAD = 1;
$all_flag = 0;
$instance_flag = -1;
$list_flag = 0;
$lspcicmd = "lspci -D";
$qlogic_lspci_str = "QLogic";
$emulex_lspci_str = "Emulex";
$serverengines_lspci_str = "ServerEngines";
$brocade_lspci_str = "Brocade";
$sysfs_pci_dir = "/sys/bus/pci/devices/";
$sysfs_scsi_host_dir = "/sys/class/scsi_host";

#
# Functions
#

# Name: Debug
# Description: Prints a debug statement
# In: func - Function name
#     message - Message to print
# Out: None
# Returns: None
sub Debug {
	my $func = $_[0];
	my $message = $_[1];

	if ($debug) {
		print "$func(): $message\n";
	}
}

# Name: ParseArguments
# Description: Parses the arguments passed at the command line
# In: None
# Out: basic_info_flag, verbose_info_flag, help_flag, pciid_flag, model_info_flag,
#      lun_info_flag and remote_port_info_flag filled in
# Returns: None
sub ParseArguments {
	# Use GetOptions to parse all the command line arguments.  If result is still set to
	# FALSE after the GetOptions call then an error occured to exit out of the script
	
	my $result = $FALSE;
	$result = GetOptions ('h' => \$help_flag, '--help' =>\$help_flag, 'a' => \$all_flag, 'all' =>\$all_flag, 'l' => \$list_flag, 'list' => \$list_flag, 'i=i' => \$instance_flag, 'instance=i' => \$instance_flag);
	
	Debug ("ParseArguments", "result=\"".$result."\"\n");
	
	if ($result == $false) {
		exit ($BAD);
	}
	
	#
	# Argument post processing
	
	# If the help flag was specified simply print the help message and exist
	
	if ($help_flag) {
		PrintHelp();
	}
	
	# If the list flag is specificed it takes precidence over all else
	
	if ($list_flag) {
		$instance_flag = -1;
		$all_flag = $FALSE;
		return($TRUE)
	}
	
	# If the instance flag is specified, it takes precedence over the all flag
	
	if ($instance_flag > -1) {
		$all_flag = $FALSE;
		return ($TRUE);
	}
}

# Name: PrintHelp
# Description: Prints a help message
# In: None
# Out: None
# Returns: None
sub PrintHelp {
	print "NAME\n\n";
	print "hp_rescan\n\n";
	print "DESCRIPTION\n\n";
	print "Sends the rescan signal to all or selected Fibre Channel HBAs/CNAs.\n\n";
	print "OPTIONS\n\n";
	print "-a, --all      - Rescan all Fibre Channel HBAs\n";
	print "-h, --help     - Prints this help message\n";
	print "-i, --instance - Rescan a particular instance <SCSI host number>\n";
	print "-l, --list     - List all supported Fibre Channel HBAs\n";	
	exit ($GOOD);
}

# Name: GetQLogicHbas
# Description: Get a list of the PCI addresses of all QLogic adapters 
# In: None
# Out: None
# Returns: List of PCI addresses of QLogic HBAs
sub GetQLogicHbas {
	Debug ("GetQLogicHbas", "Command is $lspcicmd | grep $qlogic_lspci_str | awk '{print \$1}'");

	$pci_addrs = `$lspcicmd | grep $qlogic_lspci_str | grep Fibre | awk '{print \$1}'`;
	Debug ("GetQLogicHbas", "pci_addrs = $pci_addrs");

	return split ('\n', $pci_addrs);
}

# Name: GetEmulexHbas
# Description: Get a list of the PCI addresses of all Emulex adapters 
# In: None
# Out: None
# Returns: List of PCI addresses of Emulex HBAs
sub GetEmulexHbas {
	Debug ("GetEmulexHbas", "Command is $lspcicmd | grep $emulex_lspci_str | awk '{print \$1}'");

	$pci_addrs = `$lspcicmd | grep $emulex_lspci_str | grep -v $serverengines_lspci_str | grep Fibre | awk '{print \$1}'`;
	Debug ("GetEmulexHbas", "pci_addrs = $pci_addrs");

	return split ('\n', $pci_addrs);
}

# Name: GetBrocadeHbas
# Description: Get a list of the PCI addresses of all Brocade adapters 
# In: None
# Out: None
# Returns: List of PCI addresses of Brocade HBAs
sub GetBrocadeHbas {
	Debug ("GetBrocadeHbas", "Command is $lspcicmd | grep $brocade_lspci_str | awk '{print \$1}'");

	$pci_addrs = `$lspcicmd | grep $brocade_lspci_str | awk '{print \$1}'`;
	Debug ("GetBrocadeHbas", "pci_addrs = $pci_addrs");

# For single-port devices, skip the phantom port (test for SCSI hosts)
        my @pci_addrs_1;
        my @pci_addrs_2;
        @pci_addrs_1 = split ('\n', $pci_addrs);

        foreach $pci_addrs (@pci_addrs_1) {
                if ((`ls $sysfs_pci_dir$pci_addrs | grep host`) ne "") {
                        push @pci_addrs_2, $pci_addrs;
                }
        }

#       return split ('\n', $pci_addrs);
        return @pci_addrs_2;
}

# Name: GetEmulexCnas
# Description: Get a list of the PCI addresses of all Emulex CNA adapters in the system
# In: None
# Out: None
# Returns: List of PCI addresses of EmulexCNAs
sub GetEmulexCnas {
	Debug ("GetEmulexCnas", "Command is $lspcicmd | grep $serverengines_lspci_str | grep \"Fibre Channel\" | awk '{print \$1}'");

	$pci_addrs = `$lspcicmd | grep $serverengines_lspci_str | grep \"Fibre Channel\" | awk '{print \$1}'`;
	Debug ("GetBrocadeHbas", "pci_addrs = $pci_addrs");

	return split ('\n', $pci_addrs);
}

# Name: ListAllHbas
# Description: Prints a list of all QLogic and Emulex HBAs to stdout
# In: None
# Out: None
# Returns: None
sub ListAllHbas {
	print "QLogic adapters:\n\n";
	
	foreach my $adapter (GetQLogicHbas()) {
		print $sysfs_scsi_host_dir."/".GetScsiHostNo($adapter)."\n";
	}
	
	print "\nEmulex adapters:\n\n";
	
	# FC HBAs	
	foreach my $adapter (GetEmulexHbas()) {
		print $sysfs_scsi_host_dir."/".GetScsiHostNo($adapter)."\n";
	}
	
	# CNAs
	foreach my $adapter (GetEmulexCnas()) {
		print $sysfs_scsi_host_dir."/".GetScsiHostNo($adapter)."\n";
	}
	
	print "\nBrocade adapters:\n\n";
	
	foreach my $adapter (GetBrocadeHbas()) {
		print $sysfs_scsi_host_dir."/".GetScsiHostNo($adapter)."\n";
	}
}

# Name: GetScsiHostNo
# Description: Retrieves the SCSI host number of an adapter
# In: @adapterid - PCI bus id 
# Out: None
# Returns: scsi host number
sub GetScsiHostNo {
    my $adapterid = $_[0];
    my $scsihostno = `ls $sysfs_pci_dir$adapterid | grep host |  sed \'s/host//g\'`;
	
	chomp ($scsihostno); 
	Debug ("GetScsiHostNo", "scsihostno is ".$scsihostno);
    
    return $scsihostno;
}

# Name: VerifyInstance
# Description: Verifies that a specific SCSI host exists
# In: @hostid - SCSI host number
# Out: None
# Returns: TRUE if instance exists; FALSE otherwise
sub VerifyInstance {
	my $hostid = $_[0];
	
	if (-d $sysfs_scsi_host_dir."/host".$hostid) {
		Debug ("VerifyInstance", "Returning TRUE");
		return ($TRUE);
	}	
	else {
		Debug ("VerifyInstance", "Returning FALSE");
		return ($FALSE);
	}
}

# Name: RescanInstance
# Description: Rescans a specific SCSI host instance
# In: @hostid - SCSI host number to scan
# Out: None
# Returns: None
sub RescanInstance {
	my $hostid = $_[0];
	
	if (VerifyInstance($hostid) == $FALSE) {
		print "Cannot find ".$sysfs_scsi_host_dir."/host".$hostid."\n";
		exit ($BAD);
	}
	
	print "Rescanning ".$sysfs_scsi_host_dir."/host".$hostid."\n";
	my $command_str = "echo \"- - -\" > ".$sysfs_scsi_host_dir."/host".$hostid."/scan";
	Debug ("RescanInstance", "command_str=\"".$command_str."\"");
	system ($command_str);
}

# Name: RescanAll
# Description: Rescan all Emulex and QLogic fibre channel HBAs
# In: None
# Out: None
# Returns: None
sub RescanAll {
	foreach my $adapter (GetQLogicHbas()) {
		RescanInstance(GetScsiHostNo($adapter));
	}
	
	foreach my $adapter (GetEmulexHbas()) {
		RescanInstance(GetScsiHostNo($adapter));
	}	

	foreach my $adapter (GetEmulexCnas()) {
		RescanInstance(GetScsiHostNo($adapter));
	}

	foreach my $adapter (GetBrocadeHbas()) {
		RescanInstance(GetScsiHostNo($adapter));
	}
}

#
# Script Main
#

ParseArguments();
	
# Based on the arguments that were passed in, perform the right action

if ($list_flag) {
	ListAllHbas();
}
elsif ($instance_flag > -1) {
	RescanInstance($instance_flag)
}
elsif ($all_flag) {
	RescanAll();
}
