2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2025-05-22 03:32:04 +00:00
Jarrod Johnson 7c66aec2ea Skip connection to BMC on rpower reseat
rpower reseat gets directed at the chassis manager.  Skip the
superfluous connection to the BMC and keep it from interfering
with the reseat.
2017-11-07 15:18:33 -05:00

9092 lines
324 KiB
Perl

#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#egan@us.ibm.com
#modified by jbjohnso@us.ibm.com
#(C)IBM Corp
# VIM: set tabstop=8 softtabstop=0 expandtab shiftwidth=4 smarttab
#
package xCAT_plugin::ipmi;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use warnings "all";
use xCAT::GlobalDef;
use xCAT_monitoring::monitorctrl;
use xCAT::SPD qw/decode_spd/;
use xCAT::IPMI;
use xCAT::PasswordUtils;
use File::Basename;
my %needbladeinv;
use POSIX qw(ceil floor);
use Storable qw(nstore_fd retrieve_fd thaw freeze);
use Scalar::Util qw(looks_like_number);
use xCAT::Utils;
use xCAT::TableUtils;
use xCAT::IMMUtils;
use xCAT::ServiceNodeUtils;
use xCAT::SvrUtils;
use xCAT::NetworkUtils;
use xCAT::Usage;
use File::Path;
use File::Spec;
use Thread qw(yield);
use LWP 5.64;
use HTTP::Request::Common;
use Time::HiRes qw/time/;
my $iem_support;
my $vpdhash;
my %allerrornodes = ();
my %newnodestatus = ();
my $global_sessdata;
my %child_pids;
my $xcatdebugmode = 0;
my $IPMIXCAT = "/opt/xcat/bin/ipmitool-xcat";
my $NON_BLOCK = 1;
use constant RFLASH_LOG_DIR => "/var/log/xcat/rflash";
if (-d RFLASH_LOG_DIR) {
chmod 0700, RFLASH_LOG_DIR;
} else {
mkpath(RFLASH_LOG_DIR, 0, 0700);
}
require xCAT::data::ibmhwtypes;
eval {
require IBM::EnergyManager;
$iem_support = 1;
};
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(
ipmicmd
);
sub handled_commands {
return {
rpower => 'nodehm:power,mgt', #done
renergy => 'nodehm:mgt', #support renergy for OperPOWER servers
getipmicons => 'ipmi', #done
rspconfig => 'nodehm:mgt', #done
rspreset => 'nodehm:mgt', #done
rvitals => 'nodehm:mgt', #done
rinv => 'nodehm:mgt', #done
rflash => 'nodehm:mgt', #done
rsetboot => 'nodehm:mgt', #done
rbeacon => 'nodehm:mgt', #done
reventlog => 'nodehm:mgt',
ripmi => 'ipmi',
# rfrurewrite => 'nodehm:mgt', #deferred, doesn't even work on several models, no one asks about it, keeping it commented for future requests
getrvidparms => 'nodehm:mgt', #done
rscan => 'nodehm:mgt', # used to scan the mic cards installed on the target node
}
}
use POSIX "WNOHANG";
use IO::Handle;
use IO::Socket;
use IO::Select;
use Class::Struct;
use Digest::MD5 qw(md5);
use POSIX qw(WNOHANG mkfifo strftime);
use Fcntl qw(:flock);
#local to module
my $callback;
my $ipmi_bmcipaddr;
my $timeout;
my $port;
my $debug;
my $ndebug = 0;
my $sock;
my $noclose;
my %sessiondata; #hold per session variables, in preparation for single-process strategy
my %pendingtransactions; #list of peers with callbacks, callback arguments, and timer expiry data
my $ipmiv2 = 0;
my $authoffset = 0;
my $enable_cache = "yes";
my $cache_dir = "/var/cache/xcat";
#my $ibmledtab = $ENV{XCATROOT}."/lib/GUMI/ibmleds.tab";
use xCAT::data::ibmleds;
use xCAT::data::ipmigenericevents;
use xCAT::data::ipmisensorevents;
my $cache_version = 4;
my %sdr_caches; #store sdr cachecs in memory indexed such that identical nodes do not hit the disk multiple times
#my $status_noop="XXXno-opXXX";
# Firmware data hash
my %hpm_data_hash = ();
my %idpxthermprofiles = (
'0z' => [ 0x37, 0x41, 0, 0, 0, 0, 5, 0xa, 0x3c, 0xa, 0xa, 0x1e ],
'1a' => [ 0x30, 0x3c, 0, 0, 0, 0, 5, 0xa, 0x3c, 0xa, 0xa, 0x1e ],
'2b' => [ 0x30, 0x3c, 0, 0, 0, 0, 5, 0xa, 0x3c, 0xa, 0xa, 0x1e ],
'3c' => [ 0x30, 0x3c, 0, 0, 0, 0, 5, 0xa, 0x3c, 0xa, 0xa, 0x1e ],
'4d' => [ 0x37, 0x44, 0, 0, 0, 0, 5, 0xa, 0x3c, 0xa, 0xa, 0x1e ],
'5e' => [ 0x37, 0x44, 0, 0, 0, 0, 5, 0xa, 0x3c, 0xa, 0xa, 0x1e ],
'6f' => [ 0x35, 0x44, 0, 0, 0, 0, 5, 0xa, 0x3c, 0xa, 0xa, 0x1e ],
);
my %codes = (
0x00 => "Command Completed Normal",
0xC0 => "Node busy, command could not be processed",
0xC1 => "Invalid or unsupported command",
0xC2 => "Command invalid for given LUN",
0xC3 => "Timeout while processing command, response unavailable",
0xC4 => "Out of space, could not execute command",
0xC5 => "Reservation canceled or invalid reservation ID",
0xC6 => "Request data truncated",
0xC7 => "Request data length invalid",
0xC8 => "Request data field length limit exceeded",
0xC9 => "Parameter out of range",
0xCA => "Cannot return number of requested data bytes",
0xCB => "Requested Sensor, data, or record not present",
0xCB => "Not present",
0xCC => "Invalid data field in Request",
0xCD => "Command illegal for specified sensor or record type",
0xCE => "Command response could not be provided",
0xCF => "Cannot execute duplicated request",
0xD0 => "Command reqponse could not be provided. SDR Repository in update mode",
0xD1 => "Command response could not be provided. Device in firmware update mode",
0xD2 => "Command response could not be provided. BMC initialization or initialization agent in progress",
0xD3 => "Destination unavailable",
0xD4 => "Insufficient privilege level",
0xD5 => "Command or request parameter(s) not supported in present state",
0xFF => "Unspecified error",
);
#Payload types:
# 0 => IPMI (format 1 0)
# 1 => SOL 1 0
# 0x10 => rmcp+ open req 1 0
# 0x11 => rmcp+ response 1 0
# 0x12 => rakp1 (all 1 0)
# 0x13 => rakp2
# 0x14 => rakp3
# 0x15 => rakp4
my %units = (
0 => "", #"unspecified",
1 => "C",
2 => "F",
3 => "K",
4 => "Volts",
5 => "Amps",
6 => "Watts",
7 => "Joules",
8 => "Coulombs",
9 => "VA",
10 => "Nits",
11 => "lumen",
12 => "lux",
13 => "Candela",
14 => "kPa",
15 => "PSI",
16 => "Newton",
17 => "CFM",
18 => "RPM",
19 => "Hz",
20 => "microsecond",
21 => "millisecond",
22 => "second",
23 => "minute",
24 => "hour",
25 => "day",
26 => "week",
27 => "mil",
28 => "inches",
29 => "feet",
30 => "cu in",
31 => "cu feet",
32 => "mm",
33 => "cm",
34 => "m",
35 => "cu cm",
36 => "cu m",
37 => "liters",
38 => "fluid ounce",
39 => "radians",
40 => "steradians",
41 => "revolutions",
42 => "cycles",
43 => "gravities",
44 => "ounce",
45 => "pound",
46 => "ft-lb",
47 => "oz-in",
48 => "gauss",
49 => "gilberts",
50 => "henry",
51 => "millihenry",
52 => "farad",
53 => "microfarad",
54 => "ohms",
55 => "siemens",
56 => "mole",
57 => "becquerel",
58 => "PPM",
59 => "reserved",
60 => "Decibels",
61 => "DbA",
62 => "DbC",
63 => "gray",
64 => "sievert",
65 => "color temp deg K",
66 => "bit",
67 => "kilobit",
68 => "megabit",
69 => "gigabit",
70 => "byte",
71 => "kilobyte",
72 => "megabyte",
73 => "gigabyte",
74 => "word",
75 => "dword",
76 => "qword",
77 => "line",
78 => "hit",
79 => "miss",
80 => "retry",
81 => "reset",
82 => "overflow",
83 => "underrun",
84 => "collision",
85 => "packets",
86 => "messages",
87 => "characters",
88 => "error",
89 => "correctable error",
90 => "uncorrectable error",
);
my %chassis_types = (
0 => "Unspecified",
1 => "Other",
2 => "Unknown",
3 => "Desktop",
4 => "Low Profile Desktop",
5 => "Pizza Box",
6 => "Mini Tower",
7 => "Tower",
8 => "Portable",
9 => "LapTop",
10 => "Notebook",
11 => "Hand Held",
12 => "Docking Station",
13 => "All in One",
14 => "Sub Notebook",
15 => "Space-saving",
16 => "Lunch Box",
17 => "Main Server Chassis",
18 => "Expansion Chassis",
19 => "SubChassis",
20 => "Bus Expansion Chassis",
21 => "Peripheral Chassis",
22 => "RAID Chassis",
23 => "Rack Mount Chassis",
);
my %MFG_ID = (
2 => "IBM",
343 => "Intel",
20301 => "IBM",
19046 => "Lenovo",
);
my %PROD_ID = (
"2:34869" => "e325",
"2:3" => "x346",
"2:4" => "x336",
"343:258" => "Tiger 2",
"343:256" => "Tiger 4",
);
my $localtrys = 3;
my $localdebug = 0;
struct SDR => {
rec_type => '$',
sensor_owner_id => '$',
sensor_owner_lun => '$',
sensor_number => '$',
entity_id => '$',
entity_instance => '$',
sensor_init => '$',
sensor_cap => '$',
sensor_type => '$',
event_type_code => '$',
ass_event_mask => '@',
deass_event_mask => '@',
dis_read_mask => '@',
sensor_units_1 => '$',
sensor_units_2 => '$',
sensor_units_3 => '$',
linearization => '$',
M => '$',
tolerance => '$',
B => '$',
accuracy => '$',
accuracy_exp => '$',
R_exp => '$',
B_exp => '$',
analog_char_flag => '$',
nominal_reading => '$',
normal_max => '$',
normal_min => '$',
sensor_max_read => '$',
sensor_min_read => '$',
upper_nr_threshold => '$',
upper_crit_thres => '$',
upper_ncrit_thres => '$',
lower_nr_threshold => '$',
lower_crit_thres => '$',
lower_ncrit_thres => '$',
pos_threshold => '$',
neg_threshold => '$',
id_string_type => '$',
id_string => '$',
#LED id
led_id => '$',
fru_type => '$',
fru_subtype => '$',
fru_oem => '$',
};
struct FRU => {
rec_type => '$',
desc => '$',
value => '$',
};
sub decode_fru_locator { #Handle fru locator records
my @locator = @_;
my $sdr = SDR->new();
$sdr->rec_type(0x11);
$sdr->sensor_owner_id("FRU");
$sdr->sensor_owner_lun("FRU");
$sdr->sensor_number($locator[7]);
unless ($locator[8] & 0x80 and ($locator[8] & 0x1f) == 0 and $locator[9] == 0) {
#only logical devices at lun 0 supported for now
return undef;
}
unless (($locator[16] & 0xc0) == 0xc0) { #Only unpacked ASCII for now, no unicode or BCD plus yet
return undef;
}
my $idlen = $locator[16] & 0x3f;
unless ($idlen > 1) { return undef; }
$sdr->id_string(pack("C*", @locator[ 17 .. 17 + $idlen - 1 ]));
$sdr->fru_type($locator[11]);
$sdr->fru_subtype($locator[12]);
$sdr->fru_oem($locator[15]);
return $sdr;
}
sub waitforack {
my $sock = shift;
my $select = new IO::Select;
$select->add($sock);
my $str;
if ($select->can_read(60)) { # Continue after 60 seconds, even if not acked...
if ($str = <$sock>) {
} else {
$select->remove($sock); #Block until parent acks data
}
}
}
sub translate_sensor {
my $reading = shift;
my $sdr = shift;
my $unitdesc;
my $value;
my $lformat;
my $per;
$unitdesc = $units{ $sdr->sensor_units_2 };
if ($sdr->rec_type == 1) {
$value = (($sdr->M * $reading) + ($sdr->B * (10**$sdr->B_exp))) * (10**$sdr->R_exp);
} else {
$value = $reading;
}
if ($sdr->rec_type != 1 or $sdr->linearization == 0) {
$reading = $value;
if ($value == int($value)) {
$lformat = "%-30s%8d%-20s";
} else {
$lformat = "%-30s%8.3f%-20s";
}
} elsif ($sdr->linearization == 7) {
if ($value > 0) {
$reading = 1 / $value;
} else {
$reading = 0;
}
$lformat = "%-30s%8d %-20s";
} else {
$reading = "RAW($sdr->linearization) $reading";
}
if ($sdr->sensor_units_1 & 1) {
$per = "% ";
} else {
$per = " ";
}
my $numformat = ($sdr->sensor_units_1 & 0b11000000) >> 6;
if ($numformat) {
if ($numformat eq 0b11) {
#Not sure what to do.. leave it alone for now
} else {
if ($reading & 0b10000000) {
if ($numformat eq 0b01) {
$reading = 0 - ((~($reading & 0b01111111)) & 0b1111111);
} elsif ($numformat eq 0b10) {
$reading = 0 - (((~($reading & 0b01111111)) & 0b1111111) + 1);
}
}
}
}
if ($unitdesc eq "Watts") {
my $f = ($reading * 3.413);
$unitdesc = "Watts (" . int($f + .5) . " BTUs/hr)";
#$f = ($reading * 0.00134);
#$unitdesc .= " $f horsepower)";
}
if ($unitdesc eq "C") {
my $f = ($reading * 9 / 5) + 32;
$unitdesc = "C (" . int($f + .5) . " F)";
}
if ($unitdesc eq "F") {
my $c = ($reading - 32) * 5 / 9;
$unitdesc = "F (" . int($c + .5) . " C)";
}
return "$reading $unitdesc";
}
sub ipmicmd {
my $sessdata = shift;
my $rc = 0;
my $text = "";
my $error = "";
my @output;
my $noclose = 0;
$sessdata->{ipmisession}->login(callback => \&on_bmc_connect, callback_args => $sessdata);
}
sub on_bmc_connect {
my $status = shift;
my $sessdata = shift;
my $command = $sessdata->{command};
if ($status =~ /ERROR:/) {
xCAT::SvrUtils::sendmsg([ 1, $status ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
#ok, detect some common prereqs here, notably:
#getdevid
if ($command eq "getrvidparms" or $command eq "rflash") {
unless (defined $sessdata->{device_id}) {
$sessdata->{ipmisession}->subcmd(netfn => 6, command => 1, data => [], callback => \&gotdevid, callback_args => $sessdata);
return;
}
if ($command eq "getrvidparms") {
getrvidparms($sessdata);
} else {
rflash($sessdata);
}
}
#initsdr
if ($command eq "rinv" or $command eq "reventlog" or $command eq "rvitals") {
unless (defined $sessdata->{device_id}) {
$sessdata->{ipmisession}->subcmd(netfn => 6, command => 1, data => [], callback => \&gotdevid, callback_args => $sessdata);
return;
}
unless ($sessdata->{sdr_hash}) {
initsdr($sessdata);
return;
}
}
if ($command eq "ping") {
xCAT::SvrUtils::sendmsg("ping", $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($command eq "rpower") {
unless ($sessdata->{subcommand} eq "reseat" or defined $sessdata->{device_id}) { #need get device id data initted for S3 support
$sessdata->{ipmisession}->subcmd(netfn => 6, command => 1, data => [], callback => \&gotdevid, callback_args => $sessdata);
return;
}
return power($sessdata);
} elsif ($command eq "ripmi") {
return ripmi($sessdata);
} elsif ($command eq "rspreset") {
return resetbmc($sessdata);
} elsif ($command eq "rbeacon") {
unless (defined $sessdata->{device_id}) { #need get device id data initted for SD350 workaround
$sessdata->{ipmisession}->subcmd(netfn => 6, command => 1, data => [], callback => \&gotdevid, callback_args => $sessdata);
return;
}
return beacon($sessdata);
} elsif ($command eq "rsetboot") {
return setboot($sessdata);
} elsif ($command eq "rspconfig") {
shift @{ $sessdata->{extraargs} };
# set username or/and password for specified userid #
if ($sessdata->{subcommand} =~ /userid|username|password/) {
setpassword($sessdata);
} else {
if ($sessdata->{subcommand} =~ /=/) {
setnetinfo($sessdata);
} else {
getnetinfo($sessdata);
}
}
} elsif ($command eq "rvitals") {
vitals($sessdata);
} elsif ($command eq "rinv") {
inv($sessdata);
} elsif ($command eq "reventlog") {
eventlog($sessdata);
} elsif ($command eq "renergy") {
unless (defined $sessdata->{device_id}) {
$sessdata->{ipmisession}->subcmd(netfn => 6, command => 1, data => [], callback => \&gotdevid, callback_args => $sessdata);
return;
}
renergy($sessdata);
}
return;
my @output;
my $rc; #in for testing, evaluated as a TODO
my $text;
my $error;
my $node;
my $subcommand = "";
if ($command eq "rvitals") {
($rc, @output) = vitals($subcommand);
}
elsif ($command eq "renergy") {
($rc, @output) = renergy($subcommand);
}
elsif ($command eq "rspreset") {
($rc, @output) = resetbmc();
$noclose = 1;
}
elsif ($command eq "reventlog") {
if ($subcommand eq "decodealert") {
($rc, $text) = decodealert(@_);
}
else {
($rc, @output) = eventlog($subcommand);
}
}
elsif ($command eq "rinv") {
($rc, @output) = inv($subcommand);
}
elsif ($command eq "fru") {
($rc, @output) = fru($subcommand);
}
elsif ($command eq "rgetnetinfo") {
my @subcommands = ($subcommand);
if ($subcommand eq "all") {
@subcommands = (
"ip",
"netmask",
"gateway",
"backupgateway",
"snmpdest1",
"snmpdest2",
"snmpdest3",
"snmpdest4",
"community",
);
my @coutput;
foreach (@subcommands) {
$subcommand = $_;
($rc, @output) = getnetinfo($subcommand);
push(@coutput, @output);
}
@output = @coutput;
}
else {
($rc, @output) = getnetinfo($subcommand);
}
}
elsif ($command eq "generic") {
($rc, @output) = generic($subcommand);
}
elsif ($command eq "rfrurewrite") {
($rc, @output) = writefru($subcommand, shift);
}
elsif ($command eq "fru") {
($rc, @output) = fru($subcommand);
}
elsif ($command eq "rsetboot") {
($rc, @output) = setboot($subcommand);
}
else {
$rc = 1;
$text = "unsupported command $command $subcommand";
}
if ($debug) {
print "$node: command completed\n";
}
if ($text) {
push(@output, $text);
}
return ($rc, @output);
}
sub resetbmc {
my $sessdata = shift;
$sessdata->{ipmisession}->subcmd(netfn => 6, command => 2, data => [], callback => \&resetedbmc, callback_args => $sessdata);
}
sub resetedbmc {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
} else {
if ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
xCAT::SvrUtils::sendmsg("BMC reset", $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{ipmisession} = undef; #throw away now unusable session
}
}
sub setpassword {
my $sessdata = shift;
my $subcmd = $sessdata->{subcommand};
my $netfun = 0x06;
my $command = undef;
my ($subcommand, $argument) = split(/=/, $subcmd);
if ($subcommand eq "userid") {
if ($argument !~ /^\d+$/) {
return (1, "The value for $subcommand is invalid");
}
$sessdata->{onuserid} = $argument;
$sessdata->{subcommand} = shift @{ $sessdata->{extraargs} };
if ($sessdata->{subcommand} and $sessdata->{subcommand} ne '') {
setpassword($sessdata);
}
} elsif ($subcommand eq "username") {
unless ($sessdata->{onuserid}) {
my @cmdargv = @{ $sessdata->{extraargs} };
if (grep /userid/, @cmdargv) {
foreach my $opt (@cmdargv) {
if ($opt =~ m/userid=\s*(\d+)/) {
$sessdata->{onuserid} = $1;
last;
} else {
return (1, "The value for $subcommand is invalid");
}
}
} else {
$sessdata->{onuserid} = '2'; # The default userid will be 2 if no userid specified
}
}
my @data = ();
$command = 0x45;
@data = unpack("C*", $argument);
if ($#data > 15 or $#data < 0) {
xCAT::SvrUtils::sendmsg([ 1, "The username is invalid" ], $callback, $sessdata->{node}, %allerrornodes);
return (1, "The username is invalid.");
}
$sessdata->{newusername} = $argument;
my $index = $#data;
while ($index < 15) { # Username must be padded with 0 to 16 bytes for IPMI 2.0
push @data, 0;
$index += 1;
}
unshift @data, $sessdata->{onuserid} - '0'; # byte 1, userID, User ID 1 is permanently associated with User 1, the null user name
unless (exists($sessdata->{setuseraccess})) {
$sessdata->{setuseraccess} = 1;
}
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@data, callback => \&password_set, callback_args => $sessdata);
} elsif ($subcommand eq "password") {
unless ($sessdata->{onuserid}) {
my @cmdargv = @{ $sessdata->{extraargs} };
if (grep /userid/, @cmdargv) {
foreach my $opt (@cmdargv) {
if ($opt =~ m/userid=\s*(\d+)/) {
$sessdata->{onuserid} = $1;
last;
} else {
return (1, "The value for $subcommand is invalid");
}
}
} else {
# If have username specified, if has been dealt will be store in newusername, else need to check the args array. And the default userid must be 2.
if (exists($sessdata->{newusername}) or (grep /username/, @{ $sessdata->{extraargs} })) {
$sessdata->{onuserid} = '2';
} else {
# If No username specified, the default userid will be 1.
$sessdata->{onuserid} = '1';
}
}
}
$command = 0x47;
my @data = ();
@data = unpack("C*", $argument);
if ($#data > 15 or $#data < 0) {
xCAT::SvrUtils::sendmsg([ 1, "The new password is invalid" ], $callback, $sessdata->{node}, %allerrornodes);
return (1, "The new password is invalid.");
}
$sessdata->{newpassword} = $argument;
my $index = $#data;
while ($index < 15) { # Password must be padded with 0 to 20 bytes for IPMI 2.0
push @data, 0;
$index += 1;
}
unless (exists($sessdata->{enableuserdone})) {
unshift @data, 0x01; # byte 2, operation: 0x00 disable user, 0x01 enable user, 0x02 set password, 0x03 test password
unshift @data, ($sessdata->{onuserid} - '0'); # byte 1, userID, User ID 1 is permanently associated with User 1, the null user name
$sessdata->{enableuserdone} = 1;
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@data, callback => \&password_set, callback_args => $sessdata);
} else {
unshift @data, 0x02; # byte 2, operation: 0x00 disable user, 0x01 enable user, 0x02 set password, 0x03 test password
unshift @data, ($sessdata->{onuserid} - '0'); # byte 1, userID, User ID 1 is permanently associated with User 1, the null user name
unless (exists($sessdata->{setuseraccess})) {
$sessdata->{setuseraccess} = 1;
}
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@data, callback => \&password_set, callback_args => $sessdata);
}
}
return;
}
sub password_set {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown ipmi error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
if ($sessdata->{setuseraccess} and $sessdata->{onuserid} ne '1' and $sessdata->{onuserid} ne '2') {
setuseraccess($sessdata);
return;
}
if ($sessdata->{enableuserdone}) {
$sessdata->{enableuserdone} = 0;
setpassword($sessdata);
return;
}
# Won't update ipmi table, since option username=<> and password=<> can be run Respectively
#my $ipmitab = xCAT::Table->new('ipmi');
#if (!$ipmitab) {
# xCAT::SvrUtils::sendmsg([1, "Failed to update ipmi table."],$callback,$sessdata->{node},%allerrornodes);
#} else {
# if ($sessdata->{subcommand} =~ m/username/) {
# $ipmitab->setNodeAttribs($sessdata->{node}, {'username'=>$sessdata->{newusername}});
# xCAT::SvrUtils::sendmsg("set username Done",$callback,$sessdata->{node},%allerrornodes);
# } elsif ($sessdata->{subcommand} =~ m/password/) {
# $ipmitab->setNodeAttribs($sessdata->{node}, {'password'=>$sessdata->{newpassword}});
# xCAT::SvrUtils::sendmsg("set password Done",$callback,$sessdata->{node},%allerrornodes);
# }
#}
# Give out the return complete MSG
if ($sessdata->{subcommand} =~ m/username/) {
xCAT::SvrUtils::sendmsg("set username Done", $callback, $sessdata->{node}, %allerrornodes);
} elsif ($sessdata->{subcommand} =~ m/password/) {
xCAT::SvrUtils::sendmsg("set password Done", $callback, $sessdata->{node}, %allerrornodes);
}
$sessdata->{subcommand} = shift @{ $sessdata->{extraargs} };
if ($sessdata->{subcommand} and $sessdata->{subcommand} ne '') {
setpassword($sessdata);
}
return;
}
sub setuseraccess {
my $sessdata = shift;
my $channel_number = $sessdata->{ipmisession}->{currentchannel};
unless ($sessdata->{onuserid}) {
return;
}
$sessdata->{setuseraccess} = 0;
my @data = ();
push @data, (0xd0 | $channel_number);
push @data, ($sessdata->{onuserid} - '0');
push @data, 0x04;
push @data, 0;
$sessdata->{ipmisession}->subcmd(netfn => 0x06, command => 0x43, data => \@data, callback => \&password_set, callback_args => $sessdata);
return;
}
sub next_setnetinfo {
my $rsp = shift;
my $sessdata = shift;
&setnetinfo($sessdata);
}
sub setnetinfo {
my $sessdata = shift;
my $subcommand = $sessdata->{subcommand};
my $argument;
($subcommand, $argument) = split(/=/, $subcommand);
my @input = @_;
my $netfun = 0x0c;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my $code;
my $match;
my $channel_number = $sessdata->{ipmisession}->{currentchannel};
if ($subcommand eq "snmpdest") {
$subcommand = "snmpdest1";
}
unless (defined($argument)) {
return 0;
}
if ($subcommand eq "thermprofile") {
return idpxthermprofile($argument);
}
if ($subcommand eq "alert" and $argument eq "on" or $argument =~ /^en/ or $argument =~ /^enable/) {
$netfun = 0x4;
@cmd = (0x12, 0x9, 0x1, 0x18, 0x11, 0x00);
} elsif ($subcommand eq "alert" and $argument eq "off" or $argument =~ /^dis/ or $argument =~ /^disable/) {
$netfun = 0x4;
@cmd = (0x12, 0x9, 0x1, 0x10, 0x11, 0x00);
}
elsif ($subcommand eq "garp") {
my $halfsec = $argument * 2; #pop(@input) * 2;
if ($halfsec > 255) {
$halfsec = 255;
}
if ($halfsec < 4) {
$halfsec = 4;
}
@cmd = (0x01, $channel_number, 0x0b, $halfsec);
}
elsif ($subcommand =~ m/community/) {
my $cindex = 0;
my @clist;
foreach (0 .. 17) {
push @clist, 0;
}
foreach (split //, $argument) {
$clist[ $cindex++ ] = ord($_);
}
@cmd = (1, $channel_number, 0x10, @clist);
}
elsif ($subcommand =~ m/snmpdest(\d+)/) {
my $dstip = $argument; #pop(@input);
$dstip = inet_ntoa(inet_aton($dstip));
my @dip = split /\./, $dstip;
@cmd = (0x01, $channel_number, 0x13, $1, 0x00, 0x00, $dip[0], $dip[1], $dip[2], $dip[3], 0, 0, 0, 0, 0, 0);
} elsif ($subcommand =~ m/netmask/) {
if ($argument =~ /\./) {
my @mask = split /\./, $argument;
foreach (0 .. 3) {
$mask[$_] = $mask[$_] + 0;
}
@cmd = (0x01, $channel_number, 0x6, @mask);
}
} elsif ($subcommand =~ m/gateway/) {
my $gw = inet_ntoa(inet_aton($argument));
my @mask = split /\./, $gw;
foreach (0 .. 3) {
$mask[$_] = $mask[$_] + 0;
}
@cmd = (0x01, $channel_number, 12, @mask);
} elsif ($subcommand =~ m/vlan/) {
unless ( int($argument) == $argument ) {
$callback->({ errorcode => [1], error => ["The input $argument is invalid, please input an integer"] });
return;
}
unless (($argument>=1) && ($argument<=4096)) {
$callback->({ errorcode => [1], error => ["The input $argument is invalid, valid value [1-4096]"] });
return;
}
my @vlanparam = ();
$vlanparam[0] = ($argument & 0xff);
$vlanparam[1] = 0x80 | (($argument & 0xf00) >> 8);
@cmd = (0x01, $channel_number, 0x14, @vlanparam);
} elsif ($subcommand =~ m/ip/ and $argument =~ m/dhcp/) {
@cmd = (0x01, $channel_number, 0x4, 0x2);
} elsif ($subcommand =~ m/ip/) {
# Need to set ipsrc to static for IBM POWER S822LC and S812LC
if (exists($sessdata->{netinfo_setinprogress})) {
unless ($sessdata->{set_ipsrc_static}) {
$sessdata->{set_ipsrc_static} = 1;
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => 0x01, data => [ $channel_number, 0x04, 0x1 ], callback => \&next_setnetinfo, callback_args => $sessdata);
return;
}
}
my $mip = inet_ntoa(inet_aton($argument));
my @mask = split /\./, $mip;
foreach (0 .. 3) {
$mask[$_] = $mask[$_] + 0;
}
@cmd = (0x01, $channel_number, 0x3, @mask);
}
#elsif($subcommand eq "alert" ) {
# my $action=pop(@input);
# # print "action=$action\n";
# $netfun=0x28; #TODO: not right
# # mapping alert action to number
# my $act_number=8;
# if ($action eq "on") {$act_number=8;}
# elsif ($action eq "off") { $act_number=0;}
# else { return(1,"unsupported alert action $action");}
# @cmd = (0x12, $channel_number,0x09, 0x01, $act_number+16, 0x11,0x00);
#}
else {
return (1, "configuration of $subcommand is not implemented currently");
}
unless ($sessdata->{netinfo_setinprogress}) {
$sessdata->{netinfo_setinprogress} = '1';
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => 0x01, data => [ $channel_number, 0x0, 0x1 ], callback => \&next_setnetinfo, callback_args => $sessdata);
return;
}
my $command = shift @cmd;
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@cmd, callback => \&netinfo_set, callback_args => $sessdata);
}
sub netinfo_set {
my $rsp = shift;
my $sessdata = shift;
if ($sessdata->{netinfo_setinprogress}) {
my $channel_number = $sessdata->{ipmisession}->{currentchannel};
delete $sessdata->{netinfo_setinprogress};
$sessdata->{rsp}->{error} = $rsp->{error};
$sessdata->{rsp}->{code} = $rsp->{code};
$sessdata->{ipmisession}->subcmd(netfn => 0x0c, command => 0x01, data => [ $channel_number, 0x0, 0x0 ], callback => \&netinfo_set, callback_args => $sessdata);
} else {
$rsp = $sessdata->{rsp};
}
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown ipmi error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
if ($sessdata->{subcommand} =~ m/^ip=dhcp/) {
$sessdata->{subcommand} = shift @{ $sessdata->{extraargs} };
if (!defined($sessdata->{subcommand}) or $sessdata->{subcommand} eq '') {
return;
}
}
getnetinfo($sessdata);
return;
}
sub getnetinfo {
my $sessdata = shift;
my $subcommand = $sessdata->{subcommand};
my $channel_number = $sessdata->{ipmisession}->{currentchannel};
$subcommand =~ s/=.*//;
if ($subcommand eq "thermprofile") {
my $code;
my @returnd;
my $thermdata;
my $netfun = 0x2e << 2; #currently combined netfun & lun, to be simplified later
my @cmd = (0x41, 0x4d, 0x4f, 0x00, 0x6f, 0xff, 0x61, 0x00);
my @bytes;
my $error = docmd($netfun, \@cmd, \@bytes);
@bytes = splice @bytes, 16;
my $validprofiles = "";
foreach (keys %idpxthermprofiles) {
if (sprintf("%02x %02x %02x %02x %02x %02x %02x", @bytes) eq sprintf("%02x %02x %02x %02x %02x %02x %02x", @{ $idpxthermprofiles{$_} })) {
$validprofiles .= "$_,";
}
}
if ($validprofiles) {
chop($validprofiles);
return (0, "The following thermal profiles are in effect: " . $validprofiles);
}
return (1, sprintf("Unable to identify current thermal profile: \"%02x %02x %02x %02x %02x %02x %02x\"", @bytes));
}
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my $code;
if ($subcommand eq "snmpdest") {
$subcommand = "snmpdest1";
}
my $netfun = 0x0c;
if ($subcommand eq "alert") {
$netfun = 0x4;
@cmd = (0x13, 9, 1, 0);
}
elsif ($subcommand eq "garp") {
@cmd = (0x02, $channel_number, 0x0b, 0x00, 0x00);
}
elsif ($subcommand =~ m/^snmpdest(\d+)/) {
@cmd = (0x02, $channel_number, 0x13, $1, 0x00);
}
elsif ($subcommand eq "vlan") {
@cmd = (0x02, $channel_number, 0x14, 0x00, 0x00);
}
elsif ($subcommand eq "ip") {
@cmd = (0x02, $channel_number, 0x03, 0x00, 0x00);
}
elsif ($subcommand eq "netmask") {
@cmd = (0x02, $channel_number, 0x06, 0x00, 0x00);
}
elsif ($subcommand eq "gateway") {
@cmd = (0x02, $channel_number, 0x0C, 0x00, 0x00);
}
elsif ($subcommand eq "backupgateway") {
@cmd = (0x02, $channel_number, 0x0E, 0x00, 0x00);
}
elsif ($subcommand eq "community") {
@cmd = (0x02, $channel_number, 0x10, 0x00, 0x00);
}
else {
return (1, "unsupported command getnetinfo $subcommand");
}
my $command = shift @cmd;
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@cmd, callback => \&getnetinfo_response, callback_args => $sessdata);
}
sub getnetinfo_response {
my $rsp = shift;
my $sessdata = shift;
my $subcommand = $sessdata->{subcommand};
$subcommand =~ s/=.*//;
$sessdata->{subcommand} = shift @{ $sessdata->{extraargs} };
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown ipmi error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
if ($subcommand eq "snmpdest") {
$subcommand = "snmpdest1";
}
my $bmcifo = "";
if ($sessdata->{bmcnum} != 1) { $bmcifo .= " on BMC " . $sessdata->{bmcnum}; }
my @returnd = (0, @{ $rsp->{data} });
my $format = "%-25s";
if ($subcommand eq "garp") {
my $code = $returnd[2] / 2;
xCAT::SvrUtils::sendmsg(sprintf("$format %d", "Gratuitous ARP seconds:", $code), $callback, $sessdata->{node}, %allerrornodes);
}
elsif ($subcommand eq "alert") {
if ($returnd[3] & 0x8) {
xCAT::SvrUtils::sendmsg("SP Alerting: enabled" . $bmcifo, $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg("SP Alerting: disabled" . $bmcifo, $callback, $sessdata->{node}, %allerrornodes);
}
}
elsif ($subcommand =~ m/^snmpdest(\d+)/) {
xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d" . $bmcifo,
"SP SNMP Destination $1:",
$returnd[5],
$returnd[6],
$returnd[7],
$returnd[8]), $callback, $sessdata->{node}, %allerrornodes);
} elsif ($subcommand eq "vlan") {
if (($returnd[3] >> 7) & 0x01) {
xCAT::SvrUtils::sendmsg(sprintf("$format %d" . $bmcifo,
"BMC VLAN ID enabled:",
(($returnd[3] & 0x0f) << 8) | $returnd[2]), $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg("BMC VLAN disabled" . $bmcifo, $callback, $sessdata->{node}, %allerrornodes);
}
} elsif ($subcommand eq "ip") {
xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d" . $bmcifo,
"BMC IP:",
$returnd[2],
$returnd[3],
$returnd[4],
$returnd[5]), $callback, $sessdata->{node}, %allerrornodes);
} elsif ($subcommand eq "netmask") {
xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d" . $bmcifo,
"BMC Netmask:",
$returnd[2],
$returnd[3],
$returnd[4],
$returnd[5]), $callback, $sessdata->{node}, %allerrornodes);
} elsif ($subcommand eq "gateway") {
xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d" . $bmcifo,
"BMC Gateway:",
$returnd[2],
$returnd[3],
$returnd[4],
$returnd[5]), $callback, $sessdata->{node}, %allerrornodes);
} elsif ($subcommand eq "backupgateway") {
xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d" . $bmcifo,
"BMC Backup Gateway:",
$returnd[2],
$returnd[3],
$returnd[4],
$returnd[5]), $callback, $sessdata->{node}, %allerrornodes);
} elsif ($subcommand eq "community") {
my $text = sprintf("$format ", "SP SNMP Community:");
my $l = 2;
while ($returnd[$l] ne 0) {
$l = $l + 1;
}
my $i = 2;
while ($i < $l) {
$text = $text . sprintf("%c", $returnd[$i]);
$i = $i + 1;
}
$text .= $bmcifo;
xCAT::SvrUtils::sendmsg($text, $callback, $sessdata->{node}, %allerrornodes);
}
if ($sessdata->{subcommand}) {
if ($sessdata->{subcommand} =~ /=/) {
setnetinfo($sessdata);
} else {
getnetinfo($sessdata);
}
}
return;
}
sub setboot {
my $sessdata = shift;
#This disables the 60 second timer
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 8, data => [ 3, 8 ], callback => \&setboot_timerdisabled, callback_args => $sessdata);
}
sub setboot_timerdisabled {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
return;
} elsif ($rsp->{code} == 0x80) {
#xCAT::SvrUtils::sendmsg("Unable to disable countdown timer, boot device may revert in 60 seconds",$callback,$sessdata->{node},%allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown ipmi error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
}
my $subcommand = undef;
if (@{ $sessdata->{extraargs} }) {
$subcommand = @{ $sessdata->{extraargs} }[0];
}
if (!defined($subcommand) or $subcommand =~ m/^stat/) {
setboot_stat("NOQUERY", $sessdata);
return;
} elsif ($subcommand !~ /net|hd|cd|floppy|def|setup/) {
xCAT::SvrUtils::sendmsg([ 1, "unsupported command setboot $subcommand" ], $callback, $sessdata->{node}, %allerrornodes);
} else {
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 8, data => [ 0, 1 ], callback => \&setboot_setstart, callback_args => $sessdata);
}
}
sub setboot_setstart {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
return;
} elsif ($rsp->{code} == 0x80) {
xCAT::SvrUtils::sendmsg("Unable to set boot param", $callback, $sessdata->{node}, %allerrornodes);
return;
} elsif ($rsp->{code} == 0x81) {
xCAT::SvrUtils::sendmsg("Attemp to set the 'set in process' value", $callback, $sessdata->{node}, %allerrornodes);
}
}
my $error;
@ARGV = @{ $sessdata->{extraargs} };
my $persistent = 0;
my $uefi = 0;
use Getopt::Long;
unless (GetOptions(
'p' => \$persistent,
'u' => \$uefi,
)) {
xCAT::SvrUtils::sendmsg([ 1, "Error parsing arguments" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $subcommand = shift @ARGV;
my @cmd;
my $overbootflags = 0x80 | $persistent << 6 | $uefi << 5;
if ($subcommand eq "net") {
@cmd = (0x5, $overbootflags, 0x4, 0x0, 0x0, 0x0);
}
elsif ($subcommand eq "hd") {
@cmd = (0x5, $overbootflags, 0x8, 0x0, 0x0, 0x0);
}
elsif ($subcommand eq "cd") {
@cmd = (0x5, $overbootflags, 0x14, 0x0, 0x0, 0x0);
}
elsif ($subcommand eq "floppy") {
@cmd = (0x5, $overbootflags, 0x3c, 0x0, 0x0, 0x0);
}
elsif ($subcommand =~ m/^def/) {
@cmd = (0x5, 0x0, 0x0, 0x0, 0x0, 0x0);
}
elsif ($subcommand eq "setup") { #Not supported by BMCs I've checked so far..
@cmd = (0x5, $overbootflags, 0x18, 0x0, 0x0, 0x0);
}
elsif ($subcommand =~ m/^stat/) {
setboot_stat("NOQUERY", $sessdata);
return;
}
else {
xCAT::SvrUtils::sendmsg([ 1, "unsupported command setboot $subcommand" ], $callback, $sessdata->{node}, %allerrornodes);
}
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 8, data => \@cmd, callback => \&setboot_done, callback_args => $sessdata);
}
sub setboot_done {
my $rsp = shift;
my $sessdata = shift;
if (ref $rsp) {
if ($rsp->{error}) { xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes); }
elsif ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("setboot_stat Unknown ipmi error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
}
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 8, data => [ 0, 0 ], callback => \&setboot_stat, callback_args => $sessdata);
}
sub setboot_stat {
my $rsp = shift;
my $sessdata = shift;
if (ref $rsp) {
if ($rsp->{error}) { xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes); }
elsif ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown ipmi error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
}
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 9, data => [ 5, 0, 0 ], callback => \&setboot_gotstat, callback_args => $sessdata);
}
sub setboot_gotstat {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) { xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes); }
elsif ($rsp->{code}) {
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown ipmi error %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
my %bootchoices = (
0 => 'BIOS default',
1 => 'Network',
2 => 'Hard Drive',
5 => 'CD/DVD',
6 => 'BIOS Setup',
15 => 'Floppy'
);
my @returnd = ($rsp->{code}, @{ $rsp->{data} });
unless ($returnd[3] & 0x80) {
xCAT::SvrUtils::sendmsg("boot override inactive", $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $boot = ($returnd[4] & 0x3C) >> 2;
xCAT::SvrUtils::sendmsg($bootchoices{$boot}, $callback, $sessdata->{node}, %allerrornodes);
return;
}
sub idpxthermprofile {
#iDataplex thermal profiles as of 6/10/2008
my $subcommand = lc(shift);
my @returnd;
my $netfun = 0xb8;
my @cmd = (0x41, 0x4d, 0x4f, 0x00, 0x6f, 0xfe, 0x60, 0, 0, 0, 0, 0, 0, 0, 0xff);
if ($idpxthermprofiles{$subcommand}) {
push @cmd, @{ $idpxthermprofiles{$subcommand} };
} else {
return (1, "Not an understood thermal profile, expected a 2 hex digit value corresponding to chassis label on iDataplex server");
}
docmd(
$netfun,
\@cmd,
\@returnd
);
return (0, "OK");
}
sub is_systemx {
my $sessdata = shift;
return ($sessdata->{mfg_id} == 20301 or $sessdata->{mfg_id} == 2 or $sessdata->{mfg_id} == 19046);
}
sub getrvidparms {
my $sessdata = shift;
unless ($sessdata) { die "not fixed yet" }
#check devide id
if ($sessdata->{mfg_id} == 20301 and $sessdata->{prod_id} == 220) {
my $browser = LWP::UserAgent->new();
my $message = "WEBVAR_USERNAME=" . $sessdata->{ipmisession}->{userid} . "&WEBVAR_PASSWORD=" . $sessdata->{ipmisession}->{password};
$browser->cookie_jar({});
my $baseurl = "https://" . $sessdata->{ipmisession}->{bmc} . "/";
my $response = $browser->request(GET $baseurl. "rpc/WEBSES/validate.asp");
$response = $browser->request(POST $baseurl. "rpc/WEBSES/create.asp", 'Content-Type' => "application/x-www-form-urlencoded", Content => $message);
$response = $response->content;
if ($response and $response =~ /SESSION_COOKIE' : '([^']*)'/) {
foreach (keys %{ $browser->cookie_jar->{COOKIES} }) {
$browser->cookie_jar()->set_cookie(1, "SessionCookie", $1, "/", $_);
}
}
$response = $browser->request(GET $baseurl. "/Java/jviewer.jnlp?ext_ip=" . $sessdata->{ipmisession}->{bmc});
$response = $response->content;
xCAT::SvrUtils::sendmsg("method:imm", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("jnlp:$response", $callback, $sessdata->{node}, %allerrornodes);
return;
}
unless ($sessdata->{mfg_id} == 2 or $sessdata->{mfg_id} == 01) { #Only implemented for IBM servers
xCAT::SvrUtils::sendmsg([ 1, "Remote video is not supported on this system" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
#TODO: use get bmc capabilities to see if rvid is actually supported before bothering the client java app
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x50, data => [], callback => \&getrvidparms_with_buildid, callback_args => $sessdata);
}
sub check_rsp_errors { #TODO: pass in command-specfic error code translation table
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) { #non ipmi error
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return 1;
}
if ($rsp->{code}) { #ipmi error
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown error code %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return 1;
}
return 0;
}
sub getrvidparms_imm2 {
my $rsp = shift;
my $sessdata = shift;
#wvid should be a possiblity, time to do the http...
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; #TODO: for standalone IMMs, automate CSR retrieval and granting at setup itme
#for flex, grab the CA from each CMM and store in a way accessible to this command
#for now, accept the MITM risk no worse than http, the intent being feature parity
#with http: for envs with https only, like Flex
my $browser = LWP::UserAgent->new();
if ($sessdata->{ipmisession}->{bmc} =~ /^fe80/ and $sessdata->{ipmisession}->{bmc} =~ /%/) {
xCAT::SvrUtils::sendmsg([ 1, "wvid not supported with IPv6 LLA addressing mode" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $host = $sessdata->{ipmisession}->{bmc};
my $hostname;
($hostname, $host) = xCAT::NetworkUtils->gethostnameandip($host);
my $ip6mode = 0;
if ($host =~ /:/) { $ip6mode = 1; $host = "[" . $host . "]"; }
my $message = "user=" . $sessdata->{ipmisession}->{userid} . "&password=" . $sessdata->{ipmisession}->{password} . "&SessionTimeout=1200";
$browser->cookie_jar({});
my $httpport = 443;
my $baseurl = "https://$host/";
my $response = $browser->request(POST $baseurl. "data/login", Referer => "https://$host/designs/imm/index.php", 'Content-Type' => "application/x-www-form-urlencoded", Content => $message);
if ($response->code == 500) {
$httpport = 80;
$baseurl = "http://$host/";
$response = $browser->request(POST $baseurl. "data/login", Referer => "http://$host/designs/imm/index.php", 'Content-Type' => "application/x-www-form-urlencoded", Content => $message);
}
my $sessionid;
unless ($response->content =~ /\"ok\"?(.*)/ and $response->content =~ /\"authResult\":\"0\"/) {
xCAT::SvrUtils::sendmsg([ 1, "Server returned unexpected data" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
$response = $browser->request(GET $baseurl. "/designs/imm/remote-control.php");
if ($response->content =~ /isRPInstalled\s*=\s*'0'/) {
xCAT::SvrUtils::sendmsg([ 1, "Node does not have feature key for remote video" ], $callback, $sessdata->{node}, %allerrornodes);
$response = $browser->request(GET $baseurl. "data/logout");
return;
}
$response = $browser->request(GET $baseurl. "designs/imm/viewer(" . $host . '@' . $ip6mode . '@' . time() . '@1@0@1@jnlp)');
#arguments are host, then ipv6 or not, then timestamp, then whether to encrypte or not, singleusermode, finally 'notwin32'
my $jnlp = $response->content;
unless ($jnlp) { #ok, might be the newer syntax...
$response = $browser->request(GET $baseurl. "designs/imm/viewer(" . $host . '@' . $httpport . '@' . $ip6mode . '@' . time() . '@1@0@1@jnlp' . '@USERID@0@0@0@0' . ')');
#arguments are host, then ipv6 or not, then timestamp, then whether to encrypte or not, singleusermode, finally 'notwin32'
$jnlp = $response->content;
if ($jnlp =~ /Failed to parse ip format for request/) {
$response = $browser->request(GET $baseurl. "designs/imm/viewer(" . $host . '@' . $httpport . '@' . $ip6mode . '@' . time() . '@1@0@1@jnlp' . '@USERID@0@0@0@0@0' . ')');
#arguments are host, then ipv6 or not, then timestamp, then whether to encrypte or not, singleusermode, 'notwin32', and one more (unknown)
$jnlp = $response->content;
}
}
$response = $browser->request(GET $baseurl. "data/logout");
my $currnode = $sessdata->{node};
$jnlp =~ s!argument>title=.*Video Viewer</argument>!argument>title=$currnode wvid</argument>!;
xCAT::SvrUtils::sendmsg("method:imm", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("jnlp:$jnlp", $callback, $sessdata->{node}, %allerrornodes);
}
sub getrvidparms_with_buildid {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
my @build_id = (0, @{ $rsp->{data} });
if ($build_id[1] == 0x54 and $build_id[2] == 0x43 and $build_id[3] == 0x4f and $build_id[4] == 0x4f) { ##Lenovo IMM2
return getrvidparms_imm2($rsp, $sessdata);
}
if ($build_id[1] == 0x31 and $build_id[2] == 0x41 and $build_id[3] == 0x4f and $build_id[4] == 0x4f) { #Only know how to cope with yuoo builds
return getrvidparms_imm2($rsp, $sessdata);
}
unless ($build_id[1] == 0x59 and $build_id[2] == 0x55 and $build_id[3] == 0x4f and $build_id[4] == 0x4f) { #Only know how to cope with yuoo builds
xCAT::SvrUtils::sendmsg([ 1, "Remote video is not supported on this system" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
#wvid should be a possiblity, time to do the http...
my $browser = LWP::UserAgent->new();
my $message = $sessdata->{ipmisession}->{userid} . "," . $sessdata->{ipmisession}->{password};
$browser->cookie_jar({});
my $baseurl = "http://" . $sessdata->{ipmisession}->{bmc} . "/";
my $response = $browser->request(POST $baseurl. "/session/create", 'Content-Type' => "text/xml", Content => $message);
my $sessionid;
if ($response->content =~ /^ok:?(.*)/) {
$sessionid = $1;
} else {
xCAT::SvrUtils::sendmsg([ 1, "Server returned unexpected data" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
$response = $browser->request(GET $baseurl. "/page/session.html"); #we don't care, but some firmware is confused if we don't
if ($sessionid) {
$response = $browser->request(GET $baseurl. "/kvm/kvm/jnlp?session_id=$sessionid");
} else {
$response = $browser->request(GET $baseurl. "/kvm/kvm/jnlp");
}
my $jnlp = $response->content;
if ($jnlp =~ /This advanced option requires the purchase and installation/) {
xCAT::SvrUtils::sendmsg([ 1, "Node does not have feature key for remote video" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $currnode = $sessdata->{node};
$jnlp =~ s!argument>title=.*Video Viewer</argument>!argument>title=$currnode wvid</argument>!;
xCAT::SvrUtils::sendmsg("method:imm", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("jnlp:$jnlp", $callback, $sessdata->{node}, %allerrornodes);
my @cmdargv = @{ $sessdata->{extraargs} };
if (grep /-m/, @cmdargv) {
if ($sessionid) {
$response = $browser->request(GET $baseurl. "/kvm/vm/jnlp?session_id=$sessionid");
} else {
$response = $browser->request(GET $baseurl. "/kvm/vm/jnlp");
}
xCAT::SvrUtils::sendmsg("mediajnlp:" . $response->content, $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
sub ripmi { #implement generic raw ipmi commands
my $sessdata = shift;
my $netfun = hex(shift @{ $sessdata->{extraargs} });
my $command = hex(shift @{ $sessdata->{extraargs} });
my @data;
foreach (@{ $sessdata->{extraargs} }) {
push @data, hex($_);
}
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@data, callback => \&ripmi_callback, callback_args => $sessdata);
}
sub ripmi_callback {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $output = sprintf("%02X " x (scalar(@{ $rsp->{data} })), @{ $rsp->{data} });
xCAT::SvrUtils::sendmsg($output, $callback, $sessdata->{node}, %allerrornodes);
}
sub isfpc {
my $sessdata = shift;
return 1
}
sub isopenpower {
my $sessdata = shift;
if ($sessdata->{prod_id} == 43707 and $sessdata->{mfg_id} == 0) {
# mft_id 0 and prod_id 43707 is for Firestone,Minsky
return 1;
} elsif (($sessdata->{prod_id} =~ /0|2355|2437/) and $sessdata->{mfg_id} == 10876) {
# mfg_id 10876 is for IBM Power S822LC for Big Data (Supermicro), prod_id 2355 for B&S, and 0 or 2437 for Boston
return 1;
} else {
return 0;
}
}
sub check_firmware_version {
sub _on_receive_ugp {
my $rsp = shift;
my $sessdta = shift;
shift @{ $rsp->{data} };
return;
}
sub _on_receive_version {
my $rsp = shift;
my $sessdata = shift;
shift @{ $rsp->{data} };
my $c_id = $sessdata->{c_id};
$sessdata->{$c_id} = $rsp->{data};
}
sub _on_receive_component_name {
my $rsp = shift;
my $sessdata = shift;
shift @{ $rsp->{data} };
my $c_id = $sessdata->{c_id};
# Convert ASCII to char
my $format_string = "";
my $ascii_data = $rsp->{data};
for my $i (@${ascii_data}) {
if ($i != 0) {
$format_string = $format_string . chr($i);
}
}
$sessdata->{$c_id} = $format_string;
}
my $sessdata = shift;
my $firmware_version = shift;
my $component_string = shift;
# GET TARGET UPGRADE CAPABILITIES
$sessdata->{ipmisession}->subcmd(netfn => 0x2c, command => 0x2e,
data => [ 0 ],
callback => \&_on_receive_ugp,
callback_args => $sessdata);
while (xCAT::IPMI->waitforrsp()) { yield }
foreach my $c_id (@{ $sessdata->{component_ids} }) {
$sessdata->{c_id} = $c_id;
# Get component property 1 - Current firmware version
$sessdata->{ipmisession}->subcmd(netfn => 0x2c, command => 0x2f,
data => [ 0, $c_id / 2, 1 ],
callback => \&_on_receive_version,
callback_args => $sessdata);
while (xCAT::IPMI->waitforrsp()) { yield }
$firmware_version->{$c_id} = $sessdata->{$c_id};
# Get component property 2 - Component description string
$sessdata->{ipmisession}->subcmd(netfn => 0x2c, command => 0x2f,
data => [ 0, $c_id / 2, 2 ],
callback => \&_on_receive_component_name,
callback_args => $sessdata);
while (xCAT::IPMI->waitforrsp()) { yield }
$component_string->{$c_id} = $sessdata->{$c_id};
}
}
sub get_ipmitool_version {
my $version_ptr = shift;
my $cmd = "$IPMIXCAT -V";
my $output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$callback->({ error => "Running ipmitool command failed. Error Code: $::RUNCMD_RC",
errorcode => 1 });
return -1;
}
$$version_ptr = (split(/ /, $output))[2];
return 0;
}
#----------------------------------------------------------------#
# Calculate IPMItool Version format:
# Example : 1.8.15 --> 1* 1 0000 0000 + 8 * 0000 0000 + 15
#----------------------------------------------------------------#
sub calc_ipmitool_version {
my $ver_str = shift;
my @vers = split(/\./, $ver_str);
return ord($vers[2]) + ord($vers[1]) * 10000 + ord($vers[0]) * 100000000;
}
#----------------------------------------------------------------#
# Check bmc status:
# Arguments:
# pre_cmd: A string prep cmd like ipmitool-xcat -H
# <bmc_addr> -I lan -U <bmc_userid> -P
# <bmc_password>
# inteval: inteval time to check
# retry: max retry time
# zz_retry: minimum number of 00 to receive before exiting
# sessdata: session data for display
# verbose: verbose output
# use_rc: Some machines, like IBM Power S822LC for Big Data (Supermicro), do not return
# a "00" ready string from calling ipmi raw command. Instead they return RC=0. This
# flag if 1, tells the check_bmc_status_with_ipmitool() to check the RC
# instead of "00" string. If set to 0, the "00" ready string is checked.
# Returns:
# 1 when bmc is up
# 0 when no response from bmc
#----------------------------------------------------------------#
sub check_bmc_status_with_ipmitool {
my $pre_cmd = shift;
my $interval = shift;
my $retry = shift;
my $zz_retry = shift;
my $sessdata = shift;
my $verbose = shift;
my $use_rc = shift;
my $count = 0;
my $zz_count = 0;
my $bmc_response = 0;
my $cmd = $pre_cmd . " raw 0x3a 0x0a";
my $bmc_ready_code = "00";
my $code_type = "ready";
if ($use_rc) {
$cmd = $cmd . " >> /dev/null 2>&1 ; echo \$?";
$bmc_ready_code = "0";
$code_type = "return";
}
# BMC response of " c0" means BMC still running IPL
# BMC response of " 00" means ready to flash
#
# Under certain conditions is it necessary to make sure BMC ready code 00 gets
# returned for several seconds. zz_retry argument is used to control how many iterations
# in the row 00 code is returned. This counter is reset if some other, non 00 code
# interrupts it.
while ($count < $retry) {
$bmc_response = xCAT::Utils->runcmd($cmd, -1);
if ($bmc_response =~ /$bmc_ready_code/) {
if ($zz_count > $zz_retry) {
# zero-zero ready code was received for $zz_count iterations - good to exit
if ($verbose) {
xCAT::SvrUtils::sendmsg("Received BMC $code_type code $bmc_ready_code for $zz_count iterations - BMC is ready.", $callback, $sessdata->{node}, %allerrornodes);
}
return 1;
}
else {
# check to make sure zero-zero is received again
if ($verbose) {
xCAT::SvrUtils::sendmsg("($zz_count) BMC $code_type code - $bmc_ready_code", $callback, $sessdata->{node}, %allerrornodes);
}
$zz_count++;
}
}
else {
if ($zz_count > 0) {
# zero-zero was received before, but now we get something else.
# reset the zero-zero counter to make sure we get $zz_count iterations of zero-zero
if ($verbose) {
xCAT::SvrUtils::sendmsg("Resetting counter because BMC $code_type code - $bmc_response", $callback, $sessdata->{node}, %allerrornodes);
}
$zz_count = 0;
}
}
sleep($interval);
$count++;
}
if ($verbose) {
xCAT::SvrUtils::sendmsg("Never received $code_type code $bmc_ready_code after $count retries - BMC not ready.", $callback, $sessdata->{node}, %allerrornodes);
}
return 0;
}
#----------------------------------------------------------------#
# Wait for OS to reboot by checking "nodestat" to be "sshd" :
# Arguments:
# initial_sleep: seconds to sleep before checking loop start
# inteval: inteval time to check
# retry: max retry time
# sessdata: session data for display
# verbose: verbose output
# Returns:
# 1 when OS is up
# 0 when no response of "sshd" from OS
#----------------------------------------------------------------#
sub wait_for_os_to_reboot {
my $initial_sleep = shift;
my $interval = shift;
my $retry = shift;
my $sessdata = shift;
my $verbose = shift;
my $cmd;
my $output;
if ($verbose) {
xCAT::SvrUtils::sendmsg("Sleeping for a few min waiting for node to power on before attempting to continue", $callback, $sessdata->{node}, %allerrornodes);
}
sleep($initial_sleep); # sleep initially for $initial_sleep seconds for node to reboot
# Start testing every $interval sec for node to be booted. Give up after $retry times.
foreach (1..$retry) {
# Test node is booted in to OS
$cmd = "nodestat $sessdata->{node} | /usr/bin/grep sshd";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC == 0) {
# Node is ready to retry an upgrage
if ($verbose) {
xCAT::SvrUtils::sendmsg("Detected node booted. Will retry upgrade", $callback, $sessdata->{node}, %allerrornodes);
}
return 1; #Node booted
}
else {
# Still not booted, wait for $interval sec and try again
if ($verbose) {
$cmd = "nodestat $sessdata->{node}";
$output = xCAT::Utils->runcmd($cmd, -1);
my ($nodename, $state) = split(/:/, $output);
xCAT::SvrUtils::sendmsg("($_) Node still not ready. Current state - $state, test again in $interval sec.", $callback, $sessdata->{node}, %allerrornodes);
}
sleep($interval);
}
}
return 0; #Node did not boot after requested delay
}
sub do_firmware_update {
my $sessdata = shift;
my $ret;
my $ipmitool_ver;
my $verbose = 0;
my $retry = 2;
my $verbose_opt;
my $is_firestone = 0;
my $firestone_update_version;
my $htm_update_version;
my $pUpdate_directory;
$ret = get_ipmitool_version(\$ipmitool_ver);
exit $ret if $ret < 0;
my $exit_with_error_func = sub {
my ($node, $callback, $message) = @_;
my $status = "failed to update firmware";
my $nodelist_table = xCAT::Table->new('nodelist');
if (!$nodelist_table) {
xCAT::MsgUtils->message("S", "Unable to open nodelist table, denying");
} else {
$nodelist_table->setNodeAttribs($node, { status => $status });
$nodelist_table->close();
}
xCAT::MsgUtils->message("S", $node.": ".$message);
$callback->({ error => "$node: $message", errorcode => 1 });
exit -1;
};
my $exit_with_success_func = sub {
my ($node, $callback, $message) = @_;
my $status = "success updating firmware";
my $nodelist_table = xCAT::Table->new('nodelist');
if (!$nodelist_table) {
xCAT::MsgUtils->message("S", "Unable to open nodelist table, denying");
} else {
$nodelist_table->setNodeAttribs($node, { status => $status });
$nodelist_table->close();
}
xCAT::MsgUtils->message("S", $node.": ".$message);
$callback->({ data => "$node: $message" });
exit 0;
};
# only 1.8.15 or above support hpm update for firestone machines.
if (calc_ipmitool_version($ipmitool_ver) < calc_ipmitool_version("1.8.15")) {
$callback->({ error => "IPMITool $ipmitool_ver do not support firmware update for " .
"firestone machines, please setup IPMITool 1.8.15 or above.",
errorcode => 1 });
exit -1;
}
my $output;
my $bmc_addr = $sessdata->{ipmisession}->{bmc};
my $bmc_userid = $sessdata->{ipmisession}->{userid};
my $bmc_password = undef;
if (defined($sessdata->{ipmisession}->{password})) {
$bmc_password = $sessdata->{ipmisession}->{password};
}
my $hpm_file = $sessdata->{extraargs}->[0];
if ($hpm_file !~ /^\//) {
$hpm_file = xCAT::Utils->full_path($hpm_file, $::cwd);
}
my $rflash_log_file = xCAT::Utils->full_path($sessdata->{node}.".log", RFLASH_LOG_DIR);
# NOTE (chenglch) lanplus should be used for the task of hpm update
# which indicate the bmc support ipmi protocol version 2.0.
my $pre_cmd = "$IPMIXCAT -H $bmc_addr -I lanplus -U $bmc_userid";
if ($bmc_password) {
$pre_cmd = $pre_cmd . " -P $bmc_password";
}
# check for 8335-GTB Model Type to adjust buffer size
my $buffer_size = "30000";
my $cmd = $pre_cmd . " fru print 3";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
if ($output =~ /8335-GTB/) {
$buffer_size = "15000";
}
my $directory_name;
if (@{ $sessdata->{extraargs} } > 1) {
@ARGV = @{ $sessdata->{extraargs} };
use Getopt::Long;
GetOptions('d:s' => \$directory_name);
}
# check verbose, buffersize, and retry options
for my $opt (@{$sessdata->{'extraargs'}}) {
if ($opt =~ /-V{1,4}/) {
$verbose_opt = lc($opt);
$verbose = 1;
}
if ($opt =~ /buffersize=/) {
my ($attribute, $buffer_value) = split(/=/, $opt);
if ($buffer_value) {
# buffersize option was passed in, reset the default if valid
if (looks_like_number($buffer_value) and $buffer_value > 0) {
$buffer_size = $buffer_value;
}
else {
$exit_with_error_func->($sessdata->{node}, $callback,
"Invalid buffer size value $buffer_value");
}
}
}
if ($opt =~ /retry=/) {
my ($attribute, $retry_value) = split(/=/, $opt);
if (defined $retry_value) {
# retry option was passed in, reset the default if valid
if (looks_like_number($retry_value) and $retry_value >= 0) {
$retry = $retry_value;
}
else {
$exit_with_error_func->($sessdata->{node}, $callback,
"Invalid retry value $retry_value");
}
}
}
}
# For IBM Power S822LC for Big Data (Supermicro) machines such as
# P9 Boston (9006-22C, 9006-12C, 5104-22C) or P8 Briggs (8001-22C)
# firmware update is done using pUpdate utility expected to be in the
# specified data directory along with the update files .bin for BMC or .pnor for Host
if ($output =~ /8001-22C|9006-22C|5104-22C|9006-12C/) {
# Verify valid data directory was specified
if (defined $directory_name) {
unless (File::Spec->file_name_is_absolute($directory_name)) {
# Directory name was passed in as relative path, prepend current working dir
$directory_name = xCAT::Utils->full_path($directory_name, $::cwd);
}
# directory was passed in, verify it is valid
if (-d $directory_name) {
# Passed in directory name exists
$pUpdate_directory = $directory_name;
}
else {
$exit_with_error_func->($sessdata->{node}, $callback, "Can not access data directory $directory_name");
}
}
else {
$exit_with_error_func->($sessdata->{node}, $callback, "Directory name is required to update IBM Power S822LC for Big Data machines.");
}
# Verify specified directory contains pUpdate utility
unless (-e "$pUpdate_directory/pUpdate") {
$exit_with_error_func->($sessdata->{node}, $callback,
"Can not find pUpdate utility in data directory $pUpdate_directory.");
}
# Verify specified directory contains executable pUpdate utility
unless (-x "$pUpdate_directory/pUpdate") {
$exit_with_error_func->($sessdata->{node}, $callback,
"Execute permission is not set for pUpdate utility in data directory $pUpdate_directory.");
}
# Verify there is at least one of update files inside data directory - .bin or .pnor
my @pnor_files = <"$pUpdate_directory/*.pnor">; # Get a list of all .pnor files
my @bmc_files = <"$pUpdate_directory/*.bin">; # Get a list of all .bin files
if (scalar(@pnor_files) > 1) {
# Error if more than one .pnor file in data directory
$exit_with_error_func->($sessdata->{node}, $callback,
"Multiple PNOR update files detected in data directory $pUpdate_directory.");
}
if (scalar(@bmc_files) > 1) {
# Error if more than one .bin file in data directory
$exit_with_error_func->($sessdata->{node}, $callback,
"Multiple BMC update files detected in data directory $pUpdate_directory.");
}
my $pnor_file;
if (scalar(@pnor_files) > 0) {
$pnor_file = $pnor_files[0];
}
my $bmc_file;
if (scalar(@bmc_files) > 0) {
$bmc_file = $bmc_files[0];
}
# At least one update file is needed, .bmc or .pnor
unless ($pnor_file || $bmc_file) {
$exit_with_error_func->($sessdata->{node}, $callback,
"At least one update file (.bin or .pnor) needs to be in data directory $pUpdate_directory.");
}
# All checks are done, run pUpdate utility on each of the update files found in
# the specified data directory
xCAT::SvrUtils::sendmsg("rflash started, Please wait...", $callback, $sessdata->{node});
# step 1 power off
$cmd = $pre_cmd . " chassis power off";
if ($verbose) {
xCAT::SvrUtils::sendmsg("Preparing to upgrade firmware, powering chassis off...", $callback, $sessdata->{node}, %allerrornodes);
}
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
unless (check_bmc_status_with_ipmitool($pre_cmd, 5, 10, 2, $sessdata, $verbose, 1)) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Timeout to check the bmc status");
}
# step 2 update BMC file or PNOR file, or both
if ($bmc_file) {
# BMC file was found in data directory, run update with it
my $pUpdate_bmc_cmd = "$pUpdate_directory/pUpdate -f $bmc_file -i lan -h $bmc_addr -u $bmc_userid -p $bmc_password >".$rflash_log_file." 2>&1";
if ($verbose) {
xCAT::SvrUtils::sendmsg([ 0,
"rflashing BMC, see the detail progress :\"tail -f $rflash_log_file\"" ],
$callback, $sessdata->{node});
}
my $output = xCAT::Utils->runcmd($pUpdate_bmc_cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Error running command $pUpdate_bmc_cmd");
}
# Wait for BMC to reboot before continuing to next step.
# Since this is a IBM Power S822LC for Big Data (Supermicro) machine, use RC to check if ready
unless (check_bmc_status_with_ipmitool($pre_cmd, 5, 60, 10, $sessdata, $verbose, 1)) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Timeout to check the bmc status");
}
}
if ($pnor_file) {
# PNOR file was found in data directory, run update with it
my $pUpdate_pnor_cmd = "$pUpdate_directory/pUpdate -pnor $pnor_file -i lan -h $bmc_addr -u $bmc_userid -p $bmc_password >>".$rflash_log_file." 2>&1";
if ($verbose) {
xCAT::SvrUtils::sendmsg([ 0,
"rflashing PNOR, see the detail progress :\"tail -f $rflash_log_file\"" ],
$callback, $sessdata->{node});
}
my $output = xCAT::Utils->runcmd($pUpdate_pnor_cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Error running command $pUpdate_pnor_cmd");
}
}
# step 3 power on
$cmd = $pre_cmd . " chassis power on";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
$exit_with_success_func->($sessdata->{node}, $callback, "Firmware updated, powering chassis on to populate FRU information...");
}
if (($hpm_data_hash{deviceID} ne $sessdata->{device_id}) ||
($hpm_data_hash{productID} ne $sessdata->{prod_id}) ||
($hpm_data_hash{manufactureID} ne $sessdata->{mfg_id})) {
$exit_with_error_func->($sessdata->{node}, $callback,
"The image file doesn't match this machine");
}
# check for 8335-GTB Firmware above 1610A release. If below, exit
if ($output =~ /8335-GTB/) {
$cmd = $pre_cmd . " fru print 47";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
my $grs_version = $output =~ /OP8_.*v(\d*\.\d*_\d*\.\d*)/;
if ($grs_version =~ /\d\.(\d+)_(\d+\.\d+)/) {
my $prim_grs_version = $1;
my $sec_grs_version = $2;
if ($prim_grs_version <= 7 && $sec_grs_version < 2.55) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Error: Current firmware level OP8v_$grs_version requires one-time manual update to at least version OP8v_1.7_2.55");
}
}
}
# For Firestone the update from 810 to 820 or from 820 to 810 needs to be done in 3 steps
# instead of usual one.
if ($output =~ /8335-GCA|8335-GTA/) {
$is_firestone = 1;
$cmd = $pre_cmd . " fru print 47";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
# Check what firmware version is currently running on the machine
if ($output =~ /OP8_.*v\d\.\d+_(\d+)\.\d+/) {
my $frs_version = $1;
if ($frs_version == 1) {
$firestone_update_version = "810";
}
if ($frs_version == 2) {
$firestone_update_version = "820";
}
}
else {
$exit_with_error_func->($sessdata->{node}, $callback,
"Unable to determine firmware version currently installed. Verify that \"$cmd | grep OP8_v\" command returns a version.");
}
# Check what firmware version is specified in htm file
$cmd = "/usr/bin/grep -a FW_DESC $hpm_file";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
# Parse out build date from the description string
if ($output =~ /FW_DESC=8335 \w+ \w+ \w+ (\w+)/) {
my $htm_date= $1;
# Parse out the year from "mmddyyyy" (skip 4 digits, grab last 4)
if ($htm_date =~ /\d{4}(\d+)/) {
my $htm_year = $1;
if ($htm_year == 2016) {
$htm_update_version = "810";
}
if ($htm_year == 2017) {
$htm_update_version = "820";
}
}
}
else {
$exit_with_error_func->($sessdata->{node}, $callback,
"Unable to determine firmware version of $hpm_file.");
}
}
if ($is_firestone and $firestone_update_version and
(($firestone_update_version eq "820" and $htm_update_version eq "810") or
($firestone_update_version eq "810" and $htm_update_version eq "820"))
) {
xCAT::SvrUtils::sendmsg("rflash started, Please wait...", $callback, $sessdata->{node}, %allerrornodes);
$retry = 0; # No retry support for 3 step update process
}
else {
xCAT::SvrUtils::sendmsg("rflash started, upgrade failure will be retried up to $retry times. Please wait...", $callback, $sessdata->{node}, %allerrornodes);
}
RETRY_UPGRADE:
my $failed_upgrade = 0;
# step 1 power off
$cmd = $pre_cmd . " chassis power off";
if ($verbose) {
xCAT::SvrUtils::sendmsg("Preparing to upgrade firmware, powering chassis off...", $callback, $sessdata->{node}, %allerrornodes);
}
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
# step 2 reset cold
$cmd = $pre_cmd . " mc reset cold";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
# check reset status
unless (check_bmc_status_with_ipmitool($pre_cmd, 5, 60, 2, $sessdata, $verbose, 0)) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Timeout to check the bmc status");
}
# step 3 protect network
$cmd = $pre_cmd . " raw 0x32 0xba 0x18 0x00";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
# step 4 upgrade firmware
# For firestone machines if updating from 810 to 820 version or from 820 to 810,
# extra steps are needed. Hanled in "if" block, "else" block is normal update in a single step.
if ($is_firestone and
(($firestone_update_version eq "820" and $htm_update_version eq "810") or
($firestone_update_version eq "810" and $htm_update_version eq "820"))
) {
# Step 4.1
$cmd = $pre_cmd . " -z " . $buffer_size . " hpm upgrade $hpm_file component 0 force ";
$cmd .= $verbose_opt;
$cmd .= " >".$rflash_log_file." 2>&1";
if ($verbose) {
xCAT::SvrUtils::sendmsg([ 0,
"rflashing component 0, see the detail progress :\"tail -f $rflash_log_file\"" ],
$callback, $sessdata->{node});
}
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
# Since "hpm update" command in step 4.1 above redirects standard out and error to a log file,
# nothing gets returned from execution of the command. Here if RC is not zero, we
# extract all lines containing "Error" from that log file and display them in the sendmsg below
my $get_error_cmd = "/usr/bin/grep Error $rflash_log_file";
$output = xCAT::Utils->runcmd($get_error_cmd, 0);
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed with rc=$::RUNCMD_RC and the following error messages:\n$output\nSee the $rflash_log_file for details.");
}
sleep(1); # Sleep for a second before next step
# Step 4.2
$cmd = $pre_cmd . " -z " . $buffer_size . " hpm upgrade $hpm_file component 1 force ";
$cmd .= $verbose_opt;
$cmd .= " >>".$rflash_log_file." 2>&1";
if ($verbose) {
xCAT::SvrUtils::sendmsg([ 0,
"rflashing component 1, see the detail progress :\"tail -f $rflash_log_file\"" ],
$callback, $sessdata->{node});
}
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
# Since "hpm update" command in step 4.2 above redirects standard out and error to a log file,
# nothing gets returned from execution of the command. Here if RC is not zero, we
# extract all lines containing "Error" from that log file and display them in the sendmsg below
my $get_error_cmd = "/usr/bin/grep Error $rflash_log_file";
$output = xCAT::Utils->runcmd($get_error_cmd, 0);
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed with rc=$::RUNCMD_RC and the following error messages:\n$output\nSee the $rflash_log_file for details.");
}
# Wait for BMC to reboot before continuing to next step
unless (check_bmc_status_with_ipmitool($pre_cmd, 5, 60, 10, $sessdata, $verbose, 0)) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Timeout waiting for the bmc ready status. Firmware update suspended");
}
# Step 4.3
$cmd = $pre_cmd . " -z " . $buffer_size . " hpm upgrade $hpm_file component 2 force ";
$cmd .= $verbose_opt;
$cmd .= " >>".$rflash_log_file." 2>&1";
if ($verbose) {
xCAT::SvrUtils::sendmsg([ 0,
"rflashing component 2, see the detail progress :\"tail -f $rflash_log_file\"" ],
$callback, $sessdata->{node});
}
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
# Since "hpm update" command in step 4.3 above redirects standard out and error to a log file,
# nothing gets returned from execution of the command. Here if RC is not zero, we
# extract all lines containing "Error" from that log file and display them in the sendmsg below
my $get_error_cmd = "/usr/bin/grep Error $rflash_log_file";
$output = xCAT::Utils->runcmd($get_error_cmd, 0);
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed with rc=$::RUNCMD_RC and the following error messages:\n$output\nSee the $rflash_log_file for details.");
}
}
else {
$cmd = $pre_cmd . " -z " . $buffer_size . " hpm upgrade $hpm_file force ";
$cmd .= $verbose_opt;
$cmd .= " >".$rflash_log_file." 2>&1";
if ($verbose) {
xCAT::SvrUtils::sendmsg([ 0,
"rflashing ... See the detail progress :\"tail -f $rflash_log_file\"" ],
$callback, $sessdata->{node});
}
$output = xCAT::Utils->runcmd($cmd, -1);
# if upgrade command failed and we exausted number of retries
# report an error, exit to the caller and leave node in powered off state
# otherwise report an error, power on the node and try upgrade again
if ($::RUNCMD_RC != 0) {
# Since "hpm update" command in step 4 above redirects standard out and error to a log file,
# nothing gets returned from execution of the command. Here if RC is not zero, we
# extract all lines containing "Error" from that log file and display them in the sendmsg below
my $get_error_cmd = "/usr/bin/grep Error $rflash_log_file";
$output = xCAT::Utils->runcmd($get_error_cmd, 0);
if ($retry == 0) {
# No more retries left, report an error and exit
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed with rc=$::RUNCMD_RC and the following error messages:\n$output\nSee the $rflash_log_file for details.");
}
else {
# Error upgrading, set a flag to attempt a retry
xCAT::SvrUtils::sendmsg("Running attempt $retry of ipmitool command $cmd failed with rc=$::RUNCMD_RC and the following error messages:\n$output\nSee the $rflash_log_file for details.", $callback, $sessdata->{node}, %allerrornodes);
$failed_upgrade = 1;
}
}
}
# step 5 power on
# check reset status
unless (check_bmc_status_with_ipmitool($pre_cmd, 5, 60, 10, $sessdata, $verbose, 0)) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Timeout to check the bmc status");
}
if ($failed_upgrade) {
xCAT::SvrUtils::sendmsg("Firmware update failed, powering chassis on for a retry. This can take several minutes. $retry retries left ...", $callback, $sessdata->{node}, %allerrornodes);
}
else {
if ($verbose) {
xCAT::SvrUtils::sendmsg("Firmware updated, powering chassis on to populate FRU information...", $callback, $sessdata->{node}, %allerrornodes);
}
}
$cmd = $pre_cmd . " chassis power on";
$output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$exit_with_error_func->($sessdata->{node}, $callback,
"Running ipmitool command $cmd failed: $output");
}
if ($failed_upgrade) {
# Update has failed in step 4. Wait for node to reboot and try again
my $node_ready_for_retry = wait_for_os_to_reboot(300,10,30,$sessdata,$verbose);
if ($node_ready_for_retry) {
$retry--; # decrement number of retries left
# Yes, it is a goto statement here. Ugly, but removes the need to restructure
# the above block of code.
goto RETRY_UPGRADE;
}
else {
# After 10 min of waiting node has not rebooted. Give up retrying.
$exit_with_error_func->($sessdata->{node}, $callback,
"Giving up waiting for the node to reboot. No further retries will be attempted.");
}
}
else {
$exit_with_success_func->($sessdata->{node}, $callback,
"Success updating firmware.");
}
}
sub rflash {
my $sessdata = shift;
my $directory_flag = 0;
if (isopenpower($sessdata)) {
# Do firmware update for firestone here.
@{ $sessdata->{component_ids} } = qw/1 2 4/;
foreach my $opt (@{ $sessdata->{extraargs} }) {
if ($opt =~ /^(-c|--check)$/i) {
$sessdata->{subcommand} = "check";
# support verbose options for ipmitool command
} elsif ($opt =~ /^-d$/) {
# special handling if -d option which can be followed by a directory name
$directory_flag = 1; # set a flag that directory option was given
} elsif ($opt !~ /.*\.hpm$/i && $opt !~ /^-V{1,4}$|^--buffersize=|^--retry=/) {
# An unexpected flag was passed, but it could be a directory name. Display error only if not -d option
unless ($directory_flag) {
$callback->({ error => "The option $opt is not supported or invalid update file specified",
errorcode => 1 });
return;
}
}
}
# Send firmware version information to clent.
if ($sessdata->{subcommand} eq 'check') {
my %firmware_version;
my %comp_string;
check_firmware_version($sessdata, \%firmware_version, \%comp_string);
my $msg = "";
my $i;
for ($i = 0 ; $i < scalar(@{ $sessdata->{component_ids} }) ; $i++) {
my $c_id = ${ $sessdata->{component_ids} }[$i];
my $version = $firmware_version{$c_id};
my $format_string = $comp_string{$c_id};
my $format_ver = sprintf("%3d.%02x %02X%02X%02X%02X",
$version->[0], $version->[1], $version->[2],
$version->[3], $version->[4], $version->[5]);
$msg = $msg . $sessdata->{node} . ": " .
"Node firmware version for $format_string component: $format_ver";
if ($i != scalar(@{ $sessdata->{component_ids} }) - 1) {
$msg = $msg . "\n";
}
}
$callback->({ data => $msg });
return;
}
return do_firmware_update($sessdata);
} elsif (isfpc($sessdata)) {
#first, start a fpc firmware transaction
$sessdata->{firmpath} = $sessdata->{subcommand};
$sessdata->{firmctx} = "init";
$sessdata->{ipmisession}->subcmd(netfn => 0x8, command => 0x17,
data => [ 0, 0, 1, 0, 0, 0, 0 ],
callback => \&fpc_firmup_config,
callback_args => $sessdata);
} else {
die "Unimplemented";
}
}
#----------------------------------------------------------------#
# Running rflash procedure in a child process
# Note (chenglch) If the parent process abort unexpectedly, the
# child process can not be terminated by xcat.
#----------------------------------------------------------------#
sub do_rflash_process {
my $node = shift;
my $pid = xCAT::Utils->xfork;
if (!defined($pid)) {
xCAT::SvrUtils::sendmsg([ 1, "Fork rflash process Error." ],
$callback, $node, %allerrornodes);
return;
}
# child
elsif ($pid == 0) {
unless (setpgrp()) {
xCAT::SvrUtils::sendmsg([ 1, "Faild to run setgprp for $$ process" ],
$callback, $node);
exit(1);
}
my $extra = $_[8];
my @exargs = @$extra;
my $programe = \$0;
$$programe = "$node: rflash child process";
if (grep(/^(-c|--check)$/i, @exargs)) {
$SIG{INT} = $SIG{TERM} = $SIG{HUP} = "DEFAULT";
}
# NOTE (chenglch): Actually if multiple client or rest api works on the same node,
# the bmc of the node may not be protected while rflash is running. As xcat may not
# support lock on node level, just require a lock for rflash command for specific node.
my $lock = xCAT::Utils->acquire_lock("rflash_$node", $NON_BLOCK);
if (!$lock) {
xCAT::SvrUtils::sendmsg([ 1, "rflash is running on $node, please retry after a while" ],
$callback, $node, %allerrornodes);
exit(1);
}
my $recover_image;
if (grep(/^(--recover)$/, @{ $extra })) {
if (@{ $extra } != 2) {
xCAT::SvrUtils::sendmsg([ 1, "The command format for recovery is invalid. Only support 'rflash <noderange> --recover <bmc_file_path>'" ],
$callback, $node);
exit(1);
}
my @argv = @{ $extra };
if ($argv[0] eq "--recover") {
$recover_image = $argv[1];
} elsif ($argv[1] eq "--recover") {
$recover_image = $argv[0];
}
}
if (defined($recover_image)) {
if ($recover_image !~ /^\//) {
$recover_image = xCAT::Utils->full_path($recover_image, $::cwd);
}
unless(-x "/usr/bin/tftp") {
$callback->({ error => "Could not find executable file /usr/bin/tftp, please setup tftp client.",
errorcode => 1 });
exit(1);
}
my $bmcip = $_[0];
my $cmd = "/usr/bin/tftp $bmcip -m binary -c put $recover_image ".basename($recover_image);
my $output = xCAT::Utils->runcmd($cmd, -1);
if ($::RUNCMD_RC != 0) {
$callback->({ error => "Running tftp command \'$cmd\' failed. Error Code: $::RUNCMD_RC. Output: $output.",
errorcode => 1 });
exit(1);
}
# Sometimes tftp command retrun error message but without nonzero error code
if($output) {
$callback->({ error => "Running tftp command \'$cmd\' failed. Output: $output",
errorcode => 1 });
exit(1);
}
$callback->({ data => "$node: Successfully updated recovery image. BMC is restarting and will not be reachable for 5-10 minutes."});
exit(0);
}
donode($node, @_);
while (xCAT::IPMI->waitforrsp()) { yield }
xCAT::Utils->release_lock($lock, $NON_BLOCK);
exit(0);
}
# parent
else {
$child_pids{$pid} = $node;
}
return $pid;
}
sub start_rflash_processes {
my $donargs_ptr = shift;
my @donargs = @{$donargs_ptr};
my $ipmitimeout = shift;
my $ipmitrys = shift;
my $command = shift;
my %namedargs = @_;
my $extra = $namedargs{-args};
my @exargs = @$extra;
# rflash processes can not be terminated from client
if (!grep(/^(-c|--check)$/i, @exargs)) {
$SIG{INT} = $SIG{TERM} = $SIG{HUP}="IGNORE";
} else {
$SIG{INT} = $SIG{TERM} = $SIG{HUP} = sub {
foreach (keys %child_pids) {
kill 2, $_;
}
exit 0;
};
}
my $rflash_status;
foreach (@donargs) {
do_rflash_process($_->[0], $_->[1], $_->[2], $_->[3], $_->[4],
$ipmitimeout, $ipmitrys, $command, -args => \@exargs);
$rflash_status->{$_->[0]}->{status} = "updating firmware";
}
if (!grep(/^(-c|--check)$/i, @exargs)) {
my $nodelist_table = xCAT::Table->new('nodelist');
if (!$nodelist_table) {
xCAT::MsgUtils->message("S", "Unable to open nodelist table, denying");
} else {
$nodelist_table->setNodesAttribs($rflash_status);
$nodelist_table->close();
}
}
# Wait for all processes to end
while (keys %child_pids) {
my $cpid;
if (($cpid = wait()) > 0) {
delete $child_pids{$cpid};
}
}
}
sub fpc_firmup_config {
if (check_rsp_errors(@_)) {
abort_fpc_update($_[1]);
return;
}
my $rsp = shift;
my $sessdata = shift;
unless ($sessdata->{firmupxid}) {
$sessdata->{firmupxid} = $rsp->{data}->[0];
}
my $data;
if ($sessdata->{firmctx} eq 'init') {
$data = [ 0, $sessdata->{firmupxid}, 1, 0, 1, 0, 0, 0,
length($sessdata->{firmpath}),
unpack("C*", $sessdata->{firmpath}) ];
$sessdata->{firmctx} = 'p1';
} elsif ($sessdata->{firmctx} eq 'p1') {
$data = [ 0, $sessdata->{firmupxid}, 3, 0, 5 ];
$sessdata->{firmctx} = 'p2';
} elsif ($sessdata->{firmctx} eq 'p2') {
$data = [ 0, $sessdata->{firmupxid}, 4, 0, 0xa ];
$sessdata->{firmctx} = 'p3';
} elsif ($sessdata->{firmctx} eq 'p3') {
$data = [ 0, $sessdata->{firmupxid}, 5, 0, 3 ];
$sessdata->{firmctx} = 'p4';
} elsif ($sessdata->{firmctx} eq 'p4') {
$data = [ 0, $sessdata->{firmupxid}, 6, 0, 1 ];
$sessdata->{firmctx} = 'xfer';
xCAT::SvrUtils::sendmsg("Transferring firmware", $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{ipmisession}->subcmd(netfn => 0x8, command => 0x19,
data => [ 0, $sessdata->{firmupxid} ],
callback => \&fpc_firmxfer_watch,
callback_args => $sessdata);
return;
}
$sessdata->{ipmisession}->subcmd(netfn => 0x8, command => 0x18,
data => $data,
callback => \&fpc_firmup_config,
callback_args => $sessdata);
}
sub abort_fpc_update {
my $sessdata = shift;
$sessdata->{ipmisession}->subcmd(netfn => 0x8, command => 0x15, data => [], callback => \&fpc_update_aborted, callback_args => $sessdata);
}
sub fpc_update_aborted {
check_rsp_errors(@_);
return;
}
sub fpc_firmxfer_watch {
my $abort = 0;
if ($_[0]->{code} == 0x89) {
xCAT::SvrUtils::sendmsg([ 1, "Transfer failed (wrong url?)" ], $callback, $_[1]->{node}, %allerrornodes);
$abort = 1;
} elsif ($_[0]->{code} == 0x91) {
xCAT::SvrUtils::sendmsg([ 1, "Invalid URL format given" ], $callback, $_[1]->{node}, %allerrornodes);
$abort = 1;
}
if ($abort) {
abort_fpc_update($_[1]);
return;
}
if (check_rsp_errors(@_)) {
abort_fpc_update($_[1]);
return;
}
my $rsp = shift;
my $sessdata = shift;
my $delay = 1;
my $watch = 2;
if ($sessdata->{firmctx} eq 'apply') { $delay = 15; $watch = 1; }
if (check_rsp_errors(@_)) {
return;
}
my $percent = 0;
if ($rsp->{data} and (scalar(@{ $rsp->{data} }) > 0)) {
$percent = $rsp->{data}->[0];
}
#$callback->({sinfo=>"$percent%"});
if ($percent == 100) {
if ($sessdata->{firmctx} eq 'xfer') {
xCAT::SvrUtils::sendmsg("Applying firmware", $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{firmctx} = "apply";
$sessdata->{ipmisession}->subcmd(netfn => 0x8, command => 0x20,
data => [ 0, $sessdata->{firmupxid} ],
callback => \&fpc_firmxfer_watch,
callback_args => $sessdata);
return;
} else {
xCAT::SvrUtils::sendmsg("Resetting FPC", $callback, $sessdata->{node}, %allerrornodes);
resetbmc($sessdata);
}
} else {
$sessdata->{ipmisession}->subcmd(netfn => 0x8, command => 0x12,
data => [$watch],
delayxmit => $delay,
callback => \&fpc_firmxfer_watch,
callback_args => $sessdata);
}
}
my %fpcsessions;
sub reseat_node {
my $sessdata = shift;
if (1) { # TODO: FPC path checked for
my $mptab = xCAT::Table->new('mp', -create => 0);
unless ($mptab) {
xCAT::SvrUtils::sendmsg([ 1, "mp table must be configured for reseat" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $mpent = $mptab->getNodeAttribs($sessdata->{node}, [qw/mpa id/]);
unless ($mpent and $mpent->{mpa} and $mpent->{id}) {
xCAT::SvrUtils::sendmsg([ 1, "mp table must be configured for reseat" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $fpc = $mpent->{mpa};
my $ipmitab = xCAT::Table->new("ipmi");
my $ipmihash = $ipmitab->getNodesAttribs([$fpc], [ 'bmc', 'username', 'password' ]);
my $authdata = xCAT::PasswordUtils::getIPMIAuth(noderange => [$fpc], ipmihash => $ipmihash);
my $nodeuser = $authdata->{$fpc}->{username};
my $nodepass = $authdata->{$fpc}->{password};
$sessdata->{slotnumber} = $mpent->{id};
if (exists $fpcsessions{$mpent->{mpa}}) {
$sessdata->{fpcipmisession} = $fpcsessions{$mpent->{mpa}};
until ($sessdata->{fpcipmisession}->{logged}) {
$sessdata->{fpcipmisession}->waitforrsp(timeout=>1);
}
$sessdata->{fpcipmisession}->subcmd(netfn => 0x32, command => 0xa4,
data => [ $sessdata->{slotnumber}, 2 ],
callback => \&fpc_node_reseat_complete, callback_args => $sessdata);
} else {
$sessdata->{fpcipmisession} = xCAT::IPMI->new(bmc => $mpent->{mpa}, userid => $nodeuser, password => $nodepass);
$fpcsessions{$mpent->{mpa}} = $sessdata->{fpcipmisession};
$sessdata->{fpcipmisession}->login(callback => \&fpc_node_reseat, callback_args => $sessdata);
}
}
}
sub fpc_node_reseat {
my $status = shift;
my $sessdata = shift;
if ($status =~ /ERROR:/) {
xCAT::SvrUtils::sendmsg([ 1, $status ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
$sessdata->{fpcipmisession}->subcmd(netfn => 0x32, command => 0xa4,
data => [ $sessdata->{slotnumber}, 2 ],
callback => \&fpc_node_reseat_complete, callback_args => $sessdata);
}
sub fpc_node_reseat_complete {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code} == 0) {
xCAT::SvrUtils::sendmsg("reseat", $callback, $sessdata->{node}, %allerrornodes);
} elsif ($rsp->{code} == 0xd5) {
xCAT::SvrUtils::sendmsg([ 1, "No node in slot" ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, "Unknown error code " . $rsp->{code} ], $callback, $sessdata->{node}, %allerrornodes);
}
}
sub power {
my $sessdata = shift;
my $netfun = 0x00;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my $code;
if (($sessdata->{subcommand} !~ /^on$|^off$|^reset$|^boot$|^stat$|^state$|^status$/) and isopenpower($sessdata)) {
xCAT::SvrUtils::sendmsg([ 1, "unsupported command rpower $sessdata->{subcommand} for OpenPOWER" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($sessdata->{subcommand} eq "reseat") {
reseat_node($sessdata);
} elsif (not $sessdata->{acpistate} and is_systemx($sessdata)) { #Only implemented for IBM servers
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x1d, data => [1], callback => \&power_with_acpi, callback_args => $sessdata);
} else {
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 1, data => [], callback => \&power_with_context, callback_args => $sessdata);
}
}
sub power_with_acpi {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code} == 0) {
if ($rsp->{data}->[0] == 3) {
$sessdata->{acpistate} = "suspend";
}
}
#unless ($text) { $text = sprintf("Unknown error code %02xh",$rsp->{code}); }
#xCAT::SvrUtils::sendmsg([1,$text],$callback,$sessdata->{node},%allerrornodes);
#}
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 1, data => [], callback => \&power_with_context, callback_args => $sessdata);
}
sub power_with_context {
my $rsp = shift;
my $sessdata = shift;
my $text = "";
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code} != 0) {
$text = $codes{ $rsp->{code} };
unless ($text) { $text = sprintf("Unknown error code %02xh", $rsp->{code}); }
xCAT::SvrUtils::sendmsg([ 1, $text ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
$sessdata->{powerstatus} = ($rsp->{data}->[0] & 1 ? "on" : "off");
my $reportstate;
if ($sessdata->{acpistate}) {
$reportstate = $sessdata->{acpistate};
} else {
$reportstate = $sessdata->{powerstatus};
}
if ($sessdata->{subcommand} eq "stat" or $sessdata->{subcommand} eq "state" or $sessdata->{subcommand} eq "status") {
if ($sessdata->{powerstatprefix}) {
xCAT::SvrUtils::sendmsg($sessdata->{powerstatprefix} . $reportstate, $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg($reportstate, $callback, $sessdata->{node}, %allerrornodes);
}
if ($sessdata->{sensorstoread} and scalar @{ $sessdata->{sensorstoread} }) { #if we are in an rvitals path, hook back into good graces
$sessdata->{currsdr} = shift @{ $sessdata->{sensorstoread} };
readsensor($sessdata); #next
}
return;
}
my $subcommand = $sessdata->{subcommand};
if ($sessdata->{subcommand} eq "boot") {
$text = $sessdata->{powerstatus} . " ";
$subcommand = ($sessdata->{powerstatus} eq "on" ? "reset" : "on");
$sessdata->{subcommand} = $subcommand; #lazy typing..
}
my %argmap = ( #english to ipmi dictionary
"on" => 1,
"off" => 0,
"softoff" => 5,
"reset" => 3,
"nmi" => 4
);
if ($subcommand eq "on") {
if ($sessdata->{powerstatus} eq "on") {
if ($sessdata->{acpistate} and $sessdata->{acpistate} eq "suspend") { #ok, make this a wake
$sessdata->{subcommand} = "wake";
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x1d, data => [ 0, 0 ], callback => \&power_response, callback_args => $sessdata);
return;
}
xCAT::SvrUtils::sendmsg("on", $callback, $sessdata->{node}, %allerrornodes);
$allerrornodes{ $sessdata->{node} } = 1;
return; # don't bother sending command
}
} elsif ($subcommand eq "softoff" or $subcommand eq "off" or $subcommand eq "reset") {
if ($sessdata->{powerstatus} eq "off") {
xCAT::SvrUtils::sendmsg("off", $callback, $sessdata->{node}, %allerrornodes);
$allerrornodes{ $sessdata->{node} } = 1;
return;
}
} elsif ($subcommand eq "suspend") {
my $waitforsuspend;
my $failtopowerdown;
my $failtoreset;
if ($sessdata->{powerstatus} eq "off") {
xCAT::SvrUtils::sendmsg([ 1, "System is off, cannot be suspended" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if (@{ $sessdata->{extraargs} } > 1) {
@ARGV = @{ $sessdata->{extraargs} };
use Getopt::Long;
unless (GetOptions(
'w:i' => \$waitforsuspend,
'o' => \$failtopowerdown,
'r' => \$failtoreset,
)) {
xCAT::SvrUtils::sendmsg([ 1, "Error parsing arguments" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
}
if (defined $waitforsuspend) {
if ($waitforsuspend == 0) { $waitforsuspend = 30; }
$sessdata->{waitforsuspend} = time() + $waitforsuspend;
}
$sessdata->{failtopowerdown} = $failtopowerdown;
$sessdata->{failtoreset} = $failtoreset;
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x1d, data => [ 0, 3 ], callback => \&power_response, callback_args => $sessdata);
return;
} elsif ($subcommand eq "wake") {
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x1d, data => [ 0, 0 ], callback => \&power_response, callback_args => $sessdata);
return;
} elsif (not $argmap{$subcommand}) {
xCAT::SvrUtils::sendmsg([ 1, "unsupported command power $subcommand" ], $callback);
return;
}
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 2, data => [ $argmap{$subcommand} ], callback => \&power_response, callback_args => $sessdata);
}
sub power_response {
my $rsp = shift;
my $sessdata = shift;
my $newstat;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my @returnd = ($rsp->{code}, @{ $rsp->{data} });
if ($rsp->{code}) {
my $text = $codes{ $rsp->{code} };
unless ($text) { $text = sprintf("Unknown response %02xh", $rsp->{code}); }
xCAT::SvrUtils::sendmsg([ 1, $text ], $callback, $sessdata->{node}, %allerrornodes);
return;
} else {
my $command = $sessdata->{subcommand};
my $status = $sessdata->{powerstatus};
if ($command eq "on" or $command eq "boot") { $newstat = $::STATUS_POWERING_ON; }
if ($command eq "off" or $command eq "softoff") { $newstat = $::STATUS_POWERING_OFF; }
if ($command eq "reset") {
if ($status eq "off") {
$newstat = $::STATUS_POWERING_OFF;
} else {
$newstat = $::STATUS_POWERING_ON;
}
}
if ($newstat) {
push @{ $newnodestatus{$newstat} }, $sessdata->{node};
}
}
if ($sessdata->{waitforsuspend}) { #have to repeatedly power stat until happy or timeout exceeded
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x1d, data => [1], callback => \&power_wait_for_suspend, callback_args => $sessdata);
return;
}
xCAT::SvrUtils::sendmsg($sessdata->{subcommand}, $callback, $sessdata->{node}, %allerrornodes);
}
sub power_wait_for_suspend {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code} == 0) {
if ($rsp->{data}->[0] == 3) {
$sessdata->{acpistate} = "suspend";
}
}
if ($sessdata->{acpistate} eq "suspend") {
xCAT::SvrUtils::sendmsg("suspend", $callback, $sessdata->{node}, %allerrornodes);
} elsif ($sessdata->{waitforsuspend} <= time()) {
delete $sessdata->{waitforsuspend};
if ($sessdata->{failtopowerdown}) {
$sessdata->{subcommand} = 'off',
xCAT::SvrUtils::sendmsg([ 1, "Failed to enter suspend state, forcing off" ], $callback, $sessdata->{node}, %allerrornodes);
power($sessdata);
} elsif ($sessdata->{failtoreset}) {
$sessdata->{subcommand} = 'reset',
xCAT::SvrUtils::sendmsg([ 1, "Failed to enter suspend state, forcing reset" ], $callback, $sessdata->{node}, %allerrornodes);
power($sessdata);
} else {
xCAT::SvrUtils::sendmsg([ 1, "Failed to enter suspend state" ], $callback, $sessdata->{node}, %allerrornodes);
}
} else {
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x1d, delayxmit => 5, data => [1], callback => \&power_wait_for_suspend, callback_args => $sessdata);
}
}
sub generic {
my $subcommand = shift;
my $netfun;
my @args;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my $code;
($netfun, @args) = split(/-/, $subcommand);
$netfun = oct($netfun);
printf("netfun: 0x%02x\n", $netfun);
print "command: ";
foreach (@args) {
push(@cmd, oct($_));
printf("0x%02x ", oct($_));
}
print "\n\n";
$error = docmd(
$netfun,
\@cmd,
\@returnd
);
if ($error) {
$rc = 1;
$text = $error;
}
$code = $returnd[0];
if ($code == 0x00) {
}
else {
$rc = 1;
$text = $codes{$code};
}
printf("return code: 0x%02x\n\n", $code);
print "return data:\n";
my @rdata = @returnd[ 1 .. @returnd - 2 ];
hexadump(\@rdata);
print "\n";
print "full output:\n";
hexadump(\@returnd);
print "\n";
# if(!$text) {
# $rc = 1;
# $text = sprintf("unknown response %02x",$code);
# }
return ($rc, $text);
}
sub beacon {
my $sessdata = shift;
my $subcommand = $sessdata->{subcommand};
my $ipmiv2 = 0;
if ($sessdata->{ipmisession}->{ipmiversion} eq '2.0') {
$ipmiv2 = 1;
}
if ($subcommand ne "on" and $subcommand ne "off") {
xCAT::SvrUtils::sendmsg([ 1, "Only 'on' or 'off' is supported for IPMI managed nodes."], $callback, $sessdata->{node}, %allerrornodes);
}
#if stuck with 1.5, say light for 255 seconds. In 2.0, specify to turn it on forever
if ($subcommand eq "on") {
if ($sessdata->{mfg_id} == 19046 and $sessdata->{prod_id} == 13616) { # Lenovo SD350
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 6, data => [ 1, 1 ], callback => \&beacon_answer, callback_args => $sessdata);
} elsif ($ipmiv2) {
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 4, data => [ 0, 1 ], callback => \&beacon_answer, callback_args => $sessdata);
} else {
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 4, data => [0xff], callback => \&beacon_answer, callback_args => $sessdata);
}
}
elsif ($subcommand eq "off") {
if ($ipmiv2) {
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 4, data => [ 0, 0 ], callback => \&beacon_answer, callback_args => $sessdata);
} else {
$sessdata->{ipmisession}->subcmd(netfn => 0, command => 4, data => [0x0], callback => \&beacon_answer, callback_args => $sessdata);
}
}
else {
return;
}
}
sub beacon_answer {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) { #non ipmi error
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code}) { #ipmi error
if ($codes{ $rsp->{code} }) {
xCAT::SvrUtils::sendmsg([ 1, $codes{ $rsp->{code} } ], $callback);
} else {
xCAT::SvrUtils::sendmsg([ 1, sprintf("Unknown error code %02xh", $rsp->{code}) ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
xCAT::SvrUtils::sendmsg($sessdata->{subcommand}, $callback, $sessdata->{node}, %allerrornodes);
}
sub inv {
my $sessdata = shift;
my $command = $sessdata->{command};
my $subcommand = $sessdata->{subcommand};
my $rc = 0;
my $text;
my @output;
my @types;
unless ($subcommand) {
$subcommand = "all";
}
if ($subcommand eq "all") {
@types = qw(model serial deviceid mprom guid misc hw asset firmware mac wwn);
}
elsif ($subcommand eq "asset") {
$sessdata->{skipotherfru} = 1;
@types = qw(asset);
}
elsif ($subcommand eq "firm" || $subcommand eq "firmware") {
$sessdata->{skipotherfru} = 1;
@types = qw(firmware);
}
elsif ($subcommand eq "model") {
$sessdata->{skipotherfru} = 1;
@types = qw(model);
}
elsif ($subcommand eq "serial") {
$sessdata->{skipotherfru} = 1;
@types = qw(serial);
}
elsif ($subcommand eq "vpd") {
$sessdata->{skipotherfru} = 1;
@types = qw(model serial deviceid mprom);
}
elsif ($subcommand eq "mprom") {
$sessdata->{skipfru} = 1; #full fru read is expensive, skip it
@types = qw(mprom);
}
elsif ($subcommand eq "misc") {
$sessdata->{skipotherfru} = 1;
@types = qw(misc);
}
elsif ($subcommand eq "deviceid") {
$sessdata->{skipfru} = 1; #full fru read is expensive, skip it
@types = qw(deviceid);
}
elsif ($subcommand eq "guid") {
$sessdata->{skipfru} = 1; #full fru read is expensive, skip it
@types = qw(guid);
}
elsif ($subcommand eq "uuid") {
$sessdata->{skipfru} = 1; #full fru read is expensive, skip it
@types = qw(guid);
}
elsif ($subcommand eq "hw" or $subcommand eq "dimm" or $subcommand eq "misc") {
@types = ($subcommand);
}
else {
my $usage_string = xCAT::Usage->getUsage($command);
$callback->({ error => ["$usage_string"], errorcode => [1] });
return 1;
}
$sessdata->{invtypes} = \@types;
initfru($sessdata);
}
sub fru_initted {
my $sessdata = shift;
my $key;
my @args = @{ $sessdata->{extraargs} };
my $up_group = undef;
if (grep /-t/, @args) {
$up_group = '1';
}
my @types = @{ $sessdata->{invtypes} };
my $format = "%-20s %s";
foreach $key (sort { $a <=> $b } keys %{ $sessdata->{fru_hash} }) {
my $fru = $sessdata->{fru_hash}->{$key};
my $type;
foreach $type (split /,/, $fru->rec_type) {
if (grep { $_ eq $type } @types) {
my $bmcifo = "";
if ($sessdata->{bmcnum} != 1) {
$bmcifo = " on BMC " . $sessdata->{bmcnum};
}
xCAT::SvrUtils::sendmsg(sprintf($format . $bmcifo, $sessdata->{fru_hash}->{$key}->desc . ":", $sessdata->{fru_hash}->{$key}->value), $callback, $sessdata->{node}, %allerrornodes);
if ($up_group and $type eq "model" and $fru->desc =~ /MTM/) {
my $tmp_pre = xCAT::data::ibmhwtypes::parse_group($fru->value);
if (defined($tmp_pre)) {
xCAT::TableUtils->updatenodegroups($sessdata->{node}, $tmp_pre);
}
}
last;
}
}
}
if ($sessdata->{isite} and (grep { $_ eq "mac" } @types)) {
$needbladeinv{$_} = "mac";
}
}
sub add_textual_fru {
my $parsedfru = shift;
my $description = shift;
my $category = shift;
my $subcategory = shift;
my $types = shift;
my $sessdata = shift;
my %args = @_;
if ($parsedfru->{$category} and $parsedfru->{$category}->{$subcategory}) {
my $fru;
my @subfrus;
if (ref $parsedfru->{$category}->{$subcategory} eq 'ARRAY') {
@subfrus = @{ $parsedfru->{$category}->{$subcategory} };
} else {
@subfrus = ($parsedfru->{$category}->{$subcategory})
}
my $index = 0;
foreach (@subfrus) {
$index++;
$fru = FRU->new();
$fru->rec_type($types);
if ($args{addnumber}) {
$fru->desc($description . " " . $index);
} else {
$fru->desc($description);
}
if (not ref $_) {
$fru->value($_);
} else {
if ($_->{encoding} == 3) {
$fru->value($_->{value});
} else {
$fru->value(phex($_->{value}));
}
}
$sessdata->{fru_hash}->{ $sessdata->{frudex} } = $fru;
$sessdata->{frudex} += 1;
}
}
}
sub add_textual_frus {
my $parsedfru = shift;
my $desc = shift;
my $categorydesc = shift;
my $category = shift;
my $type = shift;
my $sessdata = shift;
unless ($type) { $type = 'hw'; }
if ($desc =~ /System Firmware/i and $category =~ /product/i) {
$type = 'firmware,bmc';
}
if ( ($desc =~ /NODE \d+/ or $desc =~ /Backplane/) and $category =~ /chassis/) {
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Part Number", $category, "partnumber", 'model', $sessdata);
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Serial Number", $category, "serialnumber", 'serial', $sessdata);
} else {
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Part Number", $category, "partnumber", $type, $sessdata);
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Serial Number", $category, "serialnumber", $type, $sessdata);
}
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Manufacturer", $category, "manufacturer", $type, $sessdata);
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "FRU Number", $category, "frunum", $type, $sessdata);
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Version", $category, "version", $type, $sessdata);
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "MAC Address", $category, "macaddrs", "mac", $sessdata, addnumber => 1);
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "WWN", $category, "wwns", "wwn", $sessdata, addnumber => 1);
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "", $category, "name", $type, $sessdata);
if ($parsedfru->{$category}->{builddate}) {
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Manufacture Date", $category, "builddate", $type, $sessdata);
}
if ($parsedfru->{$category}->{buildlocation}) {
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Manufacture Location", $category, "buildlocation", $type, $sessdata);
}
if ($parsedfru->{$category}->{model}) {
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Model", $category, "model", $type, $sessdata);
}
add_textual_fru($parsedfru, $desc . " " . $categorydesc . "Additional Info", $category, "extra", $type, $sessdata);
}
sub initfru {
my $netfun = 0x28;
my $sessdata = shift;
$sessdata->{fru_hash} = {};
my $mfg_id = $sessdata->{mfg_id};
my $prod_id = $sessdata->{prod_id};
my $device_id = $sessdata->{device_id};
my $fru = FRU->new();
$fru->rec_type("deviceid");
$fru->desc("Manufacturer ID");
my $value = $mfg_id;
if ($MFG_ID{$mfg_id}) {
$value = "$MFG_ID{$mfg_id} ($mfg_id)";
}
$fru->value($value);
$sessdata->{fru_hash}->{mfg_id} = $fru;
$fru = FRU->new();
$fru->rec_type("deviceid");
$fru->desc("Product ID");
$value = $prod_id;
my $tmp = "$mfg_id:$prod_id";
if ($PROD_ID{$tmp}) {
$value = "$PROD_ID{$tmp} ($prod_id)";
}
$fru->value($value);
$sessdata->{fru_hash}->{prod_id} = $fru;
$fru = FRU->new();
$fru->rec_type("deviceid");
$fru->desc("Device ID");
$fru->value($device_id);
$sessdata->{fru_hash}->{device_id} = $fru;
$sessdata->{ipmisession}->subcmd(netfn => 0x6, command => 0x37, data => [], callback => \&gotguid, callback_args => $sessdata);
}
sub got_bmc_fw_info {
my $rsp = shift;
my $sessdata = shift;
my $fw_rev1 = $sessdata->{firmware_rev1};
my $fw_rev2 = $sessdata->{firmware_rev2};
my $mprom;
my $isanimm = 0;
if (ref $rsp and not $rsp->{error} and not $rsp->{code}) { #I am a callback and the command worked
my @returnd = (@{ $rsp->{data} });
my @a = ($fw_rev2);
my $prefix = pack("C*", @returnd[ 0 .. 3 ]);
if ($prefix =~ /yuoo/i or $prefix =~ /1aoo/i or $prefix =~ /tcoo/i) { #we have an imm
$isanimm = 1;
}
$mprom = sprintf("%d.%s (%s)", $fw_rev1, decodebcd(\@a), getascii(@returnd));
} else { #either not a callback or IBM call failed
my @a = ($fw_rev2);
$mprom = sprintf("%d.%s", $fw_rev1, decodebcd(\@a));
}
my $fru = FRU->new();
$fru->rec_type("mprom,firmware,bmc,imm");
$fru->desc("BMC Firmware");
$fru->value($mprom);
$sessdata->{fru_hash}->{mprom} = $fru;
$sessdata->{isanimm} = $isanimm;
if ($isanimm) {
#get_imm_property(property=>"/v2/bios/build_id",callback=>\&got_bios_buildid,sessdata=>$sessdata);
check_for_ite(sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_bios_buildid {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{biosbuildid} = $res{data};
get_imm_property(property => "/v2/bios/pending_build_id", callback => \&got_bios_pending_buildid, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_bios_pending_buildid {
my %res = @_;
my $sessdata = $res{sessdata};
$sessdata->{biosbuildpending} = 0;
if ($res{data} and $res{data} ne ' ') {
$sessdata->{biosbuildpending} = $res{data};
}
get_imm_property(property => "/v2/bios/build_version", callback => \&got_bios_version, sessdata => $sessdata);
}
sub got_bios_version {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{biosbuildversion} = $res{data};
get_imm_property(property => "/v2/bios/build_date", callback => \&got_bios_date, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_bios_date {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{biosbuilddate} = $res{data};
my $fru = FRU->new();
$fru->rec_type("bios,uefi,firmware");
$fru->desc("UEFI Version");
my $pending = "";
if ($sessdata->{biosbuildpending}) {
$pending = " [PENDING: $sessdata->{biosbuildpending}]";
}
my $value = $sessdata->{biosbuildversion} . " (" . $sessdata->{biosbuildid} . " " . $sessdata->{biosbuilddate} . ")$pending";
$fru->value($value);
$sessdata->{fru_hash}->{uefi} = $fru;
get_imm_property(property => "/v2/fpga/build_id", callback => \&got_fpga_buildid, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_fpga_buildid {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{fpgabuildid} = $res{data};
get_imm_property(property => "/v2/fpga/build_version", callback => \&got_fpga_version, sessdata => $sessdata);
} else {
get_imm_property(property => "/v2/ibmc/dm/fw/bios/backup_build_id", callback => \&got_backup_bios_buildid, sessdata => $sessdata);
}
}
sub got_backup_bios_buildid {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{backupbiosbuild} = $res{data};
get_imm_property(property => "/v2/ibmc/dm/fw/bios/backup_build_version", callback => \&got_backup_bios_version, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_backup_bios_version {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{backupbiosversion} = $res{data};
my $fru = FRU->new();
$fru->rec_type("bios,uefi,firmware");
$fru->desc("Backup UEFI Version");
$fru->value($sessdata->{backupbiosversion} . " (" . $sessdata->{backupbiosbuild} . ")");
$sessdata->{fru_hash}->{backupuefi} = $fru;
get_imm_property(property => "/v2/ibmc/dm/fw/imm2/backup_build_id", callback => \&got_backup_imm_buildid, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_backup_imm_buildid {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{backupimmbuild} = $res{data};
get_imm_property(property => "/v2/ibmc/dm/fw/imm2/backup_build_version", callback => \&got_backup_imm_version, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_backup_imm_version {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{backupimmversion} = $res{data};
get_imm_property(property => "/v2/ibmc/dm/fw/imm2/backup_build_date", callback => \&got_backup_imm_builddate, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_backup_imm_builddate {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{backupimmdate} = $res{data};
my $fru = FRU->new();
$fru->rec_type("bios,uefi,firmware");
$fru->desc("Backup IMM Version");
$fru->value($sessdata->{backupimmversion} . " (" . $sessdata->{backupimmbuild} . " " . $sessdata->{backupimmdate} . ")");
$sessdata->{fru_hash}->{backupimm} = $fru;
get_imm_property(property => "/v2/ibmc/trusted_buildid", callback => \&got_trusted_imm, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_trusted_imm {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
my $fru = FRU->new();
$fru->rec_type("bios,uefi,firmware");
$fru->desc("Trusted IMM Build");
$fru->value($res{data});
$sessdata->{fru_hash}->{trustedimm} = $fru;
}
initfru_with_mprom($sessdata);
}
sub got_fpga_version {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{fpgabuildversion} = $res{data};
get_imm_property(property => "/v2/fpga/build_date", callback => \&got_fpga_date, sessdata => $sessdata);
} else {
initfru_with_mprom($sessdata);
}
}
sub got_fpga_date {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{data}) {
$sessdata->{fpgabuilddate} = $res{data};
my $fru = FRU->new();
$fru->rec_type("fpga,firmware");
$fru->desc("FPGA Version");
$fru->value($sessdata->{fpgabuildversion} . " (" . $sessdata->{fpgabuildid} . " " . $sessdata->{fpgabuilddate} . ")");
$sessdata->{fru_hash}->{fpga} = $fru;
}
initfru_with_mprom($sessdata);
}
sub check_for_ite {
my %args = @_;
my @getpropertycommand;
my $sessdata = $args{sessdata};
$sessdata->{property_callback} = \&got_ite_check; #$args{callback};
@getpropertycommand = unpack("C*", "/v2/cmm/");
my $length = 0b10000000 | (scalar @getpropertycommand); #use length to store tlv
unshift @getpropertycommand, $length;
#command also needs the overall length
$length = (scalar @getpropertycommand);
unshift @getpropertycommand, 0; #do not recurse, though it's not going to matter anyway since we are just checking for the existence of the category
unshift @getpropertycommand, $length & 0xff;
unshift @getpropertycommand, ($length >> 8) & 0xff;
unshift @getpropertycommand, 2; #get all properties command,
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0xc4, data => \@getpropertycommand, callback => \&got_imm_property, callback_args => $sessdata);
}
sub got_ite_check {
my %res = @_;
my $sessdata = $res{sessdata};
if ($res{ccode} == 9) { #success, end of tree means an ITE, remember this
$sessdata->{isite} = 1;
} else {
$sessdata->{isite} = 0;
}
get_imm_property(property => "/v2/bios/build_id", callback => \&got_bios_buildid, sessdata => $sessdata);
}
sub get_imm_property {
my %args = @_;
my @getpropertycommand;
my $sessdata = $args{sessdata};
$sessdata->{property_callback} = $args{callback};
@getpropertycommand = unpack("C*", $args{property});
my $length = 0b10000000 | (scalar @getpropertycommand); #use length to store tlv
unshift @getpropertycommand, $length;
#command also needs the overall length
$length = (scalar @getpropertycommand);
unshift @getpropertycommand, $length & 0xff;
unshift @getpropertycommand, ($length >> 8) & 0xff;
unshift @getpropertycommand, 0; #the actual 'get proprety' command is 0.
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0xc4, data => \@getpropertycommand, callback => \&got_imm_property, callback_args => $sessdata);
}
sub got_imm_property {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
my @data = @{ $rsp->{data} };
my $propval = shift @data;
my %res;
$res{sessdata} = $sessdata;
$res{ccode} = $propval;
if ($propval == 0) { #success
shift @data; #discard payload size
shift @data; #discard payload size
while (@data) {
my $tlv = shift @data;
if ($tlv & 0b10000000) {
$tlv = $tlv & 0b1111111;
my @val = splice(@data, 0, $tlv);
$res{data} = unpack("Z*", pack("C*", @val));
}
}
}
$sessdata->{property_callback}->(%res);
}
sub initfru_withguid {
my $sessdata = shift;
my $mfg_id = $sessdata->{mfg_id};
my $prod_id = $sessdata->{prod_id};
my $mprom;
if (is_systemx($sessdata) && $prod_id != 34869) {
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0x50, data => [], callback => \&got_bmc_fw_info, callback_args => $sessdata);
} else {
got_bmc_fw_info(0, $sessdata);
}
}
sub initfru_with_mprom {
my $sessdata = shift;
if ($sessdata->{skipfru}) {
fru_initted($sessdata);
return;
}
$sessdata->{currfruid} = 0;
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x10, data => [0], callback => \&process_currfruid, callback_args => $sessdata);
}
sub process_currfruid {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{code} == 0xcb) {
$sessdata->{currfrudata} = "Not Present";
$sessdata->{currfrudone} = 1;
add_fruhash($sessdata);
return;
}
if ($rsp and $rsp->{code}) { #non-zero return code..
$sessdata->{currfrudata} = "Unable to read";
if ($codes{ $rsp->{code} }) {
$sessdata->{currfrudata} .= " (" . $codes{ $rsp->{code} } . ")";
} else {
$sessdata->{currfrudata} .= sprintf(" (Unknown reason %02xh)", $rsp->{code});
}
$sessdata->{currfrudone} = 1;
add_fruhash($sessdata);
return;
}
if (check_rsp_errors($rsp, $sessdata)) {
return;
}
my @bytes = @{ $rsp->{data} };
$sessdata->{currfrusize} = ($bytes[1] << 8) + $bytes[0];
readcurrfrudevice(0, $sessdata);
}
sub initfru_zero {
my $sessdata = shift;
my $fruhash = shift;
my $frudex = 0;
my $fru;
if (defined $fruhash->{product}->{manufacturer}->{value}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("System Manufacturer");
if ($fruhash->{product}->{manufacturer}->{encoding} == 3) {
$fru->value($fruhash->{product}->{manufacturer}->{value});
} else {
$fru->value(phex($fruhash->{product}->{manufacturer}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if (defined $fruhash->{product}->{product}->{value}) {
$fru = FRU->new();
$fru->rec_type("model");
$fru->desc("System Description");
if ($fruhash->{product}->{product}->{encoding} == 3) {
$fru->value($fruhash->{product}->{product}->{value});
} else {
$fru->value(phex($fruhash->{product}->{product}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if (defined $fruhash->{product}->{model}->{value}) {
$fru = FRU->new();
$fru->rec_type("model");
$fru->desc("System Model/MTM");
if ($fruhash->{product}->{model}->{encoding} == 3) {
$fru->value($fruhash->{product}->{model}->{value});
} else {
$fru->value(phex($fruhash->{product}->{model}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if (defined $fruhash->{product}->{version}->{value}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("System Revision");
if ($fruhash->{product}->{version}->{encoding} == 3) {
$fru->value($fruhash->{product}->{version}->{value});
} else {
$fru->value(phex($fruhash->{product}->{version}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if (defined $fruhash->{product}->{serialnumber}->{value}) {
$fru = FRU->new();
$fru->rec_type("serial");
$fru->desc("System Serial Number");
if ($fruhash->{product}->{serialnumber}->{encoding} == 3) {
$fru->value($fruhash->{product}->{serialnumber}->{value});
} else {
$fru->value(phex($fruhash->{product}->{serialnumber}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if (defined $fruhash->{product}->{asset}->{value}) {
$fru = FRU->new();
$fru->rec_type("asset");
$fru->desc("System Asset Number");
if ($fruhash->{product}->{asset}->{encoding} == 3) {
$fru->value($fruhash->{product}->{asset}->{value});
} else {
$fru->value(phex($fruhash->{product}->{asset}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
foreach (@{ $fruhash->{product}->{extra} }) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Product Extra data");
if ($_->{encoding} == 3) {
$fru->value($_->{value});
} else {
#print Dumper($_);
#print $_->{encoding};
next;
$fru->value(phex($_->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{chassis}->{serialnumber}->{value}) {
$fru = FRU->new();
$fru->rec_type("serial");
$fru->desc("Chassis Serial Number");
if ($fruhash->{chassis}->{serialnumber}->{encoding} == 3) {
$fru->value($fruhash->{chassis}->{serialnumber}->{value});
} else {
$fru->value(phex($fruhash->{chassis}->{serialnumber}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{chassis}->{partnumber}->{value}) {
$fru = FRU->new();
$fru->rec_type("model");
$fru->desc("Chassis Part Number");
if ($fruhash->{chassis}->{partnumber}->{encoding} == 3) {
$fru->value($fruhash->{chassis}->{partnumber}->{value});
} else {
$fru->value(phex($fruhash->{chassis}->{partnumber}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
foreach (@{ $fruhash->{chassis}->{extra} }) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Chassis Extra data");
if ($_->{encoding} == 3) {
$fru->value($_->{value});
} else {
next;
#print Dumper($_);
#print $_->{encoding};
$fru->value(phex($_->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{board}->{builddate}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board manufacture date");
$fru->value($fruhash->{board}->{builddate});
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{board}->{manufacturer}->{value}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board manufacturer");
if ($fruhash->{board}->{manufacturer}->{encoding} == 3) {
$fru->value($fruhash->{board}->{manufacturer}->{value});
} else {
$fru->value(phex($fruhash->{board}->{manufacturer}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{board}->{frunum}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board FRU Number");
$fru->value($fruhash->{board}->{frunum});
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{board}->{revision}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board Revision");
$fru->value($fruhash->{board}->{revision});
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{board}->{macaddrs}) {
my $macindex = 1;
foreach my $mac (@{ $fruhash->{board}->{macaddrs} }) {
$fru = FRU->new();
$fru->rec_type("mac");
$fru->desc("MAC Address $macindex");
$macindex++;
$fru->value($mac);
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
}
if ($fruhash->{board}->{wwns}) {
my $macindex = 1;
foreach my $mac (@{ $fruhash->{board}->{wwns} }) {
$fru = FRU->new();
$fru->rec_type("wwn");
$fru->desc("WWN $macindex");
$macindex++;
$fru->value($mac);
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
}
if ($fruhash->{board}->{name}->{value}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board Description");
if ($fruhash->{board}->{name}->{encoding} == 3) {
$fru->value($fruhash->{board}->{name}->{value});
} else {
$fru->value(phex($fruhash->{board}->{name}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{board}->{serialnumber}->{value}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board Serial Number");
if ($fruhash->{board}->{serialnumber}->{encoding} == 3) {
$fru->value($fruhash->{board}->{serialnumber}->{value});
} else {
$fru->value(phex($fruhash->{board}->{serialnumber}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
if ($fruhash->{board}->{partnumber}->{value}) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board Model Number");
if ($fruhash->{board}->{partnumber}->{encoding} == 3) {
$fru->value($fruhash->{board}->{partnumber}->{value});
} else {
$fru->value(phex($fruhash->{board}->{partnumber}->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
foreach (@{ $fruhash->{board}->{extra} }) {
$fru = FRU->new();
$fru->rec_type("misc");
$fru->desc("Board Extra data");
if ($_->{encoding} == 3) {
$fru->value($_->{value});
} else {
next;
#print Dumper($_);
#print $_->{encoding};
$fru->value(phex($_->{value}));
}
$sessdata->{fru_hash}->{ $frudex++ } = $fru;
}
#Ok, done with fru 0, on to the other fru devices from SDR
$sessdata->{frudex} = $frudex;
if ($sessdata->{skipotherfru}) { #skip non-primary fru devices
if ($sessdata->{skipotherfru} and isopenpower($sessdata)) {
# For openpower Big Data servers, fru 2 has MTM/Serial and fru 43 has firmware information
# For openpower HPC servers, fru 3 has MTM/Serial and fru 47 has firmware information
@{$sessdata->{frus_for_openpower}} = qw(2 3 43 47);
my %fruids_hash = map {$_ => 1} @{$sessdata->{frus_for_openpower}};
foreach my $key (keys %{ $sessdata->{sdr_hash} }) {
my $sdr = $sessdata->{sdr_hash}->{$key};
unless ($sdr->rec_type == 0x11) {
next;
}
my $fru_id = $sdr->sensor_number;
if (defined($fruids_hash{$fru_id})) {
$sessdata->{sdr_info_for_openpower}->{$fru_id} = $sdr;
}
}
$sessdata->{currfruid} = shift @{$sessdata->{frus_for_openpower}};
$sessdata->{currfrusdr} = $sessdata->{sdr_info_for_openpower}->{$sessdata->{currfruid}};
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x10, data => [ $sessdata->{currfruid} ], callback => \&process_currfruid, callback_args => $sessdata);
return;
}
fru_initted($sessdata);
return;
}
my $key;
my $subrc;
my %sdr_hash = %{ $sessdata->{sdr_hash} };
$sessdata->{dimmfru} = [];
$sessdata->{genhwfru} = [];
foreach $key (sort { $sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string } keys %sdr_hash) {
my $sdr = $sdr_hash{$key};
unless ($sdr->rec_type == 0x11) { #skip non fru sdr stuff
next;
}
if ($sdr->fru_subtype == 0x1) { #DIMM
push @{ $sessdata->{dimmfru} }, $sdr;
} elsif ($sdr->fru_subtype == 0 or $sdr->fru_subtype == 2) {
push @{ $sessdata->{genhwfru} }, $sdr;
}
}
if (scalar @{ $sessdata->{dimmfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{dimmfru} };
while ($sessdata->{currfrusdr}->sensor_number == 0 and scalar @{ $sessdata->{dimmfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{dimmfru} };
}
if ($sessdata->{currfrusdr}->sensor_number != 0) {
$sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
$sessdata->{currfrutype} = "dimm";
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x10, data => [ $sessdata->{currfruid} ], callback => \&process_currfruid, callback_args => $sessdata);
return;
}
}
if (scalar @{ $sessdata->{genhwfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{genhwfru} };
while ($sessdata->{currfrusdr}->sensor_number == 0 and scalar @{ $sessdata->{genhwfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{genhwfru} };
}
if ($sessdata->{currfrusdr}->sensor_number != 0) {
$sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
$sessdata->{currfrutype} = "genhw";
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x10, data => [ $sessdata->{currfruid} ], callback => \&process_currfruid, callback_args => $sessdata);
return;
}
}
fru_initted($sessdata);
}
sub get_frusize {
my $fruid = shift;
my $netfun = 0x28; # Storage (0x0A << 2)
my @cmd = (0x10, $fruid);
my @bytes;
my $error = docmd($netfun, \@cmd, \@bytes);
pop @bytes;
unless (defined $bytes[0] and $bytes[0] == 0) {
if ($codes{ $bytes[0] }) {
return (0, $codes{ $bytes[0] });
}
return (0, "FRU device $fruid inaccessible");
}
return ($bytes[2] << 8) + $bytes[1];
}
sub formfru {
my $fruhash = shift;
my $frusize = shift;
$frusize -= 8; #consume 8 bytes for mandatory header
my $availindex = 1;
my @bytes = (1, 0, 0, 0, 0, 0, 0, 0); #
if ($fruhash->{internal}) { #Allocate the space at header time
$bytes[1] = $availindex;
$availindex += ceil((scalar @{ $fruhash->{internal} }) / 8);
$frusize -= (scalar @{ $fruhash->{internal} }); #consume internal bytes
push @bytes, @{ $fruhash->{internal} };
}
if ($fruhash->{chassis}) {
$bytes[2] = $availindex;
push @bytes, @{ $fruhash->{chassis}->{raw} };
$availindex += ceil((scalar @{ $fruhash->{chassis}->{raw} }) / 8);
$frusize -= ceil((scalar @{ $fruhash->{chassis}->{raw} }) / 8) * 8;
}
if ($fruhash->{board}) {
$bytes[3] = $availindex;
push @bytes, @{ $fruhash->{board}->{raw} };
$availindex += ceil((scalar @{ $fruhash->{board}->{raw} }) / 8);
$frusize -= ceil((scalar @{ $fruhash->{board}->{raw} }) / 8) * 8;
}
#xCAT will always have a product FRU in this process
$bytes[4] = $availindex;
unless (defined $fruhash->{product}) { #Make sure there is a data structure
#to latch onto..
$fruhash->{product} = {};
}
my @prodbytes = buildprodfru($fruhash->{product});
push @bytes, @prodbytes;
$availindex += ceil((scalar @prodbytes) / 8);
$frusize -= ceil((scalar @prodbytes) / 8) * 8;
#End of product fru setup
if ($fruhash->{extra}) {
$bytes[5] = $availindex;
push @bytes, @{ $fruhash->{extra} };
$frusize -= ceil((scalar @{ $fruhash->{extra} }) / 8) * 8;
#Don't need to track availindex anymore
}
$bytes[7] = dochksum([ @bytes[ 0 .. 6 ] ]);
if ($frusize < 0) {
return undef;
} else {
return \@bytes;
}
}
sub transfieldtobytes {
my $hashref = shift;
unless (defined $hashref) {
return (0xC0);
}
my @data;
my $size;
if ($hashref->{encoding} == 3) {
@data = unpack("C*", $hashref->{value});
} else {
@data = @{ $hashref->{value} };
}
$size = scalar(@data);
if ($size > 64) {
die "Field too large for IPMI FRU specification";
}
unshift(@data, $size | ($hashref->{encoding} << 6));
return @data;
}
sub mergefru {
my $sessdata = shift;
my $phash = shift; #Product hash
unless ($phash) { die "here" }
my $currnode = $sessdata->{node};
if ($vpdhash->{$currnode}->[0]->{mtm}) {
$phash->{model}->{encoding} = 3;
$phash->{model}->{value} = $vpdhash->{$currnode}->[0]->{mtm};
}
if ($vpdhash->{$currnode}->[0]->{serial}) {
$phash->{serialnumber}->{encoding} = 3;
$phash->{serialnumber}->{value} = $vpdhash->{$currnode}->[0]->{serial};
}
if ($vpdhash->{$currnode}->[0]->{asset}) {
$phash->{asset}->{encoding} = 3;
$phash->{asset}->{value} = $vpdhash->{$currnode}->[0]->{asset};
}
}
sub buildprodfru {
my $sessdata = shift;
my $prod = shift;
mergefru($sessdata, $prod);
my $currnode = $sessdata->{node};
my @bytes = (1, 0, 0);
my @data;
my $padsize;
push @bytes, transfieldtobytes($prod->{manufacturer});
push @bytes, transfieldtobytes($prod->{product});
push @bytes, transfieldtobytes($prod->{model});
push @bytes, transfieldtobytes($prod->{version});
push @bytes, transfieldtobytes($prod->{serialnumber});
push @bytes, transfieldtobytes($prod->{asset});
push @bytes, transfieldtobytes($prod->{fruid});
push @bytes, transfieldtobytes($prod->{fruid});
foreach (@{ $prod->{extra} }) {
my $sig = getascii(transfieldtobytes($_));
unless ($sig and $sig =~ /FRU by xCAT/) {
push @bytes, transfieldtobytes($_);
}
}
push @bytes, transfieldtobytes({ encoding => 3, value => "$currnode FRU by xCAT " . xCAT::Utils::Version('short') });
push @bytes, (0xc1);
$bytes[1] = ceil((scalar(@bytes) + 1) / 8);
$padsize = (ceil((scalar(@bytes) + 1) / 8) * 8) - scalar(@bytes) - 1;
while ($padsize--) {
push @bytes, (0x00);
}
$padsize = dochksum(\@bytes); #reuse padsize for a second to store checksum
push @bytes, $padsize;
return @bytes;
}
sub fru {
my $subcommand = shift;
my $netfun = 0x28;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my @output;
my $code;
@cmd = (0x10, 0x00);
$error = docmd(
$netfun,
\@cmd,
\@returnd
);
if ($error) {
$rc = 1;
$text = $error;
return ($rc, $text);
}
$code = $returnd[0];
if ($code == 0x00) {
}
else {
$rc = 1;
$text = $codes{$code};
}
if ($rc != 0) {
if (!$text) {
$text = sprintf("unknown response %02x", $code);
}
return ($rc, $text);
}
my $fru_size_ls = $returnd[1];
my $fru_size_ms = $returnd[2];
my $fru_size = $fru_size_ms * 256 + $fru_size_ls;
if ($subcommand eq "dump") {
print "FRU Size: $fru_size\n";
my ($rc, @output) = frudump(0, $fru_size, 8);
if ($rc) {
return ($rc, @output);
}
hexadump(\@output);
return (0, "");
}
if ($subcommand eq "wipe") {
my @bytes = ();
for (my $i = 0 ; $i < $fru_size ; $i++) {
push(@bytes, 0xff);
}
my ($rc, $text) = fruwrite(0, \@bytes, 8);
if ($rc) {
return ($rc, $text);
}
return (0, "FRU $fru_size bytes wiped");
}
return (0, "");
}
sub add_fruhash {
my $sessdata = shift;
my $fruhash;
if ($sessdata->{currfruid} != 0 and not ref $sessdata->{currfrudata}) {
my $fru = FRU->new();
if ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
$fru->rec_type("dimm,hw");
} else {
$fru->rec_type("hw");
}
$fru->value($sessdata->{currfrudata});
if ($sessdata->{currfrusdr}) {
$fru->desc($sessdata->{currfrusdr}->id_string);
}
$sessdata->{fru_hash}->{ $sessdata->{frudex} } = $fru;
$sessdata->{frudex} += 1;
} elsif ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
$fruhash = decode_spd(@{ $sessdata->{currfrudata} });
} else {
my $err;
$global_sessdata = $sessdata; #pass by global, evil, but practical this time
($err, $fruhash) = parsefru($sessdata->{currfrudata});
$global_sessdata = undef; #revert state of global
if ($err) {
my $fru = FRU->new();
if ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
$fru->rec_type("dimm,hw");
} else {
$fru->rec_type("hw");
}
$fru->value($err);
if ($sessdata->{currfrusdr}) {
$fru->desc($sessdata->{currfrusdr}->id_string);
}
if (exists($sessdata->{frudex})) {
$sessdata->{fru_hash}->{ $sessdata->{frudex} } = $fru;
$sessdata->{frudex} += 1;
}
undef $sessdata->{currfrudata}; #skip useless calls to add more frus when parsing failed miserably anyway
#xCAT::SvrUtils::sendmsg([1,":Error reading fru area ".$sessdata->{currfruid}.": $err"],$callback);
#return;
}
}
if ($sessdata->{currfruid} == 0) {
initfru_zero($sessdata, $fruhash);
return;
} elsif (ref $sessdata->{currfrudata}) {
if ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
add_textual_frus($fruhash, $sessdata->{currfrusdr}->id_string, "", "product", "dimm,hw", $sessdata);
} else {
add_textual_frus($fruhash, $sessdata->{currfrusdr}->id_string, "Board ", "board", undef, $sessdata);
add_textual_frus($fruhash, $sessdata->{currfrusdr}->id_string, "Product ", "product", undef, $sessdata);
add_textual_frus($fruhash, $sessdata->{currfrusdr}->id_string, "Chassis ", "chassis", undef, $sessdata);
}
}
if ($sessdata->{skipotherfru}) {
if (scalar @{$sessdata->{frus_for_openpower}}) {
$sessdata->{currfruid} = shift @{$sessdata->{frus_for_openpower}};
$sessdata->{currfrusdr} = $sessdata->{sdr_info_for_openpower}->{$sessdata->{currfruid}};
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x10, data => [ $sessdata->{currfruid} ], callback => \&process_currfruid, callback_args => $sessdata);
return;
}
fru_initted($sessdata);
return;
}
if (scalar @{ $sessdata->{dimmfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{dimmfru} };
while ($sessdata->{currfrusdr}->sensor_number == 0 and scalar @{ $sessdata->{dimmfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{dimmfru} };
}
if ($sessdata->{currfrusdr}->sensor_number != 0) {
$sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
$sessdata->{currfrutype} = "dimm";
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x10, data => [ $sessdata->{currfruid} ], callback => \&process_currfruid, callback_args => $sessdata);
return;
}
}
if (scalar @{ $sessdata->{genhwfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{genhwfru} };
while ($sessdata->{currfrusdr}->sensor_number == 0 and scalar @{ $sessdata->{genhwfru} }) {
$sessdata->{currfrusdr} = shift @{ $sessdata->{genhwfru} };
}
if ($sessdata->{currfrusdr}->sensor_number != 0) {
$sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
$sessdata->{currfrutype} = "genhw";
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x10, data => [ $sessdata->{currfruid} ], callback => \&process_currfruid, callback_args => $sessdata);
return;
}
}
fru_initted($sessdata);
}
sub readcurrfrudevice {
my $rsp = shift;
my $sessdata = shift;
my $chunk = 16; #we have no idea how much will be supported to grab at a time, stick to 16 as a magic number for the moment
if (not ref $rsp) {
$sessdata->{currfruoffset} = 0;
$sessdata->{currfrudata} = [];
$sessdata->{currfrudone} = 0;
$sessdata->{currfruchunk} = 16;
} else {
if ($rsp->{code} != 0xcb and check_rsp_errors($rsp, $sessdata)) {
return;
} elsif ($rsp->{code} == 0xcb) {
$sessdata->{currfrudata} = "Not Present";
$sessdata->{currfrudone} = 1;
add_fruhash($sessdata);
return;
}
my @data = @{ $rsp->{data} };
if ($data[0] != $sessdata->{currfruchunk}) {
# Fix FRU 43,48 and 49 for GRS server that they can not return as much data as shall return
if ($data[0] ge 0) {
$sessdata->{currfrudone} = 1;
} else {
my $text = "Received incorrect data from BMC for FRU ID: " . $sessdata->{currfruid};
xCAT::SvrUtils::sendmsg($text, $callback, $sessdata->{node}, %allerrornodes);
add_fruhash($sessdata);
return;
}
}
shift @data;
push @{ $sessdata->{currfrudata} }, @data;
if ($sessdata->{currfrudone}) {
if ($sessdata->{isite} and $sessdata->{currfrusdr} and ($sessdata->{currfrusdr}->fru_oem & 0x80)) {
#IBM OEM command, d0,51,0 further qualifies the command name, we'll first take a stop at block 0, offset 2, one byte, to get VPD version number
#command structured as:
#d0,51,0 = command set identifier
#lsb of offset
#msb of offset
#address type (1 for fru id)
#address (fru id for our use)
#1 - fixed value
#lsb - size
#msb - size
#vpd_base_specivication_ver2.x
$sessdata->{ipmisession}->subcmd(netfn => 0x2e, command => 0x51, data => [ 0xd0, 0x51, 0, 0x2, 0x0, 1, $sessdata->{currfruid}, 1, 1, 0 ], callback => \&got_vpd_version, callback_args => $sessdata);
} else {
add_fruhash($sessdata);
}
return;
}
}
my $ms = $sessdata->{currfruoffset} >> 8;
my $ls = $sessdata->{currfruoffset} & 0xff;
if ($sessdata->{currfruoffset} + 16 >= $sessdata->{currfrusize}) {
$chunk = $sessdata->{currfrusize} - $sessdata->{currfruoffset}; # shrink chunk to only get the remainder data
$sessdata->{currfrudone} = 1;
} else {
$sessdata->{currfruoffset} += $chunk;
}
$sessdata->{currfruchunk} = $chunk;
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x11, data => [ $sessdata->{currfruid}, $ls, $ms, $chunk ], callback => \&readcurrfrudevice, callback_args => $sessdata);
}
sub got_vpd_version {
my $rsp = shift;
my $sessdata = shift;
unless ($rsp and not $rsp->{error} and $rsp->{code} == 0 and $rsp->{data}->[5] == 2) { #unless the query was successful and major vpd version was 2
#short over to adding the fru hash as-is
add_fruhash($sessdata);
return;
}
#making it this far, we have affirmative confirmation of ibm oem vpd data, time to chase component mac, wwpn, and maybe mezz firmware
#will need:
# block 0, offset 0c8h use the offset to add to block 1 offsets (usually 400h), denoting as $blone
# block 1, $blone+6 - 216 bytes: 6 sets of 36 byte version information (TODO)
# block 1, $blone+0x1d0: port type first 4 bits protocol, last 3 bits addressing
# block 1, $blone+0x240: 64 bytes, up to 8 sets of addresses, mac is left aligned
# block 1, $blone+0x300: if mac+wwn, grab wwn
$sessdata->{ipmisession}->subcmd(netfn => 0x2e, command => 0x51, data => [ 0xd0, 0x51, 0, 0xc8, 0x0, 1, $sessdata->{currfruid}, 1, 2, 0 ], callback => \&got_vpd_block1, callback_args => $sessdata);
}
sub got_vpd_block1 {
my $rsp = shift;
my $sessdata = shift;
unless ($rsp and not $rsp->{error} and $rsp->{code} == 0) { # if this should go wonky, jump ahead
add_fruhash($sessdata);
return;
}
$sessdata->{vpdblock1offset} = $rsp->{data}->[5] << 8 + $rsp->{data}->[6];
my $ptoffset = $sessdata->{vpdblock1offset} + 0x1d0;
$sessdata->{ipmisession}->subcmd(netfn => 0x2e, command => 0x51, data => [ 0xd0, 0x51, 0, $ptoffset & 0xff, $ptoffset >> 8, 1, $sessdata->{currfruid}, 1, 1, 0 ], callback => \&got_portaddr_type, callback_args => $sessdata);
}
sub got_portaddr_type {
my $rsp = shift;
my $sessdata = shift;
unless ($rsp and not $rsp->{error} and $rsp->{code} == 0) { # if this should go wonky, jump ahead
add_fruhash($sessdata);
return;
}
my $addrtype = $rsp->{data}->[5] & 0b111;
if ($addrtype == 0b101) {
$sessdata->{needmultiaddr} = 1;
$sessdata->{curraddrtype} = "mac";
} elsif ($addrtype == 0b1) {
$sessdata->{curraddrtype} = "mac";
} elsif ($addrtype == 0b10) {
$sessdata->{curraddrtype} = "wwn";
} else { #for now, skip polling addresses I haven't examined directly
add_fruhash($sessdata);
return;
}
my $addroffset = $sessdata->{vpdblock1offset} + 0x240;
$sessdata->{ipmisession}->subcmd(netfn => 0x2e, command => 0x51, data => [ 0xd0, 0x51, 0, $addroffset & 0xff, $addroffset >> 8, 1, $sessdata->{currfruid}, 1, 64, 0 ], callback => \&got_vpd_addresses, callback_args => $sessdata);
}
sub got_vpd_addresses {
my $rsp = shift;
my $sessdata = shift;
unless ($rsp and not $rsp->{error} and $rsp->{code} == 0) { # if this should go wonky, jump ahead
add_fruhash($sessdata);
return;
}
my @addrdata = @{ $rsp->{data} };
splice @addrdata, 0, 5; # remove the header info
my $macstring = "1";
while ($macstring !~ /^00:00:00:00:00:00/) {
my @currmac = splice @addrdata, 0, 8;
unless ((scalar @currmac) == 8) {
last;
}
$macstring = sprintf("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", @currmac);
if ($macstring =~ /^00:00:00:00:00:00:00:00/) {
last;
}
if ($sessdata->{curraddrtype} eq "mac") {
$macstring =~ s/:..:..$//;
push @{ $sessdata->{currmacs} }, $macstring;
} elsif ($sessdata->{curraddrtype} eq "wwn") {
push @{ $sessdata->{currwwns} }, $macstring;
}
}
if ($sessdata->{needmultiaddr}) {
$sessdata->{needmultiaddr} = 0;
$sessdata->{curraddrtype} = "wwn";
my $addroffset = $sessdata->{vpdblock1offset} + 0x300;
$sessdata->{ipmisession}->subcmd(netfn => 0x2e, command => 0x51, data => [ 0xd0, 0x51, 0, $addroffset & 0xff, $addroffset >> 8, 1, $sessdata->{currfruid}, 1, 64, 0 ], callback => \&got_vpd_addresses, callback_args => $sessdata);
return;
}
add_fruhash($sessdata);
}
sub parsefru {
my $bytes = shift;
my $fruhash;
my $curridx; #store indexes as needed for convenience
my $currsize; #store current size
my $subidx;
my @currarea;
unless (ref $bytes) {
return $bytes, undef;
}
if (!defined $bytes->[0]) {
return "clear", undef;
}
unless ($bytes->[0] == 1) {
if ($bytes->[0] == 0 or $bytes->[0] == 0xff) { #not in spec, but probably unitialized, xCAT probably will rewrite fresh
return "clear", undef;
} else { #some meaning suggested, but not parsable, xCAT shouldn't meddle
return "Unrecognized FRU format", undef;
}
}
elsif (!$bytes->[1] and !$bytes->[2] and !$bytes->[3] and !$bytes->[4] and !$bytes->[5]) {
return "No data available", undef;
}
if ($bytes->[1]) { #The FRU spec, unfortunately, gave no easy way to tell the size of internal area
#consequently, will find the next defined field and preserve the addressing and size of current FRU
#area until then
my $internal_size;
if ($bytes->[2]) {
$internal_size = $bytes->[2] * 8 - ($bytes->[1] * 8);
} elsif ($bytes->[3]) {
$internal_size = $bytes->[3] * 8 - ($bytes->[1] * 8);
} elsif ($bytes->[4]) {
$internal_size = $bytes->[4] * 8 - ($bytes->[1] * 8);
} elsif ($bytes->[5]) {
$internal_size = $bytes->[5] * 8 - ($bytes->[1] * 8);
} else { #The FRU area is intact enough to signify xCAT can't safely manipulate contents
return "unknown-winternal", undef;
}
#capture slice of bytes
$fruhash->{internal} = [ @{$bytes}[ ($bytes->[1] * 8) .. ($bytes->[1] * 8 + $internal_size - 1) ] ]; #,$bytes->[1]*8,$internal_size];
}
if ($bytes->[2]) { #Chassis info area, xCAT will preserve fields, not manipulate them
$curridx = $bytes->[2] * 8;
unless ($bytes->[$curridx] == 1) { #definitely unparsable, but the section is preservable
return "unknown-COULDGUESS", undef; #be lazy for now, TODO revisit this and add guessing if it ever matters
}
$currsize = ($bytes->[ $curridx + 1 ]) * 8;
if ($currsize > 0) {
@currarea = @{$bytes}[ $curridx .. ($curridx + $currsize - 1) ]; #splice @$bytes,$curridx,$currsize;
$fruhash->{chassis} = parsechassis(@currarea);
}
}
if ($bytes->[3]) { #Board info area, to be preserved
$curridx = $bytes->[3] * 8;
unless ($bytes->[$curridx] == 1) {
return "unknown-COULDGUESS", undef;
}
$currsize = ($bytes->[ $curridx + 1 ]) * 8;
# some systems have malformed board info
# Just in case, give the board area parser access to end
# of total area unless product or multirecord is there
my $endidx = $#{$bytes};
if ($bytes->[4] or $bytes->[5]) {
$endidx = $curridx + $currsize - 1;
}
if ($currsize > 0) {
@currarea = @{$bytes}[ $curridx .. $endidx ];
$fruhash->{board} = parseboard(@currarea);
}
}
if (ref $global_sessdata->{currmacs}) {
$fruhash->{board}->{macaddrs} = [];
push @{ $fruhash->{board}->{macaddrs} }, @{ $global_sessdata->{currmacs} };
delete $global_sessdata->{currmacs}; # consume the accumulated mac addresses to avoid afflicting subsequent fru
}
if (ref $global_sessdata->{currwwns}) {
push @{ $fruhash->{board}->{wwns} }, @{ $global_sessdata->{currwwns} };
delete $global_sessdata->{currwwns}; # consume wwns
}
if ($bytes->[4]) { #Product info area present, will probably be thoroughly modified
$curridx = $bytes->[4] * 8;
unless ($bytes->[$curridx] == 1) {
return "unknown-COULDGUESS", undef;
}
$currsize = ($bytes->[ $curridx + 1 ]) * 8;
if ($currsize > 0) {
@currarea = @{$bytes}[ $curridx .. ($curridx + $currsize - 1) ];
$fruhash->{product} = parseprod(@currarea);
}
}
if ($bytes->[5]) { #Generic multirecord present..
$fruhash->{extra} = [];
my $last = 0;
$curridx = $bytes->[5] * 8;
my $currsize;
if ($bytes->[$curridx] <= 5) { #don't even try to parse unknown stuff
#some records don't comply to any SPEC
while (not $last and $curridx < (scalar @$bytes)) {
if ($bytes->[ $curridx + 1 ] & 128) {
$last = 1;
}
$currsize = $bytes->[ $curridx + 2 ];
push @{ $fruhash->{extra} }, @{$bytes}[ $curridx .. $curridx + 4 + $currsize - 1 ];
$curridx += 5 + $currsize;
}
}
}
return 0, $fruhash;
}
sub parseprod {
my @area = @_;
my %info;
my $language = $area[2];
my $idx = 3;
my $currsize;
my $currdata;
my $encode;
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%info;
}
$idx += $currsize;
if ($currsize > 1) {
$info{manufacturer}->{encoding} = $encode;
$info{manufacturer}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%info;
}
$idx += $currsize;
if ($currsize > 1) {
$info{product}->{encoding} = $encode;
$info{product}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%info;
}
$idx += $currsize;
if ($currsize > 1) {
$info{model}->{encoding} = $encode;
$info{model}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%info;
}
$idx += $currsize;
if ($currsize > 1) {
$info{version}->{encoding} = $encode;
$info{version}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%info;
}
$idx += $currsize;
if ($currsize > 1) {
$info{serialnumber}->{encoding} = $encode;
$info{serialnumber}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%info;
}
$idx += $currsize;
if ($currsize > 1) {
$info{asset}->{encoding} = $encode;
$info{asset}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%info;
}
$idx += $currsize;
if ($currsize > 1) {
$info{fruid}->{encoding} = $encode;
$info{fruid}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
if ($currsize) {
$info{extra} = [];
}
while ($currsize > 0) {
if ($currsize > 1) {
push @{ $info{extra} }, { value => $currdata, encoding => $encode };
}
$idx += $currsize;
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
if ($currsize < 0) { last }
}
return \%info;
}
sub parseboard {
my @area = @_;
my %boardinf;
my $idx = 6;
my $language = $area[2];
my $tstamp = ($area[3] + ($area[4] << 8) + ($area[5] << 16)) * 60 + 820472400; #820472400 is meant to be 1/1/1996
$boardinf{raw} = [@area]; #store for verbatim replacement
unless ($tstamp == 820472400) {
$boardinf{builddate} = scalar localtime($tstamp);
}
my $encode;
my $currsize;
my $currdata;
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%boardinf;
}
$idx += $currsize;
if ($currsize > 1) {
$boardinf{manufacturer}->{encoding} = $encode;
$boardinf{manufacturer}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%boardinf;
}
$idx += $currsize;
if ($currsize > 1) {
$boardinf{name}->{encoding} = $encode;
$boardinf{name}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%boardinf;
}
$idx += $currsize;
if ($currsize > 1) {
$boardinf{serialnumber}->{encoding} = $encode;
$boardinf{serialnumber}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%boardinf;
}
$idx += $currsize;
if ($currsize > 1) {
$boardinf{partnumber}->{encoding} = $encode;
$boardinf{partnumber}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
unless ($currsize) {
return \%boardinf;
}
$idx += $currsize;
if ($currsize > 1) {
$boardinf{fruid}->{encoding} = $encode;
$boardinf{fruid}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
if ($currsize) {
$boardinf{extra} = [];
}
while ($currsize > 0) {
if ($currsize > 1) {
push @{ $boardinf{extra} }, { value => $currdata, encoding => $encode };
}
$idx += $currsize;
($currsize, $currdata, $encode) = extractfield(\@area, $idx);
if ($currsize < 0) { last }
}
if ($global_sessdata->{isanimm}) { #we can understand more specifically some of the extra fields...
$boardinf{frunum} = $boardinf{extra}->[0]->{value};
$boardinf{revision} = $boardinf{extra}->[4]->{value};
#time to process the mac field...
my $macdata = $boardinf{extra}->[6]->{value};
my $macstring = "1";
my $macprefix;
while ($macdata and $macstring !~ /00:00:00:00:00:00/ and not ref $global_sessdata->{currmacs}) {
my @currmac = splice @$macdata, 0, 6;
unless ((scalar @currmac) == 6) {
last;
}
$macstring = sprintf("%02x:%02x:%02x:%02x:%02x:%02x", @currmac);
unless ($macprefix) {
$macprefix = substr($macstring, 0, 8);
}
if ($macstring !~ /00:00:00:00:00:00/ and $macstring =~ /^$macprefix/) {
push @{ $boardinf{macaddrs} }, $macstring;
}
}
delete $boardinf{extra};
}
return \%boardinf;
}
sub parsechassis {
my @chassarea = @_;
my %chassisinf;
my $currsize;
my $currdata;
my $idx = 3;
my $encode;
$chassisinf{raw} = [@chassarea]; #store for verbatim replacement
$chassisinf{type} = "unknown";
if ($chassis_types{ $chassarea[2] }) {
$chassisinf{type} = $chassis_types{ $chassarea[2] };
}
if ($chassarea[$idx] == 0xc1) {
return \%chassisinf;
}
($currsize, $currdata, $encode) = extractfield(\@chassarea, $idx);
unless ($currsize) {
return \%chassisinf;
}
$idx += $currsize;
if ($currsize > 1) {
$chassisinf{partnumber}->{encoding} = $encode;
$chassisinf{partnumber}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@chassarea, $idx);
unless ($currsize) {
return \%chassisinf;
}
$idx += $currsize;
if ($currsize > 1) {
$chassisinf{serialnumber}->{encoding} = $encode;
$chassisinf{serialnumber}->{value} = $currdata;
}
($currsize, $currdata, $encode) = extractfield(\@chassarea, $idx);
if ($currsize) {
$chassisinf{extra} = [];
}
while ($currsize > 0) {
if ($currsize > 1) {
push @{ $chassisinf{extra} }, { value => $currdata, encoding => $encode };
}
$idx += $currsize;
($currsize, $currdata, $encode) = extractfield(\@chassarea, $idx);
if ($currsize < 0) { last }
}
return \%chassisinf;
}
sub extractfield { #idx is location of the type/length byte, returns something appropriate
my $area = shift;
my $idx = shift;
my $language = shift;
my $data;
if ($idx >= scalar @$area) {
# The global_sessdata store the sessdata for a node when parsefru, and it is cleaned after parsefru
xCAT::SvrUtils::sendmsg([ 1, "Error encountered when parsing FRU data from BMC" ], $callback, $global_sessdata->{node}, %allerrornodes);
return 0, undef, undef;
}
my $size = $area->[$idx] & 0b00111111;
my $encoding = ($area->[$idx] & 0b11000000) >> 6;
unless ($size) {
return 1, undef, undef;
}
if ($size == 1 && $encoding == 3) {
return 0, '', '';
}
if ($encoding == 3) {
$data = getascii(@$area[ $idx + 1 .. $size + $idx ]);
} else {
$data = [ @$area[ $idx + 1 .. $size + $idx ] ];
}
return $size + 1, $data, $encoding;
}
sub writefru {
my $netfun = 0x28; # Storage (0x0A << 2)
my @cmd = (0x10, 0);
my @bytes;
my $error = docmd($netfun, \@cmd, \@bytes);
pop @bytes;
unless (defined $bytes[0] and $bytes[0] == 0) {
return (1, "FRU device 0 inaccessible");
}
my $frusize = ($bytes[2] << 8) + $bytes[1];
($error, @bytes) = frudump(0, $frusize, 16);
if ($error) {
return (1, "Error retrieving FRU: " . $error);
}
my $fruhash;
($error, $fruhash) = parsefru(\@bytes);
my $newfru = formfru($fruhash, $frusize);
unless ($newfru) {
return (1, "FRU data will not fit in BMC FRU space, fields too long");
}
my $rc = 1;
my $writeattempts = 0;
my $text;
while ($rc and $writeattempts < 15) {
if ($writeattempts) {
sleep 1;
}
($rc, $text) = fruwrite(0, $newfru, 8);
if ($text =~ /rotected/) {
last;
}
$writeattempts++;
}
if ($rc) {
return ($rc, $text);
}
return (0, "FRU Updated");
}
sub fruwrite {
my $offset = shift;
my $bytes = shift;
my $chunk = shift;
my $length = @$bytes;
my $netfun = 0x28;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my @output;
my $code;
my @fru_data = ();
for (my $c = $offset ; $c < $length + $offset ; $c += $chunk) {
my $ms = int($c / 0x100);
my $ls = $c - $ms * 0x100;
@cmd = (0x12, 0x00, $ls, $ms, @$bytes[ $c - $offset .. $c - $offset + $chunk - 1 ]);
$error = docmd(
$netfun,
\@cmd,
\@returnd
);
if ($error) {
$rc = 1;
$text = $error;
return ($rc, $text);
}
$code = $returnd[0];
if ($code == 0x00) {
}
else {
$rc = 1;
$text = $codes{$code};
}
if ($rc != 0) {
if ($code == 0x80) {
$text = "Write protected FRU";
}
if (!$text) {
$text = sprintf("unknown response %02x", $code);
}
return ($rc, $text);
}
my $count = $returnd[1];
if ($count != $chunk) {
$rc = 1;
$text = "FRU write error (bytes requested: $chunk, wrote: $count)";
return ($rc, $text);
}
}
return (0);
}
sub decodealert {
my $sessdata = shift;
my $skip_sdrinit = 0;
unless (ref $sessdata) { #called from xcat traphandler
$sessdata = { sdr_hash => {} };
$skip_sdrinit = 1; #TODO sdr_init, cache only to avoid high trap handling overhead
}
my $trap = shift;
if ($trap =~ /xCAT_plugin::ipmi/) {
$trap = shift;
$skip_sdrinit = 1;
}
my $node = shift;
my @pet = @_;
my $rc;
my $text;
my $type;
my $desc;
#my $ipmisensoreventtab = "$ENV{XCATROOT}/lib/GUMI/ipmisensorevent.tab";
#my $ipmigenericeventtab = "$ENV{XCATROOT}/lib/GUMI/ipmigenericevent.tab";
my $offsetmask = 0b00000000000000000000000000001111;
my $offsetrmask = 0b00000000000000000000000001110000;
my $assertionmask = 0b00000000000000000000000010000000;
my $eventtypemask = 0b00000000000000001111111100000000;
my $sensortypemask = 0b00000000111111110000000000000000;
my $reservedmask = 0b11111111000000000000000000000000;
my $offset = $trap & $offsetmask;
my $offsetr = $trap & $offsetrmask;
my $event_dir = $trap & $assertionmask;
my $event_type = ($trap & $eventtypemask) >> 8;
my $sensor_type = ($trap & $sensortypemask) >> 16;
my $reserved = ($trap & $reservedmask) >> 24;
if ($debug >= 2) {
printf("offset: %02xh\n", $offset);
printf("offsetr: %02xh\n", $offsetr);
printf("assertion: %02xh\n", $event_dir);
printf("eventtype: %02xh\n", $event_type);
printf("sensortype: %02xh\n", $sensor_type);
printf("reserved: %02xh\n", $reserved);
}
my @hex = (0, @pet);
my $pad = $hex[0];
my @uuid = @hex[ 1 .. 16 ];
my @seqnum = @hex[ 17, 18 ];
my @timestamp = @hex[ 19, 20, 21, 22 ];
my @utcoffset = @hex[ 23, 24 ];
my $trap_source_type = $hex[25];
my $event_source_type = $hex[26];
my $sev = $hex[27];
my $sensor_device = $hex[28];
my $sensor_num = $hex[29];
my $entity_id = $hex[30];
my $entity_instance = $hex[31];
my $event_data_1 = $hex[32];
my $event_data_2 = $hex[33];
my $event_data_3 = $hex[34];
my @event_data = @hex[ 35 .. 39 ];
my $langcode = $hex[40];
my $mfg_id = $hex[41] + $hex[42] * 0x100 + $hex[43] * 0x10000 + $hex[44] * 0x1000000;
my $prod_id = $hex[45] + $hex[46] * 0x100;
my @oem = $hex[ 47 .. @hex - 1 ];
if ($sev == 0x00) {
$sev = "LOG";
}
elsif ($sev == 0x01) {
$sev = "MONITOR";
}
elsif ($sev == 0x02) {
$sev = "INFORMATION";
}
elsif ($sev == 0x04) {
$sev = "OK";
}
elsif ($sev == 0x08) {
$sev = "WARNING";
}
elsif ($sev == 0x10) {
$sev = "CRITICAL";
}
elsif ($sev == 0x20) {
$sev = "NON-RECOVERABLE";
}
else {
$sev = "UNKNOWN-SEVERITY:$sev";
}
$text = "$sev:";
($rc, $type, $desc) = getsensorevent($sensor_type, $offset, "ipmisensorevents");
if ($rc == 1) {
$type = "Unknown Type $sensor_type";
$desc = "Unknown Event $offset";
$rc = 0;
}
if ($event_type <= 0x0c) {
my $gtype;
my $gdesc;
($rc, $gtype, $gdesc) = getsensorevent($event_type, $offset, "ipmigenericevents");
if ($rc == 1) {
$gtype = "Unknown Type $gtype";
$gdesc = "Unknown Event $offset";
$rc = 0;
}
$desc = $gdesc;
}
if ($type eq "" || $type eq "-") {
$type = "OEM Sensor Type $sensor_type"
}
if ($desc eq "" || $desc eq "-") {
$desc = "OEM Sensor Event $offset"
}
if ($type eq $desc) {
$desc = "";
}
my $extra_info = getaddsensorevent($sensor_type, $offset, $event_data_1, $event_data_2, $event_data_3);
if ($extra_info) {
if ($desc) {
$desc = "$desc $extra_info";
}
else {
$desc = "$extra_info";
}
}
$text = "$text $type,";
$text = "$text $desc";
my $key;
my $sensor_desc = sprintf("Sensor 0x%02x", $sensor_num);
my %sdr_hash = %{ $sessdata->{sdr_hash} };
foreach $key (keys %sdr_hash) {
my $sdr = $sdr_hash{$key};
if ($sdr->sensor_number == $sensor_num and $sdr->rec_type != 192 and $sdr->rec_type != 17) {
$sensor_desc = $sdr_hash{$key}->id_string;
if ($sdr->rec_type == 0x01) {
last;
}
}
}
$text = "$text ($sensor_desc)";
if ($event_dir) {
$text = "$text - Recovered";
}
return (0, $text);
}
sub readauxentry {
my $netfn = 0x2e << 2;
my $entrynum = shift;
my $entryls = ($entrynum & 0xff);
my $entryms = ($entrynum >> 8);
my @cmd = (0x93, 0x4d, 0x4f, 0x00, $entryls, $entryms, 0, 0, 0xff, 0x5); #Get log size andup to 1275 bytes of data, keeping it under 1500 to accomodate mixed-mtu circumstances
my @data;
my $error = docmd(
$netfn,
\@cmd,
\@data
);
if ($error) { return $error; }
if ($data[0]) { return $data[0]; }
my $text;
unless ($data[1] == 0x4d and $data[2] == 0x4f and $data[3] == 0) { return "Unrecognized response format" }
$entrynum = $data[6] + ($data[7] << 8);
if (($data[10] & 1) == 1) {
$text = "POSSIBLY INCOMPLETE DATA FOLLOWS:\n";
}
my $addtext = "";
if ($data[5] > 5) {
$addtext = "\nTODO:SUPPORT MORE DATA THAT WAS SEEN HERE";
}
@data = splice @data, 11;
pop @data;
while (scalar(@data)) {
my @subdata = splice @data, 0, 30;
my $numbytes = scalar(@subdata);
my $formatstring = "%02x" x $numbytes;
$formatstring =~ s/%02x%02x/%02x%02x /g;
$text .= sprintf($formatstring . "\n", @subdata);
}
$text .= $addtext;
return (0, $entrynum, $text);
}
sub eventlog {
my $sessdata = shift;
my $subcommand = $sessdata->{subcommand};
my $cmdargv = $sessdata->{extraargs};
unless ($sessdata) { die "not fixed yet" }
my $netfun = 0x0a;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my $code;
my @output;
my $entry;
$sessdata->{fullsel} = 0;
my @sel;
my $mfg_id;
my $prod_id;
my $device_id;
#device id needed here
$rc = 0;
#reventlog <node> [[all] [-s] | <num> [-s]| clear]
$subcommand = undef;
my $arg = shift(@$cmdargv);
while ($arg) {
if ($arg eq "all" or $arg eq "clear" or $arg =~ /^\d+$/) {
if (defined($subcommand)) {
return (1, "revenglog $subcommand $arg invalid");
}
$subcommand = $arg;
} elsif ($arg =~ /^-s$/) {
$sessdata->{sort} = 1;
} else {
return (1, "unsupported command eventlog $arg");
}
$arg = shift(@$cmdargv);
}
unless (defined($subcommand)) {
$subcommand = 'all';
}
if ($subcommand eq "all") {
$sessdata->{fullsel} = 1;
}
elsif ($subcommand eq "clear") {
if (exists($sessdata->{sort})) {
return (1, "option \"first\" can not work with $subcommand");
}
}
elsif ($subcommand =~ /^\d+$/) {
$sessdata->{numevents} = $subcommand;
$sessdata->{displayedevents} = 0;
}
else {
return (1, "unsupported command eventlog $subcommand");
}
$sessdata->{subcommand} = $subcommand;
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x48, data => [], callback => \&eventlog_with_time, callback_args => $sessdata);
}
sub eventlog_with_time {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
my @returnd = (0, @{ $rsp->{data} });
#Here we set tfactor based on the delta between the BMC reported time and our
#time. The IPMI spec says the BMC should return seconds since 1970 in local
#time, but the reality is the firmware pushing to the BMC has no context
#to know, so here we guess and adjust all timestamps based on delta between
#our now and the BMC's now
$sessdata->{tfactor} = $returnd[4] << 24 | $returnd[3] << 16 | $returnd[2] << 8 | $returnd[1];
if ($sessdata->{tfactor} > 0x20000000) {
$sessdata->{tfactor} -= time();
} else {
$sessdata->{tfactor} = 0;
}
$sessdata->{ipmisession}->subcmd(netfn => 0x0a, command => 0x40, data => [], callback => \&eventlog_with_selinfo, callback_args => $sessdata);
}
sub eventlog_with_selinfo {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
my $code = $rsp->{code};
my @returnd = (0, @{ $rsp->{data} });
#sif($code == 0x81) {
# $rc = 1;
# $text = "cannot execute command, SEL erase in progress";
#}
my $sel_version = $returnd[1];
if ($sel_version != 0x51) {
xCAT::SvrUtils::sendmsg(sprintf("SEL version 51h support only, version reported: %x", $sel_version), $callback, $sessdata->{node}, %allerrornodes);
return;
}
hexdump(\@returnd);
my $num_entries = ($returnd[3] << 8) + $returnd[2];
if ($num_entries <= 0) {
xCAT::SvrUtils::sendmsg("no SEL entries", $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $canres = $returnd[14] & 0b00000010;
if (!$canres) {
xCAT::SvrUtils::sendmsg([ 1, "SEL reservation not supported" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my $subcommand = $sessdata->{subcommand};
if ($subcommand =~ /clear/) { #Don't bother with a reservation unless a clear is involved
#atomic SEL retrieval need not require it, so an event during retrieval will not kill reventlog effort off
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x42, data => [], callback => \&clear_sel_with_reservation, callback_args => $sessdata);
return;
} elsif ($sessdata->{mfg_id} == 2) {
#read_ibm_auxlog($sessdata); #TODO JBJ fix this back in
#return;
#For requests other than clear, we check for IBM extended auxillary log data
}
$sessdata->{selentries} = [];
$sessdata->{selentry} = 0;
if (exists($sessdata->{sort})) {
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x43, data => [ 0, 0, 0xFF, 0xFF, 0x00, 0xFF ], callback => \&got_sel, callback_args => $sessdata);
} else {
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x43, data => [ 0, 0, 0x00, 0x00, 0x00, 0xFF ], callback => \&got_sel, callback_args => $sessdata);
}
}
sub got_sel {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
my @returnd = (0, @{ $rsp->{data} });
#elsif($code == 0x81) {
# $rc = 1;
# $text = "cannot execute command, SEL erase in progress";
#}
my $next_rec_ls;
my $next_rec_ms;
my @sel_data = @returnd[ 3 .. 19 ];
if (exists($sessdata->{sort})) {
$next_rec_ls = $sel_data[0] - 1;
$next_rec_ms = $sel_data[1];
if (($next_rec_ls < 0) && ($next_rec_ms > 0)) {
$next_rec_ls += 256;
$next_rec_ms -= 1;
}
} else {
$next_rec_ls = $returnd[1];
$next_rec_ms = $returnd[2];
}
$sessdata->{selentry} += 1;
if ($debug) {
print $sessdata->{selentry} . ": ";
hexdump(\@sel_data);
}
my $record_id = $sel_data[0] + $sel_data[1] * 256;
my $record_type = $sel_data[2];
if ($record_type == 0x02) {
}
else {
my $text = getoemevent($record_type, $sessdata->{mfg_id}, \@sel_data);
my $entry = $sessdata->{selentry};
if ($sessdata->{auxloginfo} and $sessdata->{auxloginfo}->{$entry}) {
$text .= " With additional data:\n" . $sessdata->{auxloginfo}->{$entry};
}
if ($sessdata->{fullsel}) {
xCAT::SvrUtils::sendmsg($text, $callback, $sessdata->{node}, %allerrornodes);
} else {
push(@{ $sessdata->{selentries} }, $text);
}
if (($next_rec_ms == 0xFF && $next_rec_ls == 0xFF) or
($next_rec_ms == 0x0 && $next_rec_ls == 0x0)) {
sendsel($sessdata);
return;
}
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x43, data => [ 0, 0, $next_rec_ls, $next_rec_ms, 0x00, 0xFF ], callback => \&got_sel, callback_args => $sessdata);
return;
}
my $timestamp = ($sel_data[3] | $sel_data[4] << 8 | $sel_data[5] << 16 | $sel_data[6] << 24);
unless ($timestamp < 0x20000000) { #IPMI Spec says below this is effectively BMC uptime, not correctable
$timestamp -= $sessdata->{tfactor}; #apply correction factor based on how off the current BMC clock is from management server
}
my ($seldate, $seltime) = timestamp2datetime($timestamp);
# $text = "$entry: $seldate $seltime";
my $text = ":$seldate $seltime";
# my $gen_id_slave_addr = ($sel_data[7] & 0b11111110) >> 1;
# my $gen_id_slave_addr_hs = ($sel_data[7] & 0b00000001);
# my $gen_id_ch_num = ($sel_data[8] & 0b11110000) >> 4;
# my $gen_id_ipmb = ($sel_data[8] & 0b00000011);
my $sensor_owner_id = $sel_data[7];
my $sensor_owner_lun = $sel_data[8];
my $sensor_type = $sel_data[10];
my $sensor_num = $sel_data[11];
my $event_dir = $sel_data[12] & 0b10000000;
my $event_type = $sel_data[12] & 0b01111111;
my $offset = $sel_data[13] & 0b00001111;
my $event_data_1 = $sel_data[13];
my $event_data_2 = $sel_data[14];
my $event_data_3 = $sel_data[15];
my $sev = 0;
$sev = ($sel_data[14] & 0b11110000) >> 4;
# if($event_type != 1) {
# $sev = ($sel_data[14] & 0b11110000) >> 4;
# }
# $text = "$text $sev:";
my $type;
my $desc;
my $rc;
($rc, $type, $desc) = getsensorevent($sensor_type, $offset, "ipmisensorevents");
if ($rc == 1) {
$type = "Unknown Type $sensor_type";
$desc = "Unknown Event $offset";
$rc = 0;
}
if ($event_type <= 0x0c) {
my $gtype;
my $gdesc;
($rc, $gtype, $gdesc) = getsensorevent($event_type, $offset, "ipmigenericevents");
if ($rc == 1) {
$gtype = "Unknown Type $gtype";
$gdesc = "Unknown Event $offset";
$rc = 0;
}
$desc = $gdesc;
}
if ($type eq "" || $type eq "-") {
$type = "OEM Sensor Type $sensor_type"
}
if ($desc eq "" || $desc eq "-") {
$desc = "OEM Sensor Event $offset"
}
if ($type eq $desc) {
$desc = "";
}
my $extra_info = getaddsensorevent($sensor_type, $offset, $event_data_1, $event_data_2, $event_data_3);
if ($extra_info) {
if ($desc) {
$desc = "$desc $extra_info";
}
else {
$desc = "$extra_info";
}
}
$text = "$text $type,";
$text = "$text $desc";
# my $key;
my $key = $sensor_owner_id . "." . $sensor_owner_lun . "." . $sensor_num;
my $sensor_desc = sprintf("Sensor 0x%02x", $sensor_num);
# foreach $key (keys %sdr_hash) {
# my $sdr = $sdr_hash{$key};
# if($sdr->sensor_number == $sensor_num) {
# $sensor_desc = $sdr_hash{$key}->id_string;
# last;
# }
# }
my %sdr_hash = %{ $sessdata->{sdr_hash} };
if (defined $sdr_hash{$key}) {
$sensor_desc = $sdr_hash{$key}->id_string;
if ($sdr_hash{$key}->event_type_code == 1) {
if (($event_data_1 & 0b11000000) == 0b01000000) {
$sensor_desc .= " reading " . translate_sensor($event_data_2, $sdr_hash{$key});
if (($event_data_1 & 0b00110000) == 0b00010000) {
$sensor_desc .= " with threshold " . translate_sensor($event_data_3, $sdr_hash{$key});
}
}
}
}
$text = "$text ($sensor_desc)";
if ($event_dir) {
$text = "$text - Recovered";
}
my $entry = $sessdata->{selentry};
if ($sessdata->{bmcnum} != 1) {
$text .= " on BMC " . $sessdata->{bmcnum};
}
if ($sessdata->{auxloginfo} and $sessdata->{auxloginfo}->{$entry}) {
$text .= " with additional data:";
if ($sessdata->{fullsel} || ($sessdata->{numevents}
&& $sessdata->{numevents} > $sessdata->{displayedevents})) {
xCAT::SvrUtils::sendmsg($text, $callback, $sessdata->{node}, %allerrornodes);
foreach (split /\n/, $sessdata->{auxloginfo}->{$entry}) {
xCAT::SvrUtils::sendmsg($_, $sessdata->{node});
}
$sessdata->{displayedevents}++;
} else {
push(@{ $sessdata->{selentries} }, $text);
push @{ $sessdata->{selentries} }, split /\n/, $sessdata->{auxloginfo}->{$entry};
}
} else {
if ($sessdata->{fullsel} || ($sessdata->{numevents}
&& $sessdata->{numevents} > $sessdata->{displayedevents})) {
xCAT::SvrUtils::sendmsg($text, $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{displayedevents}++;
} else {
push(@{ $sessdata->{selentries} }, $text);
}
}
if (($next_rec_ms == 0xFF && $next_rec_ls == 0xFF) or
($next_rec_ms == 0x0 && $next_rec_ls == 0x0)) {
sendsel($sessdata);
return;
}
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x43, data => [ 0, 0, $next_rec_ls, $next_rec_ms, 0x00, 0xFF ], callback => \&got_sel, callback_args => $sessdata);
}
sub sendsel {
my $sessdata = shift;
####my @routput = reverse(@output);
####my @noutput;
####my $c;
####foreach(@routput) {
#### $c++;
#### if($c > $num) {
#### last;
#### }
#### push(@noutput,$_);
####}
####@output = reverse(@noutput);
####return($rc,@output);
}
sub clear_sel_with_reservation {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
my @returnd = (0, @{ $rsp->{data} });
#elsif($code == 0x81) {
# $rc = 1;
# $text = "cannot execute command, SEL erase in progress";
#}
$sessdata->{res_id_ls} = $returnd[1];
$sessdata->{res_id_ms} = $returnd[2];
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x47, data => [ $sessdata->{res_id_ls}, $sessdata->{res_id_ms}, 0x43, 0x4c, 0x52, 0xaa ], callback => \&wait_for_selerase, callback_args => $sessdata);
}
sub wait_for_selerase {
my $rsp = shift;
my $sessdata = shift;
my @returnd = (0, @{ $rsp->{data} });
my $erase_status = $returnd[1] & 0b00000001;
xCAT::SvrUtils::sendmsg("SEL cleared", $callback, $sessdata->{node}, %allerrornodes);
}
#commenting out usless 'while 0' loop.
#skip test for now, need to get new res id for some machines
# while($erase_status == 0 && 0) {
# sleep(1);
# @cmd=(0x47,$res_id_ls,$res_id_ms,0x43,0x4c,0x52,0x00);
# $error = docmd(
# $netfun,
# \@cmd,
# \@returnd
# );
# if($error) {
# $rc = 1;
# $text = $error;
# return($rc,$text);
# }
# $code = $returnd[0];
# if($code == 0x00) {
# }
# else {
# $rc = 1;
# $text = $codes{$code};
# }
# if($rc != 0) {
# if(!$text) {
# $text = sprintf("unknown response %02x",$code);
# }
# return($rc,$text);
# }
# $erase_status = $returnd[1] & 0b00000001;
# }
sub read_ibm_auxlog {
my $sessdata = shift;
my $entry = $sessdata->{selentry};
my @auxdata;
my $netfn = 0xa << 2;
my @auxlogcmd = (0x5a, 1);
my $error = docmd(
$netfn,
\@auxlogcmd,
\@auxdata);
#print Dumper(\@auxdata);
unless ($error or $auxdata[0] or $auxdata[5] != 0x4d or $auxdata[6] != 0x4f or $auxdata[7] != 0x0) { #Don't bother if support cannot be confirmed by service processor
$netfn = 0x2e << 2; #switch netfunctions to read
my $numauxlogs = $auxdata[8] + ($auxdata[9] << 8);
my $auxidx = 1;
my $rc;
my $entry;
my $extdata;
while ($auxidx <= $numauxlogs) {
($rc, $entry, $extdata) = readauxentry($auxidx++);
unless ($rc) {
if ($sessdata->{auxloginfo}->{$entry}) {
$sessdata->{auxloginfo}->{$entry} .= "!" . $extdata;
} else {
$sessdata->{auxloginfo}->{$entry} = $extdata;
}
}
}
if ($sessdata->{auxloginfo}->{0}) {
if ($sessdata->{fullsel}) {
foreach (split /!/, $sessdata->{auxloginfo}->{0}) {
sendoutput(0, ":Unassociated auxillary data detected:");
foreach (split /\n/, $_) {
sendoutput(0, $_);
}
}
}
}
#print Dumper(\%auxloginfo);
}
}
sub getoemevent {
my $record_type = shift;
my $mfg_id = shift;
my $sel_data = shift;
my $sessdata;
my $text = ":";
if ($record_type < 0xE0 && $record_type > 0x2F) { #Should be timestampped, whatever it is
my $timestamp = (@$sel_data[3] | @$sel_data[4] << 8 | @$sel_data[5] << 16 | @$sel_data[6] << 24);
unless ($timestamp < 0x20000000) {
$timestamp -= $sessdata->{tfactor};
}
my ($seldate, $seltime) = timestamp2datetime($timestamp);
my @rest = @$sel_data[ 7 .. 15 ];
if ($mfg_id == 2) {
$text .= "$seldate $seltime IBM OEM Event-";
if ($rest[3] == 0 && $rest[4] == 0 && $rest[7] == 0) {
$text = $text . "PCI Event/Error, details in next event"
} elsif ($rest[3] == 1 && $rest[4] == 0 && $rest[7] == 0) {
$text = $text . "Processor Event/Error occurred, details in next event"
} elsif ($rest[3] == 2 && $rest[4] == 0 && $rest[7] == 0) {
$text = $text . "Memory Event/Error occurred, details in next event"
} elsif ($rest[3] == 3 && $rest[4] == 0 && $rest[7] == 0) {
$text = $text . "Scalability Event/Error occurred, details in next event"
} elsif ($rest[3] == 4 && $rest[4] == 0 && $rest[7] == 0) {
$text = $text . "PCI bus Event/Error occurred, details in next event"
} elsif ($rest[3] == 5 && $rest[4] == 0 && $rest[7] == 0) {
$text = $text . "Chipset Event/Error occurred, details in next event"
} elsif ($rest[3] == 6 && $rest[4] == 1 && $rest[7] == 0) {
$text = $text . "BIOS/BMC Power Executive mismatch (BIOS $rest[5], BMC $rest[6])"
} elsif ($rest[3] == 6 && $rest[4] == 2 && $rest[7] == 0) {
$text = $text . "Boot denied due to power limitations"
} else {
$text = $text . "Unknown event " . phex(\@rest);
}
} else {
$text .= "$seldate $seltime " . sprintf("Unknown OEM SEL Type %02x:", $record_type) . phex(\@rest);
}
} else { #Non-timestamped
my %memerrors = (
0x00 => "DIMM enabled",
0x01 => "DIMM disabled, failed ECC test",
0x02 => "POST/BIOS memory test failed, DIMM disabled",
0x03 => "DIMM disabled, non-supported memory device",
0x04 => "DIMM disabled, non-matching or missing DIMM(s)",
);
my %pcierrors = (
0x00 => "Device OK",
0x01 => "Required ROM space not available",
0x02 => "Required I/O Space not available",
0x03 => "Required memory not available",
0x04 => "Required memory below 1MB not available",
0x05 => "ROM checksum failed",
0x06 => "BIST failed",
0x07 => "Planar device missing or disabled by user",
0x08 => "PCI device has an invalid PCI configuration space header",
0x09 => "FRU information for added PCI device",
0x0a => "FRU information for removed PCI device",
0x0b => "A PCI device was added, PCI FRU information is stored in next log entry",
0x0c => "A PCI device was removed, PCI FRU information is stored in next log entry",
0x0d => "Requested resources not available",
0x0e => "Required I/O Space Not Available",
0x0f => "Required I/O Space Not Available",
0x10 => "Required I/O Space Not Available",
0x11 => "Required I/O Space Not Available",
0x12 => "Required I/O Space Not Available",
0x13 => "Planar video disabled due to add in video card",
0x14 => "FRU information for PCI device partially disabled ",
0x15 => "A PCI device was partially disabled, PCI FRU information is stored in next log entry",
0x16 => "A 33Mhz device is installed on a 66Mhz bus, PCI device information is stored in next log entry",
0x17 => "FRU information, 33Mhz device installed on 66Mhz bus",
0x18 => "Merge cable missing",
0x19 => "Node 1 to Node 2 cable missing",
0x1a => "Node 1 to Node 3 cable missing",
0x1b => "Node 2 to Node 3 cable missing",
0x1c => "Nodes could not merge",
0x1d => "No 8 way SMP cable",
0x1e => "Primary North Bridge to PCI Host Bridge IB Link has failed",
0x1f => "Redundant PCI Host Bridge IB Link has failed",
);
my %procerrors = (
0x00 => "Processor has failed BIST",
0x01 => "Unable to apply processor microcode update",
0x02 => "POST does not support current stepping level of processor",
0x03 => "CPU mismatch detected",
);
my @rest = @$sel_data[ 3 .. 15 ];
if ($record_type == 0xE0 && $rest[0] == 2 && $mfg_id == 2 && $rest[1] == 0 && $rest[12] == 1) { #Rev 1 POST memory event
$text = "IBM Memory POST Event-";
my $msuffix = sprintf(", chassis %d, card %d, dimm %d", $rest[3], $rest[4], $rest[5]);
#the next bit is a basic lookup table, should implement as a table ala ibmleds.tab, or a hash... yeah, a hash...
$text = $text . $memerrors{ $rest[2] } . $msuffix;
} elsif ($record_type == 0xE0 && $rest[0] == 1 && $mfg_id == 2 && $rest[12] == 0) { #A processor error or event, rev 0 only known in the spec I looked at
$text = $text . $procerrors{ $rest[1] };
} elsif ($record_type == 0xE0 && $rest[0] == 0 && $mfg_id == 2) { #A PCI error or event, rev 1 or 2, the revs differe in endianness
my $msuffix;
if ($rest[12] == 0) {
$msuffix = sprintf("chassis %d, slot %d, bus %s, device %02x%02x:%02x%02x", $rest[2], $rest[3], $rest[4], $rest[5], $rest[6], $rest[7], $rest[8]);
} elsif ($rest[12] == 1) {
$msuffix = sprintf("chassis %d, slot %d, bus %s, device %02x%02x:%02x%02x", $rest[2], $rest[3], $rest[4], $rest[5], $rest[6], $rest[7], $rest[8]);
} else {
return ("Unknown IBM PCI event/error format");
}
$text = $text . $pcierrors{ $rest[1] } . $msuffix;
} else {
#Some event we can't define that is OEM or some otherwise unknown event
$text = sprintf("SEL Type %02x:", $record_type) . phex(\@rest);
}
} #End timestampped intepretation
return ($text);
}
sub getsensorevent
{
my $sensortype = sprintf("%02Xh", shift);
my $sensoroffset = sprintf("%02Xh", shift);
my $file = shift;
my @line;
my $type;
my $code;
my $desc;
my $offset;
my $rc = 1;
if ($file eq "ipmigenericevents") {
if ($xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,$sensoroffset"}) {
($type, $desc) = split(/,/, $xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,$sensoroffset"}, 2);
return (0, $type, $desc);
}
if ($xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,-"}) {
($type, $desc) = split(/,/, $xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,-"}, 2);
return (0, $type, $desc);
}
}
if ($file eq "ipmisensorevents") {
if ($xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,$sensoroffset"}) {
($type, $desc) = split(/,/, $xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,$sensoroffset"}, 2);
return (0, $type, $desc);
}
if ($xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,-"}) {
($type, $desc) = split(/,/, $xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,-"}, 2);
return (0, $type, $desc);
}
}
return (0, "No Mappings found ($sensortype)", "No Mappings found ($sensoroffset)");
}
sub getaddsensorevent {
my $sensor_type = shift;
my $offset = shift;
my $event_data_1 = shift;
my $event_data_2 = shift;
my $event_data_3 = shift;
my $text = "";
if ($sensor_type == 0x08 && $offset == 6) {
my %extra = (
0x0 => "Vendor mismatch",
0x1 => "Revision mismatch",
0x2 => "Processor missing",
0x3 => "Power Supply rating mismatch",
0x4 => "Voltage rating mismatch",
);
if ($extra{$event_data_3}) {
$text = $extra{$event_data_3};
}
}
if ($sensor_type == 0x0C) {
$text = sprintf("Memory module %d", $event_data_3);
}
if ($sensor_type == 0x0f) {
if ($offset == 0x00) {
my %extra = (
0x00 => "Unspecified",
0x01 => "No system memory installed",
0x02 => "No usable system memory",
0x03 => "Unrecoverable hard disk failure",
0x04 => "Unrecoverable system board failure",
0x05 => "Unrecoverable diskette failure",
0x06 => "Unrecoverable hard disk controller failure",
0x07 => "Unrecoverable keyboard failure",
0x08 => "Removable boot media not found",
0x09 => "Unrecoverable video controller failure",
0x0a => "No video device detected",
0x0b => "Firmware (BIOS) ROM corruption detected",
0x0c => "CPU voltage mismatch",
0x0d => "CPU speed matching failure",
);
$text = $extra{$event_data_2};
}
if ($offset == 0x02) {
my %extra = (
0x00 => "Unspecified",
0x01 => "Memory initialization",
0x02 => "Hard-disk initialization",
0x03 => "Secondary processor(s) initialization",
0x04 => "User authentication",
0x05 => "User-initiated system setup",
0x06 => "USB resource configuration",
0x07 => "PCI resource configuration",
0x08 => "Option ROM initialization",
0x09 => "Video initialization",
0x0a => "Cache initialization",
0x0b => "SM Bus initialization",
0x0c => "Keyboard controller initialization",
0x0d => "Embedded controller/management controller initialization",
0x0e => "Docking station attachement",
0x0f => "Enabling docking station",
0x10 => "Docking staion ejection",
0x11 => "Disable docking station",
0x12 => "Calling operating system wake-up vector",
0x13 => "Starting operating system boot process, call init 19h",
0x14 => "Baseboard or motherboard initialization",
0x16 => "Floppy initialization",
0x17 => "Keyboard test",
0x18 => "Pointing device test",
0x19 => "Primary processor initialization",
);
$text = $extra{$event_data_2};
}
}
if ($sensor_type == 0x10) {
if ($offset == 0x0) {
$text = sprintf("Memory module %d", $event_data_2);
} elsif ($offset == 0x01) {
$text = "Disabled for ";
unless ($event_data_3 & 0x20) {
if ($event_data_3 & 0x10) {
$text .= "assertions of";
} else {
$text .= "deassertions of";
}
}
$text .= sprintf("type %02xh/offset %02xh", $event_data_2, $event_data_3 & 0x0F);
} elsif ($offset == 0x05) {
$text = "$event_data_3% full";
} elsif ($offset == 0x06) {
if (defined $event_data_2) {
if (defined($event_data_3) and ($event_data_3 & 0x80 == 0x80)) {
$text = "Vendor-specific processor number:";
} else {
$text = "Entity Instance number:";
}
$text .= sprintf("%02xh", $event_data_2 & 0xff);
} else {
$text = "for all Processor sensors";
}
}
}
if ($sensor_type == 0x12) {
if ($offset == 0x03) {
my %extra = {
0x0 => "Log Entry Action: entry added",
0x1 => "Log Entry Action: entry added because event did not be map to standard IPMI event",
0x2 => "Log Entry Action: entry added along with one or more corresponding SEL entries",
0x3 => "Log Entry Action: log cleared",
0x4 => "Log Entry Action: log disabled",
0x5 => "Log Entry Action: log enabled",
};
$text = "$text, " . $extra{ ($event_data_2 >> 4) & 0x0f };
%extra = {
0x0 => "Log Type:MCA Log",
0x1 => "Log Type:OEM 1",
0x2 => "Log Type:OEM 2",
};
$text = "$text, " . $extra{ ($event_data_2) & 0x0f };
$text =~ s/^, //;
}
if ($offset == 0x04) {
if ($event_data_2 & 0b00100000) {
$text = "$text, NMI";
}
if ($event_data_2 & 0b00010000) {
$text = "$text, OEM action";
}
if ($event_data_2 & 0b00001000) {
$text = "$text, power cycle";
}
if ($event_data_2 & 0b00000100) {
$text = "$text, reset";
}
if ($event_data_2 & 0b00000010) {
$text = "$text, power off";
}
if ($event_data_2 & 0b00000001) {
$text = "$text, Alert";
}
$text =~ s/^, //;
}
if ($offset == 0x05) {
if ($event_data_2 & 0x80) {
$text = "$text, event is second of pair";
} elsif ($event_data_2 & 0x80 == 0) {
$text = "$text, event is first of pair";
}
if ($event_data_2 & 0x0F == 0x1) {
$text = "$text, SDR Timestamp Clock updated";
} elsif ($event_data_2 & 0x0F == 0x0) {
$text = "$text, SEL Timestamp Clock updated";
}
$text =~ s/^, //;
}
}
if ($sensor_type == 0x1d && $offset == 0x07) {
my %causes = (
0 => "Unknown",
1 => "Chassis reset via User command to BMC",
2 => "Reset button",
3 => "Power button",
4 => "Watchdog action",
5 => "OEM",
6 => "AC Power apply force on",
7 => "Restore previous power state on AC",
8 => "PEF initiated reset",
9 => "PEF initiated power cycle",
10 => "Soft reboot",
11 => "RTC Wake",
);
if ($causes{ $event_data_2 & 0xf }) {
$text = $causes{$event_data_2};
} else {
$text = "Unrecognized cause " . $event_data_2 & 0xf;
}
$text .= "via channel $event_data_3";
}
if ($sensor_type == 0x21) {
my %extra = (
0 => "PCI slot",
1 => "Drive array",
2 => "External connector",
3 => "Docking port",
4 => "Other slot",
5 => "Sensor ID",
6 => "AdvncedTCA",
7 => "Memory slot",
8 => "FAN",
9 => "PCIe",
10 => "SCSI",
11 => "SATA/SAS",
);
$text = $extra{ $event_data_2 & 127 };
unless ($text) {
$text = "Unknown slot/conn type " . $event_data_2 & 127;
}
$text .= " $event_data_3";
}
if ($sensor_type == 0x23) {
my %extra = (
0x10 => "SMI",
0x20 => "NMI",
0x30 => "Messaging Interrupt",
0xF0 => "Unspecified",
0x01 => "BIOS FRB2",
0x02 => "BIOS/POST",
0x03 => "OS Load",
0x04 => "SMS/OS",
0x05 => "OEM",
0x0F => "Unspecified"
);
if ($extra{ $event_data_2 & 0xF0 }) {
$text = $extra{ $event_data_2 & 0xF0 };
}
if ($extra{ $event_data_2 & 0x0F }) {
$text .= ", " . $extra{ $event_data_2 & 0x0F };
}
$text =~ s/^, //;
}
if ($sensor_type == 0x28) {
if ($offset == 0x4) {
$text = "Sensor $event_data_2";
} elsif ($offset == 0x5) {
$text = "";
my $logicalfru = 0;
if ($event_data_2 & 128) {
$logicalfru = 1;
}
my $intelligent = 1;
if ($event_data_2 & 24) {
$text .= "LUN " . ($event_data_2 & 24) >> 3;
} else {
$intelligent = 0;
}
if ($event_data_2 & 7) {
$text .= "Bus ID " . ($event_data_2 & 7);
}
if ($logicalfru) {
$text .= "FRU ID " . $event_data_3;
} elsif (not $intelligent) {
$text .= "I2C addr " . $event_data_3 >> 1;
}
}
}
if ($sensor_type == 0x2a) {
$text = sprintf("Channel %d, User %d", $event_data_3 & 0x0f, $event_data_2 & 0x3f);
if ($offset == 1) {
if (($event_data_3 & 207) == 1) {
$text .= " at user request";
} elsif (($event_data_3 & 207) == 2) {
$text .= " timed out";
} elsif (($event_data_3 & 207) == 3) {
$text .= " configuration change";
}
}
}
if ($sensor_type == 0x2b) {
my %extra = (
0x0 => "Unspecified",
0x1 => "BMC device ID",
0x2 => "BMC Firmware",
0x3 => "BMC Hardware",
0x4 => "BMC manufacturer",
0x5 => "IPMI Version",
0x6 => "BMC aux firmware ID",
0x7 => "BMC boot block",
0x8 => "Other BMC Firmware",
0x09 => "BIOS/EFI change",
0x0a => "SMBIOS change",
0x0b => "OS change",
0x0c => "OS Loader change",
0x0d => "Diagnostics change",
0x0e => "Management agent change",
0x0f => "Management software change",
0x10 => "Management middleware change",
0x11 => "FPGA/CPLD/PSoC change",
0x12 => "FRU change",
0x13 => "device addition/removal",
0x14 => "Equivalent replacement",
0x15 => "Newer replacement",
0x16 => "Older replacement",
0x17 => "DIP/Jumper change",
);
if ($extra{$event_data_2}) {
$text = $extra{$event_data_2};
} else {
$text = "Unknown version change type $event_data_2";
}
}
if ($sensor_type == 0x2c) {
my %extra = (
0 => "",
1 => "Software dictated",
2 => "Latch operated",
3 => "Hotswap buton pressed",
4 => "automatic operation",
5 => "Communication lost",
6 => "Communication lost locally",
7 => "Unexpected removal",
8 => "Operator intervention",
9 => "Unknwon IPMB address",
10 => "Unexpected deactivation",
0xf => "unknown",
);
if ($extra{ $event_data_2 >> 4 }) {
$text = $extra{ $event_data_2 >> 4 };
} else {
$text = "Unrecognized cause " . $event_data_2 >> 4;
}
my $prev_state = $event_data_2 & 0xf;
unless ($prev_state == $offset) {
my %oldstates = (
0 => "Not Installed",
1 => "Inactive",
2 => "Activation requested",
3 => "Activating",
4 => "Active",
5 => "Deactivation requested",
6 => "Deactivating",
7 => "Communication lost",
);
if ($oldstates{$prev_state}) {
$text .= "(was " . $oldstates{$prev_state} . ")";
} else {
$text .= "(was in unrecognized state $prev_state)";
}
}
}
return ($text);
}
sub initiem {
my $sessdata = shift;
$sessdata->{iem} = IBM::EnergyManager->new();
my @payload = $sessdata->{iem}->get_next_payload();
my $netfun = shift @payload;
my $command = shift @payload;
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@payload, callback => \&ieminitted, callback_args => $sessdata);
}
sub ieminitted {
my $rsp = shift;
my $sessdata = shift;
my @returnd = ($rsp->{code}, @{ $rsp->{data} });
$sessdata->{iem}->handle_next_payload(@returnd);
$sessdata->{iemcallback}->($sessdata);
}
sub readenergy {
my $sessdata = shift;
unless ($iem_support) {
xCAT::SvrUtils::sendmsg([ 1, ":IBM::EnergyManager package required for this value" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
my @entries;
$sessdata->{iemcallback} = \&readenergy_withiem;
initiem($sessdata);
}
sub readenergy_withiem {
my $sessdata = shift;
$sessdata->{iem}->prep_get_precision();
$sessdata->{iemcallback} = \&got_precision;
execute_iem_commands($sessdata); #this gets all precision data initialized for AC and DC
# we need not make use of the generic extraction function, so we call execute_iem instead of process_data
#sorry the perl api I wrote sucks..
}
sub got_precision {
my $sessdata = shift;
$sessdata->{iem}->prep_get_ac_energy();
$sessdata->{iemcallback} = \&got_ac_energy;
process_data_from_iem($sessdata);
}
sub got_ac_energy {
my $sessdata = shift;
unless ($sessdata->{abortediem}) {
$sessdata->{iemtextdata} .= sprintf(" +/-%.1f%%", $sessdata->{iem}->energy_ac_precision() * 0.1); #note while \x{B1} would be cool, it's non-trivial to support
xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata}, $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{abortediem} = 0;
}
#this would be 'if sessdata->{abortediem}'. Thus far this is only triggered in the case of a system that fairly obviously
#shouldn't have an AC meter. As a consequence, don't output data that would suggest a user might actually get it
#in that case, another entity can provide a measure of the AC usage, but only an aggregate measure not an individual measure
# $sessdata->{iemtextdata} = "AC Energy Usage: ";
# if ($sessdata->{abortediemreason}) {
# $sessdata->{iemtextdata} .= $sessdata->{abortediemreason};
# }
# xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata},$callback,$sessdata->{node},%allerrornodes);
$sessdata->{iem}->prep_get_dc_energy();
$sessdata->{iemcallback} = \&got_dc_energy;
process_data_from_iem($sessdata);
}
sub got_ac_energy_with_precision {
my $sessdata = shift;
$sessdata->{iemtextdata} .= sprintf(" +/-%.1f%%", $sessdata->{iem}->energy_ac_precision() * 0.1); #note while \x{B1} would be cool, it's non-trivial to support
xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata}, $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{iem}->prep_get_dc_energy();
$sessdata->{iemcallback} = \&got_dc_energy;
process_data_from_iem($sessdata);
}
sub got_dc_energy {
my $sessdata = shift;
$sessdata->{iemtextdata} .= sprintf(" +/-%.1f%%", $sessdata->{iem}->energy_dc_precision() * 0.1);
xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata}, $callback, $sessdata->{node}, %allerrornodes);
if (scalar @{ $sessdata->{sensorstoread} }) {
$sessdata->{currsdr} = shift @{ $sessdata->{sensorstoread} };
readsensor($sessdata); #next sensor
}
}
sub execute_iem_commands {
my $sessdata = shift;
my @payload = $sessdata->{iem}->get_next_payload();
if (scalar @payload) {
my $netfun = shift @payload;
my $command = shift @payload;
$sessdata->{ipmisession}->subcmd(netfn => $netfun, command => $command, data => \@payload, callback => \&executed_iem_command, callback_args => $sessdata);
} else { #complete, return to callback
$sessdata->{iemcallback}->($sessdata);
}
}
sub executed_iem_command {
my $rsp = $_[0];
my $sessdata = $_[1];
if ($rsp->{code} == 0xcb) {
$sessdata->{abortediem} = 1;
$sessdata->{abortediemreason} = "Not Present";
$sessdata->{iemcallback}->($sessdata);
return;
}
if (check_rsp_errors(@_)) { #error while in an IEM transaction, skip to the end
$sessdata->{abortediem} = 1;
$sessdata->{iemcallback}->($sessdata);
return;
}
my @returnd = ($rsp->{code}, @{ $rsp->{data} });
$sessdata->{iem}->handle_next_payload(@returnd);
execute_iem_commands($sessdata);
}
sub process_data_from_iem {
my $sessdata = shift;
my @returnd;
$sessdata->{iemdatacallback} = $sessdata->{iemcallback};
$sessdata->{iemcallback} = \&got_data_to_process_from_iem;
execute_iem_commands($sessdata);
}
sub got_data_to_process_from_iem {
my $sessdata = shift;
my @iemdata = $sessdata->{iem}->extract_data;
my $label = shift @iemdata;
my $units = shift @iemdata;
my $value = 0;
my $shift = 0;
while (scalar @iemdata) { #stuff the 64-bits of data into an int, would break in 32 bit
$value += pop(@iemdata) << $shift;
#$value.=sprintf("%02x ",shift @iemdata);
$shift += 8;
}
if ($units eq "mJ") {
$units = "kWh";
$value = $value / 3600000000;
$sessdata->{iemtextdata} = sprintf("$label: %.4f $units", $value);
} elsif ($units eq "mW") {
$units = "W";
$value = $value / 1000.0;
$sessdata->{iemtextdata} = sprintf("$label: %.1f $units", $value);
}
$sessdata->{iemdatacallback}->($sessdata);
}
sub gotchassis { #get chassis status command
my $rsp = shift;
my $sessdata = shift;
unless (check_rsp_errors($rsp, $sessdata)) {
my @data = @{ $rsp->{data} };
my $powerstat;
if ($data[0] & 1) {
$powerstat = "on";
} else {
$powerstat = "off";
}
xCAT::SvrUtils::sendmsg("Power Status: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[0] & 0b10) {
$powerstat = "true";
} else {
$powerstat = "false";
}
xCAT::SvrUtils::sendmsg("Power Overload: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[0] & 0b100) {
$powerstat = "active";
} else {
$powerstat = "inactive";
}
xCAT::SvrUtils::sendmsg("Power Interlock: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[0] & 0b1000) {
$sessdata->{healthsummary} &= 2; #set to critical state
$powerstat = "true";
} else {
$powerstat = "false";
}
xCAT::SvrUtils::sendmsg("Power Fault: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[0] & 0b10000) {
$sessdata->{healthsummary} &= 2; #set to critical state
$powerstat = "true";
} else {
$powerstat = "false";
}
xCAT::SvrUtils::sendmsg("Power Control Fault: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
my $powpolicy = ($data[0] & 0b1100000) >> 5;
my %powpolicies = (0 => "Always off", 1 => "Last State", 2 => "Always on", 3 => "Unknown");
xCAT::SvrUtils::sendmsg("Power Restore Policy: " . $powpolicies{$powpolicy}, $callback, $sessdata->{node}, %allerrornodes);
my @lastevents;
if ($data[1] & 0b1) {
push @lastevents, "AC failed";
}
if ($data[1] & 0b10) {
$sessdata->{healthsummary} &= 2; #set to critical state
push @lastevents, "Power overload";
}
if ($data[1] & 0b100) {
$sessdata->{healthsummary} &= 1; #set to critical state
push @lastevents, "Interlock activated";
}
if ($data[1] & 0b1000) {
$sessdata->{healthsummary} &= 2; #set to critical state
push @lastevents, "Power Fault";
}
if ($data[1] & 0b10000) {
push @lastevents, "By Request";
}
my $lastevent = join(",", @lastevents);
xCAT::SvrUtils::sendmsg("Last Power Event: $lastevent", $callback, $sessdata->{node}, %allerrornodes);
if ($data[2] & 0b1) {
$sessdata->{healthsummary} &= 1; #set to warn state
$powerstat = "active";
} else {
$powerstat = "inactive";
}
xCAT::SvrUtils::sendmsg("Chassis intrusion: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[2] & 0b10) {
$powerstat = "active";
} else {
$powerstat = "inactive";
}
xCAT::SvrUtils::sendmsg("Front Panel Lockout: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[2] & 0b100) { # drive fault
$sessdata->{healthsummary} &= 2; #set to critical state
$powerstat = "true";
} else {
$powerstat = "false";
}
xCAT::SvrUtils::sendmsg("Drive Fault: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[2] & 0b1000) { # fan fault
$sessdata->{healthsummary} &= 1; #set to warn state
$powerstat = "true";
} else {
$powerstat = "false";
}
xCAT::SvrUtils::sendmsg("Cooling Fault: $powerstat", $callback, $sessdata->{node}, %allerrornodes);
if ($data[2] & 0b1000000) { #can look at light status
my $idstat = ($data[2] & 0b110000) >> 4;
my %idstats = (0 => "off", 1 => "on", 2 => "on", 3 => "unknown");
xCAT::SvrUtils::sendmsg("Identify Light: " . $idstats{$idstat}, $callback, $sessdata->{node}, %allerrornodes);
}
}
#$sessdata->{powerstatprefix}="Power Status: ";
if ($sessdata->{sensorstoread} and scalar @{ $sessdata->{sensorstoread} }) {
$sessdata->{currsdr} = shift @{ $sessdata->{sensorstoread} };
readsensor($sessdata); #next sensor
}
}
sub readchassis {
my $sessdata = shift;
$sessdata->{ipmisession}->subcmd(netfn => 0x0, command => 0x1, data => [], callback => \&gotchassis, callback_args => $sessdata);
}
sub checkleds {
my $sessdata = shift;
my @cmd;
my @returnd = ();
my $error;
my $led_id_ms;
my $led_id_ls;
my $rc = 0;
my @output = ();
my $text = "";
my $key;
my $mfg_id = $sessdata->{mfg_id};
#TODO device id
unless (is_systemx($sessdata)) {
xCAT::SvrUtils::sendmsg("LED status not supported on this system", $callback, $sessdata->{node}, %allerrornodes);
return;
}
my %sdr_hash = %{ $sessdata->{sdr_hash} };
$sessdata->{doleds} = [];
foreach $key (sort { $sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string } keys %sdr_hash) {
my $sdr = $sdr_hash{$key};
if ($sdr->rec_type == 0xC0 && $sdr->sensor_type == 0xED) {
#this stuff is to help me build the file from spec paste
#my $tehstr=sprintf("grep 0x%04X /opt/xcat/lib/x3755led.tab",$sdr->led_id);
#my $tehstr=`$tehstr`;
#$tehstr =~ s/^0x....//;
#printf("%X.%X.0x%04x",$mfg_id,$prod_id,$sdr->led_id);
#print $tehstr;
#We are inconsistant in our spec, first try a best guess
#at endianness, assume the smaller value is MSB
if (($sdr->led_id & 0xff) > ($sdr->led_id >> 8)) {
$led_id_ls = $sdr->led_id & 0xff;
$led_id_ms = $sdr->led_id >> 8;
} else {
$led_id_ls = $sdr->led_id >> 8;
$led_id_ms = $sdr->led_id & 0xff;
}
push @{ $sessdata->{doleds} }, [ $led_id_ms, $led_id_ls, $sdr ];
}
}
$sessdata->{doled} = shift @{ $sessdata->{doleds} };
if ($sessdata->{doled}) {
$sessdata->{current_led_sdr} = pop @{ $sessdata->{doled} };
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0xc0, data => $sessdata->{doled}, callback => \&did_led, callback_args => $sessdata);
} else {
xCAT::SvrUtils::sendmsg("No supported LEDs found in system", $callback, $sessdata->{node}, %allerrornodes);
}
# if ($#output==-1) {
# push(@output,"No active error LEDs detected");
# }
}
sub did_led {
my $rsp = $_[0];
my $sessdata = $_[1];
my $mfg_id = $sessdata->{mfg_id};
my $prod_id = $sessdata->{prod_id};
my $sdr = $sessdata->{current_led_sdr};
if (not $sessdata->{ledswappedendian} and $_[0]->{code} == 0xc9) { #missed an endian guess probably
$sessdata->{ledswappedendian} = 1;
my @doled;
$doled[0] = $sessdata->{doled}->[1];
$doled[1] = $sessdata->{doled}->[0];
$sessdata->{doled} = \@doled;
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0xc0, data => $sessdata->{doled}, callback => \&did_led, callback_args => $sessdata);
return;
} elsif ($_[0]->{code} == 0xc9) {
$_[0]->{code} = 0; #TODO: some system actually gives an led locator record that doesn't exist....
print "DEBUG: unfindable LED record\n";
}
$sessdata->{ledswappedendian} = 0; #reset ledswappedendian flag to allow future swaps
if (check_rsp_errors(@_)) {
return;
}
my $led_id_ls = $sessdata->{doled}->[1];
my $led_id_ms = $sessdata->{doled}->[0];
my @returnd = (0, @{ $rsp->{data} });
if ($returnd[2]) { # != 0) {
#It's on...
if ($returnd[6] == 4) {
xCAT::SvrUtils::sendmsg(sprintf("BIOS or admininstrator has %s lit", getsensorname($mfg_id, $prod_id, $sdr->led_id, "ibmleds", $sdr)), $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{activeleds} = 1;
}
elsif ($returnd[6] == 3) {
xCAT::SvrUtils::sendmsg(sprintf("A user has manually requested LED 0x%04x (%s) be active", $sdr->led_id, getsensorname($mfg_id, $prod_id, $sdr->led_id, "ibmleds", $sdr)), $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{activeleds} = 1;
}
elsif ($returnd[6] == 1 && $sdr->led_id != 0) {
xCAT::SvrUtils::sendmsg(sprintf("LED 0x%02x%02x (%s) active to indicate LED 0x%02x%02x (%s) is active", $led_id_ms, $led_id_ls, getsensorname($mfg_id, $prod_id, $sdr->led_id, "ibmleds", $sdr), $returnd[4], $returnd[5], getsensorname($mfg_id, $prod_id, ($returnd[4] << 8) + $returnd[5], "ibmleds")), $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{activeleds} = 1;
}
elsif ($sdr->led_id == 0 and $led_id_ms == 0 and $led_id_ls == 0) {
xCAT::SvrUtils::sendmsg(sprintf("LED 0x0000 (%s) active to indicate system error condition.", getsensorname($mfg_id, $prod_id, $sdr->led_id, "ibmleds", $sdr)), $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{activeleds} = 1;
if ($returnd[6] == 1 and $returnd[4] == 0xf and $returnd[5] == 0xff) {
$sessdata->{doled} = [ $returnd[4], $returnd[5] ];
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0xc0, data => $sessdata->{doled}, callback => \&did_led, callback_args => $sessdata);
return;
}
}
elsif ($returnd[6] == 2) {
my $sensor_desc;
#Ok, LED is tied to a sensor..
my $sensor_num = $returnd[5];
my %sdr_hash = %{ $sessdata->{sdr_hash} };
foreach my $key (keys %sdr_hash) {
my $osdr = $sdr_hash{$key};
if ($osdr->sensor_number == $sensor_num and $osdr->rec_type != 192 and $osdr->rec_type != 17) {
$sensor_desc = $sdr_hash{$key}->id_string;
if ($osdr->rec_type == 0x01) {
last;
}
}
}
#push(@output,sprintf("Sensor 0x%02x (%s) has activated LED 0x%04x",$sensor_num,$sensor_desc,$sdr->led_id));
if ($led_id_ms == 0xf and $led_id_ls == 0xff) {
xCAT::SvrUtils::sendmsg(sprintf("LED active to indicate Sensor 0x%02x (%s) error.", $sensor_num, $sensor_desc), $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg(sprintf("LED %02x%02x (%s) active to indicate Sensor 0x%02x (%s) error.", $led_id_ms, $led_id_ls, getsensorname($mfg_id, $prod_id, $sdr->led_id, "ibmleds", $sdr), $sensor_num, $sensor_desc), $callback, $sessdata->{node}, %allerrornodes);
}
$sessdata->{activeleds} = 1;
} else { #an LED is on for some other reason
#print "DEBUG: unknown LED reason code ".$returnd[6]."\n";
#TODO: discern meaning of more 'reason' codes, 5 and ff have come up
}
}
$sessdata->{doled} = shift @{ $sessdata->{doleds} };
if ($sessdata->{doled}) {
$sessdata->{current_led_sdr} = pop @{ $sessdata->{doled} };
$sessdata->{ipmisession}->subcmd(netfn => 0x3a, command => 0xc0, data => $sessdata->{doled}, callback => \&did_led, callback_args => $sessdata);
} elsif (not $sessdata->{activeleds}) {
xCAT::SvrUtils::sendmsg("No active error LEDs detected", $callback, $sessdata->{node}, %allerrornodes);
}
if (scalar @{ $sessdata->{sensorstoread} }) {
$sessdata->{currsdr} = shift @{ $sessdata->{sensorstoread} };
readsensor($sessdata); #next sensor
}
}
sub done_powerusage {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, "Get Power Reading failed" ], $callback, $sessdata->{node}, %allerrornodes);
} else {
my $e_id = shift(@{ $rsp->{data} });
if ($e_id ne 0xdc) {
xCAT::SvrUtils::sendmsg([ 1, "The Power Reading response is incorrect" ], $callback, $sessdata->{node}, %allerrornodes);
} else {
my $curr_power = $rsp->{data}->[0] + $rsp->{data}->[1] * 0x100;
my $mini_power = $rsp->{data}->[2] + $rsp->{data}->[3] * 0x100;
my $max_power = $rsp->{data}->[4] + $rsp->{data}->[5] * 0x100;
my $aver_power = $rsp->{data}->[6] + $rsp->{data}->[7] * 0x100;
my $time_stamp = $rsp->{data}->[8] + $rsp->{data}->[9] * 0x100 +
$rsp->{data}->[10] * 0x10000 + $rsp->{data}->[11] * 0x1000000;
my ($sec, $min, $hour, $day, $mon, $year) = localtime($time_stamp);
$mon += 1;
$year += 1900;
my $time_period = $rsp->{data}->[12] + $rsp->{data}->[13] * 0x100 +
$rsp->{data}->[14] * 0x10000 + $rsp->{data}->[15] * 0x1000000;
my $reading_state = (($rsp->{data}->[16] & 0x40) >> 6) ? "Active" : "Inactivate";
xCAT::SvrUtils::sendmsg("Current Power : $curr_power" . "W", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("Minimum Power over sampling duration : $mini_power" . "W", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("Maximum Power over sampling duration : $max_power" . "W", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("Average Power over sampling duration : $aver_power" . "W", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("Time Stamp : $mon/$day/$year - $hour:$min:$sec", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("Statistics reporting time period : $time_period milliseconds", $callback, $sessdata->{node}, %allerrornodes);
xCAT::SvrUtils::sendmsg("Power Measurement : $reading_state", $callback, $sessdata->{node}, %allerrornodes);
}
}
do_dcmi_operating($sessdata);
}
my %dcmi_sensors = (
0x37 => "Inlet Temperature",
0x03 => "CPU Temperature",
0x07 => "Baseboard temperature",
);
sub do_temperature {
my $rsp = shift;
my $sessdata = shift;
my $cur_sensor = $sessdata->{dcmi_sensor}->{cur_sensor};
my $sensor_dis = "unknown sensor";
if (defined($cur_sensor) and exists($dcmi_sensors{$cur_sensor})) {
$sensor_dis = $dcmi_sensors{$cur_sensor};
}
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, "Get Power Reading failed for $sensor_dis" ], $callback, $sessdata->{node}, %allerrornodes);
} elsif (exists($rsp->{data})) {
my $e_id = shift(@{ $rsp->{data} });
if ($e_id ne 0xdc) {
xCAT::SvrUtils::sendmsg([ 1, "The Power Reading response is incorrect for $sensor_dis" ], $callback, $sessdata->{node}, %allerrornodes);
} else {
my $num_of_instances = shift(@{ $rsp->{data} });
my $num_cur = shift(@{ $rsp->{data} });
if ($num_cur) {
while (scalar(@{ $rsp->{data} })) {
my $temp = shift(@{ $rsp->{data} });
my $iid = shift(@{ $rsp->{data} });
my $flag = ($temp & 0x80) >> 7;
$temp &= 0x7f;
if ($flag) {
$sessdata->{dcmi_sensor_instances}->{$cur_sensor}->{$iid} = "-$temp Centigrade";
} else {
$sessdata->{dcmi_sensor_instances}->{$cur_sensor}->{$iid} = "+$temp Centigrade";
}
}
}
}
}
{
my $sensor_eid = shift(@{ $sessdata->{dcmi_sensor}->{sensors} });
if ($sensor_eid) {
$sessdata->{dcmi_sensor}->{cur_sensor} = $sensor_eid;
# The raw format for dcmi command Get Temperature Reading
# ipmitool-xcat -I lanplus -H <bmc_ip> -U <bmc_username> -P <bmc_password> raw 0x2c 0x10 0xdc 0x01 <sensor entity_id> 0 0
$sessdata->{ipmisession}->subcmd(netfn => 0x2c, command => 0x10,
data => [ 0xdc, 0x01, $sensor_eid, 0, 0 ],
callback => \&do_temperature,
callback_args => $sessdata);
return;
}
}
done_temperature($sessdata);
}
sub done_temperature {
my $sessdata = shift;
foreach my $sid (sort keys %{ $sessdata->{dcmi_sensor_instances} }) {
my $sensor_dis = "unknown sensor";
if (exists($dcmi_sensors{$sid})) {
$sensor_dis = $dcmi_sensors{$sid};
}
foreach my $instance_id (sort keys %{ $sessdata->{dcmi_sensor_instances}->{$sid} }) {
my $temp = $sessdata->{dcmi_sensor_instances}->{$sid}->{$instance_id};
my $string = sprintf("%-36s", "$sensor_dis Instance $instance_id");
xCAT::SvrUtils::sendmsg("$string : $temp", $callback, $sessdata->{node}, %allerrornodes);
}
}
do_dcmi_operating($sessdata);
}
sub do_dcmi_operating {
my $sessdata = shift;
unless (scalar(@{ $sessdata->{energy_options} })) {
return;
} else {
my $cur_op = shift(@{ $sessdata->{energy_options} });
if ($cur_op eq "powerusage") {
# The raw format for dcmi command Get Power Reading:
# ipmitool-xcat -I lanplus -H <bmc_ip> -U <bmc_username> -P <bmc_password> raw 0x2c 0x02 0xdc 1 0 0
$sessdata->{ipmisession}->subcmd(netfn => 0x2c, command => 0x02,
data => [ 0xdc, 1, 0, 0 ],
callback => \&done_powerusage,
callback_args => $sessdata);
} elsif ($cur_op eq "temperature") {
# DCMI Sensor Entity ID
# 0x37/0x40 55 for Inlet Temperature
# 0x03/0x41 for CPU Temperature
# 0x07/0x42 for Baseboard temperature
@{ $sessdata->{dcmi_sensor}->{sensors} } = qw/55 3 7/;
&do_temperature({ startpoint => 1 }, $sessdata);
}
}
}
sub renergy {
my $sessdata = shift;
my @subcommands = @{ $sessdata->{extraargs} };
if (isopenpower($sessdata)) {
unless (@subcommands) {
@subcommands = qw/powerusage temperature/;
}
foreach (@subcommands) {
if ($_ eq "powerusage") {
push @{ $sessdata->{energy_options} }, 'powerusage';
} elsif ($_ eq "temperature") {
push @{ $sessdata->{energy_options} }, 'temperature';
} else {
if ($_ =~ /=/) {
xCAT::SvrUtils::sendmsg([ 1, "Only Query is supported" ], $callback, $sessdata->{node}, %allerrornodes);
} else {
xCAT::SvrUtils::sendmsg([ 1, "The option $_ is not supported" ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
}
# DCMI -- Data Center Manageability Interface, for more info, pls reference http://www.intel.com/content/www/us/en/data-center/dcmi/data-center-manageability-interface.html
do_dcmi_operating($sessdata);
return;
} else {
if (grep /powerusage/, @subcommands) {
xCAT::SvrUtils::sendmsg([ 1, "The option 'powerusage' is only supported for OpenPOWER servers" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if (grep /temperature/, @subcommands) {
xCAT::SvrUtils::sendmsg([ 1, "The option 'temperatue' is only supported for OpenPOWER servers" ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
}
unless ($iem_support) {
xCAT::SvrUtils::sendmsg(":Command unsupported without IBM::EnergyManager installed", $callback, $sessdata->{node});
return;
}
my @directives = ();
foreach (@subcommands) {
if ($_ eq 'cappingmaxmin') {
push @directives, 'cappingmax', 'cappingmin';
}
push @directives, split /,/, $_;
}
$sessdata->{directives} = \@directives;
$sessdata->{iemcallback} = \&renergy_withiem;
initiem($sessdata);
}
sub renergy_withiem {
my $sessdata = shift;
my @settable_keys = qw/savingstatus cappingstatus cappingwatt cappingvalue/;
my $directive = shift(@{ $sessdata->{directives} });
if ($sessdata->{iemtextdata}) {
xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata}, $callback, $sessdata->{node}, %allerrornodes);
$sessdata->{iemtextdata} = "";
}
if ($sessdata->{gotcapstatus}) {
$sessdata->{gotcapstatus} = 0;
my $capenabled = $sessdata->{iem}->capping_enabled();
xCAT::SvrUtils::sendmsg("cappingstatus: " . ($capenabled ? "on" : "off"), $callback, $sessdata->{node}, %allerrornodes);
}
if ($sessdata->{gothistogram}) {
$sessdata->{gothistogram} = 0;
my @histdata = $sessdata->{iem}->extract_relative_histogram;
foreach (sort { $a <=> $b } keys %{ $histdata[0] }) {
xCAT::SvrUtils::sendmsg("$_: " . $histdata[0]->{$_}, $callback, $sessdata->{node}, %allerrornodes);
}
}
unless ($directive) {
return;
}
my $value = undef;
my $key = undef;
$sessdata->{iemcallback} = \&renergy_withiem;
if ($directive =~ /(.*)=(.*)\z/) {
$key = $1;
$value = $2;
unless (grep /$key/, @settable_keys and $value) {
return (1, "Malformed argument $directive");
}
if ($key eq "cappingwatt" or $key eq "cappingvalue") {
$value = $value * 1000; #convert to milliwatts
$sessdata->{iem}->prep_set_cap($value);
execute_iem_commands($sessdata); #this gets all precision data initialized
}
if ($key eq "cappingstatus") {
if (grep /$value/, qw/enable on 1/) {
$value = 1;
} else {
$value = 0;
}
$sessdata->{iem}->prep_set_capenable($value);
execute_iem_commands($sessdata); #this gets all precision data initialized
}
}
if ($directive =~ /cappingmin/) {
$sessdata->{iem}->prep_get_mincap();
process_data_from_iem($sessdata);
} elsif ($directive =~ /cappingmax$/) {
$sessdata->{iem}->prep_get_maxcap();
process_data_from_iem($sessdata);
}
if ($directive =~ /cappingvalue/) {
$sessdata->{iem}->prep_get_cap();
process_data_from_iem($sessdata);
}
if ($directive =~ /cappingstatus/) {
$sessdata->{iem}->prep_get_powerstatus();
$sessdata->{gotcapstatus} = 1;
execute_iem_commands($sessdata);
}
if ($directive =~ /relhistogram/) {
$sessdata->{gothistogram} = 1;
$sessdata->{iem}->prep_retrieve_histogram();
execute_iem_commands($sessdata);
}
return;
}
sub vitals {
my $sessdata = shift;
$sessdata->{healthsummary} = 0; #0 means healthy for now
my %sdr_hash = %{ $sessdata->{sdr_hash} };
my @textfilters;
foreach (@{ $sessdata->{extraargs} }) {
push @textfilters, (split /,/, $_);
}
unless (scalar @textfilters) { @textfilters = ("all"); }
my $rc = 0;
my $text;
my $key;
my %sensor_filters = ();
my @output;
my $reading;
my $unitdesc;
my $value;
my $extext;
my $format = "%-30s%8s %-20s";
my $doall;
$doall = 0;
$rc = 0;
#filters: defined in sensor type codes and data table
# 1 == temp, 2 == voltage 3== current (we lump in wattage here for lack of a better spot), 4 == fan
if (grep { $_ eq "all" } @textfilters) {
$sensor_filters{1} = 1; #,0x02,0x03,0x04); rather than filtering, unfiltered results
$sensor_filters{energy} = 1;
$sensor_filters{chassis} = 1;
$sensor_filters{leds} = 1;
$doall = 1;
}
if (grep /^temp$/, @textfilters) {
$sensor_filters{0x01} = 1;
}
if (grep /^voltage$/, @textfilters) {
$sensor_filters{0x02} = 1;
}
if (grep /^wattage$/, @textfilters) {
$sensor_filters{watt} = 1;
}
if (grep /^fanspeed$/, @textfilters) {
$sensor_filters{0x04} = 1;
}
if (grep /^power$/, @textfilters) { #power does not really include energy, but most people use 'power' to mean both
$sensor_filters{0x03} = 1;
$sensor_filters{powerstate} = 1;
$sensor_filters{energy} = 1;
}
if (grep /^energy$/, @textfilters) {
$sensor_filters{energy} = 1;
}
if (grep /^leds$/, @textfilters) {
$sensor_filters{leds} = 1;
}
if (grep /^chassis$/, @textfilters) {
$sensor_filters{chassis} = 1;
}
unless (keys %sensor_filters) {
xCAT::SvrUtils::sendmsg([ 1, "Unrecognized rvitals arguments " . join(" ", @{ $sessdata->{extraargs} }) ], $callback, $sessdata->{node}, %allerrornodes);
}
$sessdata->{sensorstoread} = [];
my %usedkeys;
foreach (keys %sensor_filters) {
my $filter = $_;
if ($filter eq "energy" or $filter eq "leds") { next; }
foreach $key (sort { $sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string } keys %sdr_hash) {
if ($usedkeys{$key}) { next; } #avoid duplicate requests for sensor data
my $sdr = $sdr_hash{$key};
if (($doall and not $sdr->rec_type == 0x11 and not $sdr->sensor_type == 0xed) or ($sdr->rec_type == 0x01 and $sdr->sensor_type == $filter)) {
my $lformat = $format;
push @{ $sessdata->{sensorstoread} }, $sdr;
$usedkeys{$key} = 1;
} elsif ($filter eq "watt" and $sdr->sensor_units_2 and $sdr->sensor_units_2 == 0x06) {
push @{ $sessdata->{sensorstoread} }, $sdr;
$usedkeys{$key} = 1;
}
}
}
if ($sensor_filters{leds}) {
push @{ $sessdata->{sensorstoread} }, "leds";
#my @cleds;
#($rc,@cleds) = checkleds();
#push @output,@cleds;
}
if ($sensor_filters{powerstate} and not $sensor_filters{chassis}) {
push @{ $sessdata->{sensorstoread} }, "powerstat";
#($rc,$text) = power("stat");
#$text = sprintf($format,"Power Status:",$text,"");
#push(@output,$text);
}
if ($sensor_filters{energy}) {
if ($iem_support) {
push @{ $sessdata->{sensorstoread} }, "energy";
} #elsif (not $doall) {
#xCAT::SvrUtils::sendmsg([ 1, ":Energy data requires additional IBM::EnergyManager plugin in conjunction with IMM managed IBM equipment" ], $callback, $sessdata->{node}, %allerrornodes);
#}
#my @energies;
#($rc,@energies)=readenergy();
#push @output,@energies;
}
if ($sensor_filters{chassis}) {
unshift @{ $sessdata->{sensorstoread} }, "chassis";
}
if (scalar @{ $sessdata->{sensorstoread} }) {
$sessdata->{currsdr} = shift @{ $sessdata->{sensorstoread} };
readsensor($sessdata); #and we are off
}
}
sub sensorformat {
my $sessdata = shift;
my $sdr = $sessdata->{currsdr};
my $rc = shift;
my $reading = shift;
my $extext = shift;
my $unitdesc = "";
my $value;
my $lformat = "%-30s %-20s";
my $per = " ";
my $data;
if ($rc == 0) {
$data = translate_sensor($reading, $sdr);
} else {
$data = "N/A";
}
#$unitdesc.= sprintf(" %x",$sdr->sensor_type);
# use Data::Dumper;
# print Dumper($lformat,$sdr->id_string,$data);
my $text = sprintf($lformat, $sdr->id_string . ":", $data);
if ($extext) {
$text = "$text ($extext)";
}
if ($sessdata->{bmcnum} != 1) { $text .= " on BMC " . $sessdata->{bmcnum}; }
xCAT::SvrUtils::sendmsg($text, $callback, $sessdata->{node}, %allerrornodes);
if (scalar @{ $sessdata->{sensorstoread} }) {
$sessdata->{currsdr} = shift @{ $sessdata->{sensorstoread} };
readsensor($sessdata); #next
}
}
sub readsensor {
my $sessdata = shift;
if (not ref $sessdata->{currsdr}) {
if ($sessdata->{currsdr} eq "leds") {
checkleds($sessdata);
return;
} elsif ($sessdata->{currsdr} eq "powerstat") {
$sessdata->{powerstatprefix} = "Power Status: ";
$sessdata->{subcommand} = "stat";
power($sessdata);
return;
} elsif ($sessdata->{currsdr} eq "chassis") {
readchassis($sessdata);
return;
} elsif ($sessdata->{currsdr} eq "energy") {
readenergy($sessdata);
return;
} else {
xCAT::SvrUtils::sendmsg([ 1, "TODO: make " . $sessdata->{currsdr} . " work again" ], $callback, $sessdata->{node}, %allerrornodes);
}
return;
}
my $sensor = $sessdata->{currsdr}->sensor_number;
$sessdata->{ipmisession}->subcmd(netfn => 0x4, command => 0x2d, data => [$sensor], callback => \&sensor_was_read, callback_args => $sessdata);
}
sub sensor_was_read {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $rsp->{error} ], $callback, $sessdata->{node}, %allerrornodes);
return;
}
if ($rsp->{code}) {
my $text = $codes{ $rsp->{code} };
unless ($text) { $text = sprintf("Unknown error %02xh", $rsp->{code}) }
return sensorformat($sessdata, 1, $text);
}
my @returnd = (0, @{ $rsp->{data} });
if ($returnd[2] & 0x20) {
return sensorformat($sessdata, 1, "N/A");
}
my $text = $returnd[1];
my $exdata1 = $returnd[3];
my $exdata2 = $returnd[3];
my $extext;
my @exparts;
my $sdr = $sessdata->{currsdr};
if ($sdr->event_type_code == 0x1) {
if ($exdata1 & 1 << 5) {
$extext = "At or above upper non-recoverable threshold";
} elsif ($exdata1 & 1 << 4) {
$extext = "At or above upper critical threshold";
} elsif ($exdata1 & 1 << 3) {
$extext = "At or above upper non-critical threshold";
}
if ($exdata1 & 1 << 2) {
$extext = "At or below lower non-critical threshold";
} elsif ($exdata1 & 1 << 1) {
$extext = "At or below lower critical threshold";
} elsif ($exdata1 & 1) {
$extext = "At or below lower non-recoverable threshold";
}
} elsif ($sdr->event_type_code == 0x6f) {
@exparts = ();
if ($sdr->sensor_type == 0x10) {
if ($exdata1 & 1 << 4) {
push @exparts, "SEL full";
} elsif ($exdata1 & 1 << 5) {
push @exparts, "SEL almost full";
}
if ($exdata1 & 1) {
push @exparts, "Correctable Memory Error Logging Disabled";
}
if ($exdata1 & 1 << 3) {
push @exparts, "All logging disabled";
} elsif ($exdata1 & 1 << 1) {
push @exparts, "Some logging disabled";
}
} elsif ($sdr->sensor_type == 0x7) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "IERR";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Thermal trip";
}
if ($exdata1 & 1 << 2) {
push @exparts, "FRB1/BIST failure";
}
if ($exdata1 & 1 << 3) {
push @exparts, "FRB2/Hang in POST due to processor";
}
if ($exdata1 & 1 << 4) {
push @exparts, "FRB3/Processor Initialization failure";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Configuration error";
}
if ($exdata1 & 1 << 6) {
push @exparts, "Uncorrectable CPU-complex error";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Present";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Processor disabled";
}
if ($exdata1 & 1 << 9) {
push @exparts, "Terminator present";
}
if ($exdata1 & 1 << 10) {
push @exparts, "Hardware throttled";
}
if ($exdata1 & 1 << 11) {
push @exparts, "Uncorrectable Machine Check Exception";
}
if ($exdata1 & 1 << 12) {
push @exparts, "Correctable Machine Check Error";
}
} elsif ($sdr->sensor_type == 0x8) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "Present";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Failed";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Failure predicted";
}
if ($exdata1 & 1 << 3) {
push @exparts, "AC Lost";
}
if ($exdata1 & 1 << 4) {
push @exparts, "AC input lost or out of range";
}
if ($exdata1 & 1 << 5) {
push @exparts, "AC input out of range";
}
if ($exdata1 & 1 << 6) {
push @exparts, "Configuration error";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Power Supply Inactive";
}
if (@exparts) {
$extext = join(",", @exparts);
}
} elsif ($sdr->sensor_type == 0x13) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "Front panel NMI/Diagnostic";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Bus timeout";
}
if ($exdata1 & 1 << 2) {
push @exparts, "I/O channel check NMI";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Software NMI";
}
if ($exdata1 & 1 << 4) {
push @exparts, "PCI PERR";
}
if ($exdata1 & 1 << 5) {
push @exparts, "PCI SERR";
}
if ($exdata1 & 1 << 6) {
push @exparts, "EISA failsafe timeout";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Bus correctable .rror";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Bus uncorrectable error";
}
if ($exdata1 & 1 << 9) {
push @exparts, "Fatal NMI";
}
if ($exdata1 & 1 << 10) {
push @exparts, "Bus fatal error";
}
if ($exdata1 & 1 << 11) {
push @exparts, "Bus Degraded";
}
} elsif ($sdr->sensor_type == 0xc) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "Correctable error(s)";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Uncorrectable error(s)";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Parity";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Memory scrub failure";
}
if ($exdata1 & 1 << 4) {
push @exparts, "DIMM disabled";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Correctable error limit reached";
}
if ($exdata1 & 1 << 6) {
push @exparts, "Present";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Configuration error";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Spare";
}
if ($exdata1 & 1 << 9) {
push @exparts, "Memory Automatically Throttled";
}
if ($exdata1 & 1 << 10) {
push @exparts, "Critical Overtemperature";
}
if (@exparts) {
$extext = join(",", @exparts);
}
} elsif ($sdr->sensor_type == 0x21) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "Fault";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Identify";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Installed/attached";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Ready for install";
}
if ($exdata1 & 1 << 4) {
push @exparts, "Ready for removal";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Powered off";
}
if ($exdata1 & 1 << 6) {
push @exparts, "Removal requested";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Interlocked";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Disabled";
}
if ($exdata1 & 1 << 9) {
push @exparts, "Spare";
}
} elsif ($sdr->sensor_type == 0xf) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "POST error";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Firmware hang";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Firmware progress";
}
} elsif ($sdr->sensor_type == 0x9) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "Power off";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Power Cycle";
}
if ($exdata1 & 1 << 2) {
push @exparts, "240VA Power Down";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Interlock Power Down";
}
if ($exdata1 & 1 << 4) {
push @exparts, "AC lost";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Soft power control failure";
}
if ($exdata1 & 1 << 6) {
push @exparts, "Power unit failure";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Power unit failure predicted";
}
} elsif ($sdr->sensor_type == 0x12) {
@exparts = ();
if ($exdata1 & 1) {
push @exparts, "System Reconfigured";
}
if ($exdata1 & 1 << 1) {
push @exparts, "OEM System Boot Event";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Undetermined system hardware failure";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Aux log manipulated";
}
if ($exdata1 & 1 << 4) {
push @exparts, "PEF Action";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Timestamp Clock Synch";
}
} elsif ($sdr->sensor_type == 0x1e) {
if ($exdata1 & 1) {
push @exparts, "No bootable media";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Non-bootable diskette left in drive";
}
if ($exdata1 & 1 << 2) {
push @exparts, "PXE Server not found";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Invalid boot sector";
}
if ($exdata1 & 1 << 4) {
push @exparts, "Timeout waiting for user selection";
}
} elsif ($sdr->sensor_type == 0x1f) {
if ($exdata1 & 1) {
push @exparts, "boot completed";
}
if ($exdata1 & 1 << 1) {
push @exparts, "boot completed";
}
if ($exdata1 & 1 << 2) {
push @exparts, "PXE boot completed";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Diagnostic boot completed";
}
if ($exdata1 & 1 << 4) {
push @exparts, "CD-ROM boot completed";
}
if ($exdata1 & 1 << 5) {
push @exparts, "ROM boot completed";
}
if ($exdata1 & 1 << 6) {
push @exparts, "boot completed-device not specified";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Base OS Installation started";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Base OS Installation completed";
}
if ($exdata1 & 1 << 9) {
push @exparts, "Base OS Installation aborted";
}
if ($exdata1 & 1 << 9) {
push @exparts, "Base OS Installation failed";
}
} elsif ($sdr->sensor_type == 0x25) {
if ($exdata1 & 1) {
push @exparts, "Present";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Absent";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Disabled";
}
} elsif ($sdr->sensor_type == 0x23) {
if ($exdata1 & 1) {
push @exparts, "Expired";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Hard Reset";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Power Down";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Power Cycle";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Timer interrupt";
}
} elsif ($sdr->sensor_type == 0xd) {
if ($exdata1 & 1) {
push @exparts, "Present";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Fault";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Failure Predicted";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Hot Spare";
}
if ($exdata1 & 1 << 4) {
push @exparts, "Consistency Check";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Critical Array";
}
if ($exdata1 & 1 << 6) {
push @exparts, "Failed Array";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Rebuilding";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Rebuild aborted";
}
} elsif ($sdr->sensor_type == 0x20) {
if ($exdata1 & 1) {
push @exparts, "Critical stop during OS load";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Runtime Critical Stop";
}
if ($exdata1 & 1 << 2) {
push @exparts, "OS Graceful Stop";
}
if ($exdata1 & 1 << 3) {
push @exparts, "OS Graceful Shutdown";
}
if ($exdata1 & 1 << 4) {
push @exparts, "Soft Shutdown";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Agent Not Responding";
}
} elsif ($sdr->sensor_type == 0x22) {
if ($exdata1 & 1) {
push @exparts, "working";
}
if ($exdata1 & 1 << 1) {
push @exparts, "sleeping";
}
if ($exdata1 & 1 << 2) {
push @exparts, "processor context lost";
}
if ($exdata1 & 1 << 3) {
push @exparts, "memory retained";
}
if ($exdata1 & 1 << 4) {
push @exparts, "non-volatile sleep";
}
if ($exdata1 & 1 << 5) {
push @exparts, "soft-off";
}
if ($exdata1 & 1 << 6) {
push @exparts, "soft-off";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Mechanical off";
}
if ($exdata1 & 1 << 8) {
push @exparts, "Sleeping in as S1,S2, or S3 states";
}
if ($exdata1 & 1 << 9) {
push @exparts, "sleeping";
}
if ($exdata1 & 1 << 10) {
push @exparts, "entered by override";
}
if ($exdata1 & 1 << 11) {
push @exparts, "Legacy ON";
}
if ($exdata1 & 1 << 12) {
push @exparts, "Legacy OFF";
}
if ($exdata1 & 1 << 13) {
push @exparts, "Unknown";
}
} elsif ($sdr->sensor_type == 0x28) {
if ($exdata1 & 1) {
push @exparts, "Degraded or unavailable";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Degraded or unavailable";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Offline";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Unavailable";
}
if ($exdata1 & 1 << 4) {
push @exparts, "Sensor Failure";
}
if ($exdata1 & 1 << 5) {
push @exparts, "FRU Failure";
}
} elsif ($sdr->sensor_type == 0x29) {
if ($exdata1 & 1) {
push @exparts, "battery low";
}
if ($exdata1 & 1 << 1) {
push @exparts, "battery failed";
}
if ($exdata1 & 1 << 2) {
push @exparts, "battery presence detected";
}
} elsif ($sdr->sensor_type == 0x2a) {
if ($exdata1 & 1) {
push @exparts, "Session Activated";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Session Deactivated";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Invalid username or password";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Invalid password disable";
}
} elsif ($sdr->sensor_type == 0x2b) {
if ($exdata1 & 1) {
push @exparts, "Change detected";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Firmware change detected";
}
if ($exdata1 & 1 << 2) {
push @exparts, "Hardware incompatibility detected";
}
if ($exdata1 & 1 << 3) {
push @exparts, "Firmware incompatibility detected";
}
if ($exdata1 & 1 << 4) {
push @exparts, "Unsupported hardware version";
}
if ($exdata1 & 1 << 5) {
push @exparts, "Unsupported firmware verion";
}
if ($exdata1 & 1 << 6) {
push @exparts, "Hardware change successful";
}
if ($exdata1 & 1 << 7) {
push @exparts, "Firmware change successful";
}
} elsif ($sdr->sensor_type == 0x1b) {
if ($exdata1 & 1) {
push @exparts, "Cable connected";
}
if ($exdata1 & 1 << 1) {
push @exparts, "Incorrect cable connection";
}
} elsif ($sdr->sensor_type == 0x2c) {
if ($exdata1 & 1) {
push @exparts, "FRU Not Installed";
}
if ($exdata1 & 1 << 1) {
push @exparts, "FRU Inactive";
}
if ($exdata1 & 1 << 2) {
push @exparts, "FRU Activation Requested";
}
if ($exdata1 & 1 << 3) {
push @exparts, "FRU Activation In Progress";
}
if ($exdata1 & 1 << 4) {
push @exparts, "FRU Active";
}
if ($exdata1 & 1 << 5) {
push @exparts, "FRU Deactivation Requested";
}
if ($exdata1 & 1 << 6) {
push @exparts, "FRU Deactivation In Progress";
}
if ($exdata1 & 1 << 7) {
push @exparts, "FRU Communication Lost";
}
} elsif ($sdr->sensor_type >= 0xc0) {
$extext = "OEM Reserved " . $sdr->sensor_type;
} else {
$extext = "xCAT needs to add support for " . $sdr->sensor_type;
}
if (@exparts) {
$extext = join(",", @exparts);
}
}
return sensorformat($sessdata, 0, $text, $extext);
}
sub initsdr {
my $sessdata = shift;
my $netfun;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my $code;
my $resv_id_ls;
my $resv_id_ms;
my $sdr_type;
my $sdr_offset;
my $sdr_len;
my @sdr_data = ();
my $offset;
my $len;
my $i;
# my $numbytes = 27;
my $ipmisensortab = "$ENV{XCATROOT}/lib/GUMI/ipmisensor.tab";
my $byte_format;
my $cache_file;
#device id data TODO
$sessdata->{ipmisession}->subcmd(netfn => 0x0a, command => 0x20, data => [], callback => \&got_sdr_rep_info, callback_args => $sessdata);
}
sub initsdr_withrepinfo {
my $sessdata = shift;
my $mfg_id = $sessdata->{mfg_id};
my $prod_id = $sessdata->{prod_id};
my $device_id = $sessdata->{device_id};
my $dev_rev = $sessdata->{device_rev};
my $fw_rev1 = $sessdata->{firmware_rev1};
my $fw_rev2 = $sessdata->{firmware_rev2};
#TODO: beware of dynamic SDR contents
my $cache_file = "$cache_dir/sdr_$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2";
$sessdata->{sdrcache_file} = $cache_file;
if ($enable_cache eq "yes") {
if ($sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2"}) {
$sessdata->{sdr_hash} = $sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2"};
on_bmc_connect("SUCCESS", $sessdata); #retry bmc_connect since sdr_cache is validated
return; #don't proceed to slow load
} else {
my $rc = loadsdrcache($sessdata, $cache_file);
if ($rc == 0) {
$sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2"} = $sessdata->{sdr_hash};
on_bmc_connect("SUCCESS", $sessdata); #retry bmc_connect since sdr_cache is validated
return; #don't proceed to slow load
}
}
}
if ($sessdata->{sdr_info}->{version} != 0x51) {
sendoutput(1, "SDR version unsupported.");
return (1); #bail, do not try to continue
}
if ($sessdata->{sdr_info}->{resv_sdr} != 1) {
sendoutput(1, "SDR reservation unsupported.");
return 1;
}
$sessdata->{ipmisession}->subcmd(netfn => 0x0a, command => 0x22, data => [], callback => \&reserved_sdr_repo, callback_args => $sessdata);
}
sub initsdr_withreservation {
my $sessdata = shift;
my $rid_ls = 0;
my $rid_ms = 0;
if ($sessdata->{sdr_nrid_ls}) { $rid_ls = $sessdata->{sdr_nrid_ls}; }
if ($sessdata->{sdr_nrid_ms}) { $rid_ms = $sessdata->{sdr_nrid_ms}; }
####if($debug) {
#### print "mfg,prod,dev: $mfg_id, $prod_id, $device_id\n";
#### printf("SDR info: %02x %d %d\n",$sdr_rep_info->version,$sdr_rep_info->rec_count,$sdr_rep_info->resv_sdr);
#### print "resv_id: $resv_id_ls $resv_id_ms\n";
####}
my $resv_id_ls = $sessdata->{resv_id_ls};
my $resv_id_ms = $sessdata->{resv_id_ms};
if ($rid_ls == 0xff and $rid_ms == 0xff) {
if ($enable_cache eq "yes") { #cache SDR repository for future use
storsdrcache($sessdata->{sdrcache_file}, $sessdata);
}
on_bmc_connect("SUCCESS", $sessdata); #go back armed with a capable reserviction
return; #Have reached the end
}
$sessdata->{sdr_fetch_args} = [ $resv_id_ls, $resv_id_ms, $rid_ls, $rid_ms, 0, 5 ];
$sessdata->{ipmisession}->subcmd(netfn => 0xa, command => 0x23, data => $sessdata->{sdr_fetch_args}, callback => \&start_sdr_record, callback_args => $sessdata);
return;
}
sub start_sdr_record {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
sendoutput(1, $rsp->{error});
return;
}
my $resv_id_ls = shift @{ $sessdata->{sdr_fetch_args} };
my $resv_id_ms = shift @{ $sessdata->{sdr_fetch_args} };
my $rid_ls = shift @{ $sessdata->{sdr_fetch_args} };
my $rid_ms = shift @{ $sessdata->{sdr_fetch_args} };
my @returnd = ($rsp->{code}, @{ $rsp->{data} });
my $code = $returnd[0];
if ($code != 0x00) {
my $text = $codes{$code};
if (!$text) {
$text = sprintf("unknown response %02x", $code);
}
sendoutput(1, $text);
return;
}
$sessdata->{sdr_nrid_ls} = $returnd[1];
$sessdata->{sdr_nrid_ms} = $returnd[2];
my $sdr_ver = $returnd[5];
my $sdr_type = $returnd[6];
$sessdata->{curr_sdr_type} = $sdr_type;
$sessdata->{curr_sdr_len} = $returnd[7] + 5;
if ($sdr_type == 0x01) {
$sessdata->{total_sdr_offset} = 0;
}
elsif ($sdr_type == 0x02) {
$sessdata->{total_sdr_offset} = 16; #TODO: understand this..
}
elsif ($sdr_type == 0xC0) {
#LED descriptor, maybe
}
elsif ($sdr_type == 0x11) { #FRU locator
}
elsif ($sdr_type == 0x12) {
initsdr_withreservation($sessdata); #next, skip this unsupported record type
return;
}
else {
initsdr_withreservation($sessdata); #next
return;
}
$sessdata->{sdr_data} = [ 0, 0, 0, $sdr_ver, $sdr_type, $sessdata->{curr_sdr_len} ]; #seems that an extra zero is prepended to allow other code to do 1 based counting out of laziness to match our index to the spec indicated index
$sessdata->{sdr_offset} = 5;
my $offset = 5; #why duplicate? to make for shorter typing
my $numbytes = 22;
if (5 < $sessdata->{curr_sdr_len}) { #can't imagine this not bing the case,but keep logic in case
if ($offset + $numbytes > $sessdata->{curr_sdr_len}) { #scale back request for remainder
$numbytes = $sessdata->{curr_sdr_len} - $offset;
}
$sessdata->{sdr_fetch_args} = [ $resv_id_ls, $resv_id_ms, $rid_ls, $rid_ms, $offset, $numbytes ];
$sessdata->{ipmisession}->subcmd(netfn => 0x0a, command => 0x23, data => $sessdata->{sdr_fetch_args}, callback => \&add_sdr_data, callback_args => $sessdata);
return;
} else {
initsdr_withreservation($sessdata); #next
return;
}
}
sub add_sdr_data {
my $rsp = shift;
my $sessdata = shift;
my $numbytes = $sessdata->{sdr_fetch_args}->[5];
my $offset = $sessdata->{sdr_offset}; #shorten typing a little
if ($rsp->{error}) {
sendoutput([ 1, $rsp->{error} ]);
return; #give up
}
my @returnd = ($rsp->{code}, @{ $rsp->{data} });
my $code = $returnd[0];
if ($code != 0x00) {
my $text = $codes{$code};
if (!$text) {
$text = sprintf("unknown response %02x", $code);
}
sendoutput([ 1, $text ]);
return; #abort the whole mess
}
push @{ $sessdata->{sdr_data} }, @returnd[ 3 .. @returnd - 1 ];
$sessdata->{sdr_offset} += $numbytes;
if ($sessdata->{sdr_offset} + $numbytes > $sessdata->{curr_sdr_len}) { #scale back request for remainder
$numbytes = $sessdata->{curr_sdr_len} - $sessdata->{sdr_offset};
}
$sessdata->{sdr_fetch_args}->[4] = $sessdata->{sdr_offset};
$sessdata->{sdr_fetch_args}->[5] = $numbytes;
if ($sessdata->{sdr_offset} < $sessdata->{curr_sdr_len}) {
$sessdata->{ipmisession}->subcmd(netfn => 0x0a, command => 0x23, data => $sessdata->{sdr_fetch_args}, callback => \&add_sdr_data, callback_args => $sessdata);
return;
} else { #in this case, time to parse the accumulated data
parse_sdr($sessdata);
}
}
sub parse_sdr { #parse sdr data, then cann initsdr_withreserveation to advance to next record
my $sessdata = shift;
my @sdr_data = @{ $sessdata->{sdr_data} };
#not bothering trying to keep a packet pending concurrent with operation, harder to code that
my $mfg_id = $sessdata->{mfg_id};
my $prod_id = $sessdata->{prod_id};
my $device_id = $sessdata->{device_id};
my $dev_rev = $sessdata->{device_rev};
my $fw_rev1 = $sessdata->{firmware_rev1};
my $fw_rev2 = $sessdata->{firmware_rev2};
my $sdr_type = $sessdata->{curr_sdr_type};
if ($sdr_type == 0x11) { #FRU locator
my $sdr = decode_fru_locator(@sdr_data);
if ($sdr) {
$sessdata->{sdr_hash}->{ $sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number } = $sdr;
}
initsdr_withreservation($sessdata); #advance to next record
return;
}
####if($debug) {
#### hexadump(\@sdr_data);
####}
if ($sdr_type == 0x12) { #if required, TODO support type 0x12
hexadump(\@sdr_data);
initsdr_withreservation($sessdata); #next record
return;
}
my $sdr = SDR->new();
if (is_systemx($sessdata) && $sdr_type == 0xC0 && $sdr_data[9] == 0xED) {
#printf("%02x%02x\n",$sdr_data[13],$sdr_data[12]);
$sdr->rec_type($sdr_type);
$sdr->sensor_type($sdr_data[9]);
#Using an impossible sensor number to not conflict with decodealert
$sdr->sensor_owner_id(260);
$sdr->sensor_owner_lun(260);
$sdr->id_string("LED");
if ($sdr_data[12] > $sdr_data[13]) {
$sdr->led_id(($sdr_data[13] << 8) + $sdr_data[12]);
} else {
$sdr->led_id(($sdr_data[12] << 8) + $sdr_data[13]);
}
if (scalar(@sdr_data) > 17) { #well what do you know, we have an ascii description, probably...
my $id = unpack("Z*", pack("C*", @sdr_data[ 16 .. $#sdr_data ]));
if ($id) { $sdr->id_string($id); }
}
#$sdr->led_id_ms($sdr_data[13]);
#$sdr->led_id_ls($sdr_data[12]);
$sdr->sensor_number(sprintf("%04x", $sdr->led_id));
#printf("%02x,%02x,%04x\n",$mfg_id,$prod_id,$sdr->led_id);
#Was going to have a human readable name, but specs
#seem to not to match reality...
#$override_string = getsensorname($mfg_id,$prod_id,$sdr->sensor_number,$ipmiledtab);
#I'm hacking in owner and lun of 260 for LEDs....
$sessdata->{sdr_hash}->{ "260.260." . $sdr->led_id } = $sdr;
initsdr_withreservation($sessdata); #next record
return;
}
$sdr->rec_type($sdr_type);
$sdr->sensor_owner_id($sdr_data[6]);
$sdr->sensor_owner_lun($sdr_data[7]);
$sdr->sensor_number($sdr_data[8]);
$sdr->entity_id($sdr_data[9]);
$sdr->entity_instance($sdr_data[10]);
$sdr->sensor_type($sdr_data[13]);
$sdr->event_type_code($sdr_data[14]);
$sdr->sensor_units_2($sdr_data[22]);
$sdr->sensor_units_3($sdr_data[23]);
if ($sdr_type == 0x01) {
$sdr->sensor_units_1($sdr_data[21]);
$sdr->linearization($sdr_data[24] & 0b01111111);
$sdr->M(comp2int(10, (($sdr_data[26] & 0b11000000) << 2) + $sdr_data[25]));
$sdr->B(comp2int(10, (($sdr_data[28] & 0b11000000) << 2) + $sdr_data[27]));
$sdr->R_exp(comp2int(4, ($sdr_data[30] & 0b11110000) >> 4));
$sdr->B_exp(comp2int(4, $sdr_data[30] & 0b00001111));
} elsif ($sdr_type == 0x02) {
$sdr->sensor_units_1($sdr_data[21]);
}
$sdr->id_string_type($sdr_data[ 48 - $sessdata->{total_sdr_offset} ]);
my $override_string = getsensorname($mfg_id, $prod_id, $sdr->sensor_number);
if ($override_string ne "") {
$sdr->id_string($override_string);
}
else {
unless (defined $sdr->id_string_type) { initsdr_withreservation($sessdata); return; }
my $byte_format = ($sdr->id_string_type & 0b11000000) >> 6;
if ($byte_format == 0b11) {
my $len = ($sdr->id_string_type & 0b00011111) - 1;
if ($len > 1) {
$sdr->id_string(pack("C*", @sdr_data[ 49 - $sessdata->{total_sdr_offset} .. 49 - $sessdata->{total_sdr_offset} + $len ]));
}
else {
$sdr->id_string("no description");
}
}
elsif ($byte_format == 0b10) {
$sdr->id_string("ASCII packed unsupported");
}
elsif ($byte_format == 0b01) {
$sdr->id_string("BCD unsupported");
}
elsif ($byte_format == 0b00) {
my $len = ($sdr->id_string_type & 0b00011111) - 1;
if ($len > 1) { #It should be something, but need sample to code
$sdr->id_string("unicode unsupported");
} else {
initsdr_withreservation($sessdata); return;
}
}
}
$sessdata->{sdr_hash}->{ $sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number } = $sdr;
initsdr_withreservation($sessdata); return;
}
sub getsensorname
{
my $mfgid = shift;
my $prodid = shift;
my $sensor = shift;
my $file = shift;
my $sdr = shift;
my $mfg;
my $prod;
my $type;
my $desc;
my $name = "";
if ($file and $file eq "ibmleds") {
if ($sdr and $sdr->id_string ne "LED") { return $sdr->id_string; } # this is preferred mechanism
if ($xCAT::data::ibmleds::leds{"$mfgid,$prodid"}->{$sensor}) {
return $xCAT::data::ibmleds::leds{"$mfgid,$prodid"}->{$sensor} . " LED";
} elsif ($ndebug) {
return "Unknown $sensor/$mfgid/$prodid";
} else {
return sprintf("LED 0x%x", $sensor);
}
} else {
return "";
}
}
sub getchassiscap {
my $netfun = 0x00;
my @cmd;
my @returnd = ();
my $error;
my $rc = 0;
my $text;
my $code;
@cmd = (0x00);
$error = docmd(
$netfun,
\@cmd,
\@returnd
);
if ($error) {
$rc = 1;
$text = $error;
return ($rc, $text);
}
$code = $returnd[0];
if ($code == 0x00) {
$text = "";
}
else {
$rc = 1;
$text = $codes{$code};
if (!$text) {
$rc = 1;
$text = sprintf("unknown response %02x", $code);
}
return ($rc, $text);
}
return ($rc, @returnd[ 1 .. @returnd - 2 ]);
}
sub gotdevid {
#($rc,$text,$mfg_id,$prod_id,$device_id,$dev_rev,$fw_rev1,$fw_rev2) = getdevid();
my $rsp = shift;
my $sessdata = shift;
my $text;
if ($rsp->{error}) {
sendoutput([ 1, $rsp->{error} ]);
return;
}
else {
my $code = $rsp->{code};
if ($code != 0x00) {
my $text = $codes{$code};
if (!$text) {
$text = sprintf("unknown response %02x", $code);
}
sendoutput([ 1, $text ]);
return;
}
}
my @returnd = ($rsp->{code}, @{ $rsp->{data} });
$sessdata->{device_id} = $returnd[1];
$sessdata->{device_rev} = $returnd[2] & 0b00001111;
$sessdata->{firmware_rev1} = $returnd[3] & 0b01111111;
$sessdata->{firmware_rev2} = $returnd[4];
$sessdata->{ipmi_ver} = $returnd[5];
$sessdata->{dev_support} = $returnd[6];
####my $sensor_device = 0;
####my $SDR = 0;
####my $SEL = 0;
####my $FRU = 0;
####my $IPMB_ER = 0;
####my $IPMB_EG = 0;
####my $BD = 0;
####my $CD = 0;
####if($dev_support & 0b00000001) {
#### $sensor_device = 1;
####}
####if($dev_support & 0b00000010) {
#### $SDR = 1;
####}
####if($dev_support & 0b00000100) {
#### $SEL = 1;
####}
####if($dev_support & 0b00001000) {
#### $FRU = 1;
####}
####if($dev_support & 0b00010000) {
#### $IPMB_ER = 1;
####}
####if($dev_support & 0b00100000) {
#### $IPMB_EG = 1;
####}
####if($dev_support & 0b01000000) {
#### $BD = 1;
####}
####if($dev_support & 0b10000000) {
#### $CD = 1;
####}
$sessdata->{mfg_id} = $returnd[7] + $returnd[8] * 0x100 + $returnd[9] * 0x10000;
$sessdata->{prod_id} = $returnd[10] + $returnd[11] * 0x100;
on_bmc_connect("SUCCESS", $sessdata);
# my @data = @returnd[12..@returnd-2];
# return($rc,$text,$mfg_id,$prod_id,$device_id,$device_rev,$firmware_rev1,$firmware_rev2);
}
sub gotguid {
if (check_rsp_errors(@_)) {
return;
}
my $rsp = shift;
my $sessdata = shift;
#my @guidcmd = (0x18,0x37);
#if($mfg_id == 2 && $prod_id == 34869) { TODO: if GUID is inaccurate on the products mentioned, this code may be uncommented
# @guidcmd = (0x18,0x08);
#}
#if($mfg_id == 2 && $prod_id == 4) {
# @guidcmd = (0x18,0x08);
#}
#if($mfg_id == 2 && $prod_id == 3) {
# @guidcmd = (0x18,0x08);
#}
my $fru = FRU->new();
$fru->rec_type("guid");
$fru->desc("UUID/GUID");
my $binuuid = pack("C*", @{ $rsp->{data} });
my @pieces = unpack("VvvNNN", $binuuid);
my @uuid = unpack("C*", pack("NnnNNN", @pieces));
$fru->value(sprintf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", @uuid));
$sessdata->{fru_hash}->{guid} = $fru;
initfru_withguid($sessdata);
}
sub got_sdr_rep_info {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
sendoutput([ 1, $rsp->{error} ]);
return;
}
else {
my $code = $rsp->{code};
if ($code != 0x00) {
my $text = $codes{$code};
if (!$text) {
$text = sprintf("unknown response %02x", $code);
}
sendoutput(1, $text);
return;
}
}
my @returnd = @{ $rsp->{data} };
$sessdata->{sdr_info}->{version} = $returnd[0];
$sessdata->{sdr_info}->{rec_count} = $returnd[1] + $returnd[2] << 8;
$sessdata->{sdr_info}->{resv_sdr} = ($returnd[13] & 0b00000010) >> 1;
initsdr_withrepinfo($sessdata);
}
sub reserved_sdr_repo {
my $rsp = shift;
my $sessdata = shift;
if ($rsp->{error}) {
sendoutput([ 1, $rsp->{error} ]);
return;
}
else {
my $code = $rsp->{code};
if ($code != 0x00) {
my $text = $codes{$code};
if (!$text) {
$text = sprintf("unknown response %02x", $code);
}
sendoutput([ 1, $text ]);
}
}
my @returnd = @{ $rsp->{data} };
$sessdata->{resv_id_ls} = $returnd[0];
$sessdata->{resv_id_ms} = $returnd[1];
initsdr_withreservation($sessdata);
}
sub dochksum()
{
my $data = shift;
my $sum = 0;
foreach (@$data) {
$sum += $_;
}
$sum = ~$sum + 1;
return ($sum & 0xFF);
}
sub hexdump {
my $data = shift;
foreach (@$data) {
printf("%02x ", $_);
}
print "\n";
}
sub getascii {
my @alpha;
my $text = "";
my $c = 0;
foreach (@_) {
if (defined $_ and $_ < 128 and $_ > 0x20) {
$alpha[$c] = sprintf("%c", $_);
} else {
$alpha[$c] = " ";
}
if ($alpha[$c] !~ /[\S]/) {
if ($alpha[ ($c - 1) ] !~ /\s/) {
$alpha[$c] = " ";
} else {
$c--;
}
}
$c++;
}
foreach (@alpha) {
$text = $text . $_;
}
$text =~ s/^\s+|\s+$//;
return $text;
}
sub phex {
my $data = shift;
my @alpha;
my $text = "";
my $c = 0;
foreach (@$data) {
$text = $text . sprintf("%02x ", $_);
$alpha[$c] = sprintf("%c", $_);
if ($alpha[$c] !~ /\w/) {
$alpha[$c] = " ";
}
$c++;
}
$text = $text . "(";
foreach (@alpha) {
$text = $text . $_;
}
$text = $text . ")";
return $text;
}
sub hexadump {
my $data = shift;
my @alpha;
my $c = 0;
foreach (@$data) {
printf("%02x ", $_);
$alpha[$c] = sprintf("%c", $_);
if ($_ < 0x20 or $_ > 0x7e) {
$alpha[$c] = ".";
}
$c++;
if ($c == 16) {
print " ";
foreach (@alpha) {
print $_;
}
print "\n";
@alpha = ();
$c = 0;
}
}
foreach ($c .. 16) {
print " ";
}
foreach (@alpha) {
print $_;
}
print "\n";
}
sub comp2int {
my $length = shift;
my $bits = shift;
my $neg = 0;
if ($bits & 2**($length - 1)) {
$neg = 1;
}
$bits &= (2**($length - 1) - 1);
if ($neg) {
$bits -= 2**($length - 1);
}
return ($bits);
}
sub timestamp2datetime {
my $ts = shift;
if ($ts < 0x20000000) {
return "BMC Uptime", sprintf("%6d s", $ts);
}
my @t = localtime($ts);
my $time = strftime("%H:%M:%S", @t);
my $date = strftime("%m/%d/%Y", @t);
return ($date, $time);
}
sub decodebcd {
my $numbers = shift;
my @bcd;
my $text;
my $ms;
my $ls;
foreach (@$numbers) {
$ms = ($_ & 0b11110000) >> 4;
$ls = ($_ & 0b00001111);
push(@bcd, $ms);
push(@bcd, $ls);
}
foreach (@bcd) {
if ($_ < 0x0a) {
$text .= $_;
}
elsif ($_ == 0x0a) {
$text .= " ";
}
elsif ($_ == 0x0b) {
$text .= "-";
}
elsif ($_ == 0x0c) {
$text .= ".";
}
}
return ($text);
}
sub storsdrcache {
my $file = shift;
my $sessdata = shift;
unless ($sessdata) { die "need to fix this one too" }
my $key;
my $fh;
system("mkdir -p $cache_dir");
if (!open($fh, ">$file")) {
return (1);
}
flock($fh, LOCK_EX) || return (1);
my $hdr;
$hdr->{xcat_sdrcacheversion} = $cache_version;
nstore_fd($hdr, $fh);
foreach $key (keys %{ $sessdata->{sdr_hash} }) {
my $r = $sessdata->{sdr_hash}->{$key};
nstore_fd($r, $fh);
}
close($fh);
return (0);
}
sub loadsdrcache {
my $sessdata = shift;
my $file = shift;
my $r;
my $c = 0;
my $fh;
if (!open($fh, "<$file")) {
return (1);
}
$r = retrieve_fd($fh);
unless ($r) { close($fh); return 1; }
unless ($r->{xcat_sdrcacheversion} and $r->{xcat_sdrcacheversion} == $cache_version) { close($fh); return 1; } #version mismatch
flock($fh, LOCK_SH) || return (1);
while () {
eval {
$r = retrieve_fd($fh);
} || last;
$sessdata->{sdr_hash}->{ $r->sensor_owner_id . "." . $r->sensor_owner_lun . "." . $r->sensor_number } = $r;
}
close($fh);
return (0);
}
sub randomizelist { #in place shuffle of list
my $list = shift;
my $index = @$list;
while ($index--) {
my $swap = int(rand($index + 1));
@$list[ $index, $swap ] = @$list[ $swap, $index ];
}
}
sub preprocess_request {
my $request = shift;
if (defined $request->{_xcatpreprocessed}->[0] and $request->{_xcatpreprocessed}->[0] == 1) {
# exit if preprocessed
return [$request];
}
my $callback = shift;
my @requests;
my $realnoderange = $request->{node}; #Should be arrayref
my $command = $request->{command}->[0];
my $extrargs = $request->{arg};
my @exargs = ($request->{arg});
my $delay = 0;
my $delayincrement = 0;
my $chunksize = 0;
if (ref($extrargs)) {
@exargs = @$extrargs;
}
my $usage_string = xCAT::Usage->parseCommand($command, @exargs);
if ($usage_string) {
$callback->({ data => $usage_string });
$request = {};
return;
}
if ($command eq "rpower") {
my $subcmd = $exargs[0];
if ($subcmd eq '') {
#$callback->({data=>["Please enter an action (eg: boot,off,on, etc)", $usage_string]});
#Above statement will miss error code, so replaced by the below statement
$callback->({ errorcode => [1], data => [ "Please enter an action (eg: boot,off,on, etc)", $usage_string ] });
$request = {};
return 0;
}
#pdu commands will be handled in the pdu plugin
if(($subcmd eq 'pduoff') || ($subcmd eq 'pduon') || ($subcmd eq 'pdustat') || ($subcmd eq 'pdureset')){
return 0;
}
if (($subcmd ne 'reseat') && ($subcmd ne 'stat') && ($subcmd ne 'state') && ($subcmd ne 'status') && ($subcmd ne 'on') && ($subcmd ne 'off') && ($subcmd ne 'softoff') && ($subcmd ne 'nmi') && ($subcmd ne 'cycle') && ($subcmd ne 'reset') && ($subcmd ne 'boot') && ($subcmd ne 'wake') && ($subcmd ne 'suspend')) {
#$callback->({data=>["Unsupported command: $command $subcmd", $usage_string]});
#Above statement will miss error code, so replaced by the below statement
$callback->({ errorcode => [1], data => [ "Unsupported command: $command $subcmd", $usage_string ] });
$request = {};
return;
}
if (($subcmd eq 'on' or $subcmd eq 'reset' or $subcmd eq 'boot') and $::XCATSITEVALS{syspowerinterval}) {
unless ($::XCATSITEVALS{syspowermaxnodes}) {
$callback->({ errorcode => [1], error => ["IPMI plugin requires syspowermaxnodes be defined if syspowerinterval is defined"] });
$request = {};
return 0;
}
$chunksize = $::XCATSITEVALS{syspowermaxnodes};
$delayincrement = $::XCATSITEVALS{syspowerinterval};
}
} elsif ($command eq "renergy") {
# filter out the nodes which should be handled by ipmi.pm
my (@bmcnodes, @nohandle);
xCAT::Utils->filter_nodes($request, undef, undef, \@bmcnodes, \@nohandle);
$realnoderange = \@bmcnodes;
} elsif ($command eq "rspconfig") {
# filter out the nodes which should be handled by ipmi.pm
my (@bmcnodes, @nohandle);
xCAT::Utils->filter_nodes($request, undef, undef, \@bmcnodes, \@nohandle);
$realnoderange = \@bmcnodes;
if ($realnoderange) {
my $optset;
my $option;
foreach (@exargs) {
if ($_ =~ /^(\w+)=(.*)/) {
if ($optset eq 0) {
$callback->({ errorcode => [1], data => [ "Usage Error: Cannot display and change attributes on the same command."] });
$request = {};
return;
}
$optset = 1;
$option = $1;
} else {
if ($optset eq 1) {
$callback->({ errorcode => [1], data => [ "Usage Error: Cannot display and change attributes on the same command."] });
$request = {};
return;
}
$option = $_;
$optset = 0;
}
unless ($option =~ /^USERID$|^ip$|^netmask$|^gateway$|^vlan$|^userid$|^username$|^password$|^snmpdest|^thermprofile$|^alert$|^garp$|^community$|^backupgateway$/) {
$callback->({ errorcode => [1], data => [ "Unsupported command: $command $_"] });
$request = {};
return;
}
}
}
} elsif ($command eq "rinv") {
if ($exargs[0] eq "-t" and $#exargs == 0) {
unshift @{ $request->{arg} }, 'all';
} elsif ((grep /-t/, @exargs) and !(grep /(all|vpd)/, @exargs)) {
$callback->({ errorcode => [1], error => ["option '-t' can only work with 'all' or 'vpd'"] });
$request = {};
return 0;
}
}
if (!$realnoderange) {
$usage_string = xCAT::Usage->getUsage($command);
$callback->({ data => $usage_string });
$request = {};
return;
}
#print "noderange=@$noderange\n";
# find service nodes for requested nodes
# build an individual request for each service node
my @noderanges;
srand();
if ($chunksize) {
#first, we try to spread out the chunks so they don't happen to correlate to constrained service nodes or circuits
#for now, will get the sn map for all of them and interleave if dispatching
#if not dispatching, will randomize the noderange instead to lower likelihood of turning everything on a circuit at once
if (defined $::XCATSITEVALS{ipmidispatch} and $::XCATSITEVALS{ipmidispatch} =~ /0|n/i) { #no SN indicated, instead do randomize
randomizelist($realnoderange);
} else { # sn is indicated
my $bigsnmap = xCAT::ServiceNodeUtils->get_ServiceNode($realnoderange, "xcat", "MN");
foreach my $servicenode (keys %$bigsnmap) { #let's also shuffle within each service node responsibliity
randomizelist($bigsnmap->{$servicenode})
}
#now merge the per-servicenode list into a big list again
$realnoderange = [];
while (keys %$bigsnmap) {
foreach my $servicenode (keys %$bigsnmap) {
if (@{ $bigsnmap->{$servicenode} }) {
push(@$realnoderange, pop(@{ $bigsnmap->{$servicenode} }));
} else {
delete $bigsnmap->{$servicenode};
}
}
}
}
while (scalar(@$realnoderange)) {
my @tmpnoderange;
while (scalar(@$realnoderange) and $chunksize) {
push @tmpnoderange, (shift @$realnoderange);
$chunksize--;
}
push @noderanges, \@tmpnoderange;
$chunksize = $::XCATSITEVALS{syspowermaxnodes};
}
} else {
@noderanges = ($realnoderange);
}
foreach my $noderange (@noderanges) {
my $sn;
if (defined $::XCATSITEVALS{ipmidispatch} and $::XCATSITEVALS{ipmidispatch} =~ /0|n/i) {
$sn = { '!xcatlocal!' => $noderange };
} else {
$sn = xCAT::ServiceNodeUtils->get_ServiceNode($noderange, "xcat", "MN");
}
# build each request for each service node
foreach my $snkey (keys %$sn) {
#print "snkey=$snkey\n";
my $reqcopy = {%$request};
$reqcopy->{node} = $sn->{$snkey};
unless ($snkey eq '!xcatlocal!') {
$reqcopy->{'_xcatdest'} = $snkey;
}
$reqcopy->{_xcatpreprocessed}->[0] = 1;
if ($delay) {
$reqcopy->{'_xcatdelay'} = $delay;
}
push @requests, $reqcopy;
}
$delay += $delayincrement;
}
return \@requests;
}
sub getipmicons {
my $argr = shift;
#$argr is [$node,$nodeip,$nodeuser,$nodepass];
my $cb = shift;
my $ipmicons = { node => [ { name => [ $argr->[0] ] } ] };
$ipmicons->{node}->[0]->{bmcaddr}->[0] = $argr->[1];
$ipmicons->{node}->[0]->{bmcuser}->[0] = $argr->[2];
$ipmicons->{node}->[0]->{bmcpass}->[0] = $argr->[3];
my $ipmisess = xCAT::IPMI->new(bmc => $argr->[1], userid => $argr->[2], password => $argr->[3]);
if ($ipmisess->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $ipmisess->{error} ], $cb, $argr->[0], %allerrornodes);
return;
}
$ipmisess->{ipmicons} = $ipmicons;
$ipmisess->{cb} = $cb;
$ipmisess->subcmd(netfn => 0x6, command => 0x38, data => [ 0x0e, 0x04 ], callback => \&got_channel_auth_cap_foripmicons, callback_args => $ipmisess);
}
sub got_channel_auth_cap_foripmicons {
my $rsp = shift;
my $ipmis = shift;
if ($rsp->{error}) {
return;
}
if ($rsp->{code} != 0) { return; }
my $cb = $ipmis->{cb};
$cb->($ipmis->{ipmicons}); #ipmicons);
}
# scan subroutine is used to scan the hardware devices which installed on the host node
# In current implementation, only the mic cards will be scanned.
# scan
# scan -u/-w/-z
my @rscan_header = (
[ "type", "%-8s" ],
[ "name", "" ],
[ "id", "%-8s" ],
[ "host", "" ]);
sub scan {
my $request = shift;
my $subreq = shift;
my $nodes = shift;
my $args = shift;
my $usage_string = "rscan [-u][-w][-z]";
my ($update, $write, $stanza);
foreach (@$args) {
if (/-w/) {
$write = 1;
} elsif (/-u/) {
$update = 1;
} elsif (/-z/) {
$stanza = 1;
} else {
$callback->({ error => [$usage_string] });
return;
}
}
my $output = xCAT::Utils->runxcmd({ command => ['xdsh'],
node => $nodes,
arg => [ 'micinfo', '-listDevices' ] }, $subreq, 0, 1);
# parse the output from 'xdsh micinfo -listDevices'
my %host2mic;
my $maxhostname = 0;
foreach (@$output) {
foreach (split /\n/, $_) {
if (/([^:]*):\s+(\d+)\s*\|/) {
my $host = $1;
my $deviceid = $2;
push @{ $host2mic{$host} }, $deviceid;
if (length($host) > $maxhostname) {
$maxhostname = length($host);
}
}
}
}
# generate the display message
my @displaymsg;
my $format = sprintf "%%-%ds", ($maxhostname + 10);
$rscan_header[1][1] = $format;
$format = sprintf "%%-%ds", ($maxhostname + 2);
$rscan_header[3][1] = $format;
if ($stanza) {
# generate the stanza for each mic
foreach (keys %host2mic) {
my $host = $_;
foreach (@{ $host2mic{$host} }) {
my $micid = $_;
push @displaymsg, "$host-mic$micid:";
push @displaymsg, "\tobjtype=node";
push @displaymsg, "\tmichost=$host";
push @displaymsg, "\tmicid=$micid";
push @displaymsg, "\thwtype=mic";
push @displaymsg, "\tmgt=mic";
}
}
} else {
# generate the headers for scan message
my $header;
foreach (@rscan_header) {
$header .= sprintf @$_[1], @$_[0];
}
push @displaymsg, $header;
# generate every entries
foreach (keys %host2mic) {
my $host = $_;
foreach (@{ $host2mic{$host} }) {
my $micid = $_;
my @data = ("mic", "$host-mic$micid", "$micid", "$host");
my $i = 0;
my $entry;
foreach (@rscan_header) {
$entry .= sprintf @$_[1], $data[ $i++ ];
}
push @displaymsg, $entry;
}
}
}
$callback->({ data => \@displaymsg });
unless ($update || $write) {
return;
}
# for -u / -w, write or update the mic node in the xCAT DB
my $nltab = xCAT::Table->new('nodelist');
my $mictab = xCAT::Table->new('mic');
my $nhmtab = xCAT::Table->new('nodehm');
if (!$nltab || !$mictab || !$nhmtab) {
$callback->({ error => ["Open database table failed."], errorcode => 1 });
return;
}
# update the node to the database
foreach (keys %host2mic) {
my $host = $_;
foreach (@{ $host2mic{$host} }) {
my $micid = $_;
my $micname = "$host-mic$micid";
# update the nodelist table
$nltab->setAttribs({ node => $micname }, { groups => "all,mic" });
# update the mic table
$mictab->setAttribs({ node => $micname }, { host => $host, id => $micid, nodetype => 'mic' });
# update the nodehm table
$nhmtab->setAttribs({ node => $micname }, { mgt => 'mic', cons => 'mic' });
}
}
}
#----------------------------------------------------------------#
# HPM file format:
# Upgrade image header:
# 0:8 --> Upgrade Image signature
# 8:1 --> Format Version
# 9:1 --> Device ID
# 10:3 --> Manufacture ID
# 13:2 --> Product ID
# 15:4 --> Img created Time
# 19:1 --> Img capabilites
# 20:1 --> Components.
# 21:1 --> Self-test timeout.
# 22:1 --> Rollback timeout
# 23:1 --> Inaccessibility timeout
# 24:2 --> Earliest Compatible Revision. 0-6 Major, 8-15 Minor
# 26:6 --> Firmware revision
# 32:2 --> OEM data descriptor length.
# 34:n --> OEM data descriptor
# 0:1 --> Descriptor Type ID.
# 1:1 --> End of List or Version
# 2:2 --> Length (8+m)
# 4:3 --> Manufacutre ID
# 7:1 --> Descriptor checksum
# 8:m --> Data.
# 34+n:1 --> Header checksum
# Upgrade action 1
# 0:1 --> Upgrade action type. 0: Backup component, 0x01: Prepare components, 0x02: Upload firmware image.
# 1:1 --> Components. The selected component
# 2:1 --> header checksum
# The following bytes 3:(34+m) are only present if action type is 0x02.
# 3:6 --> Firmware version.
# 9:21 --> Firmware description string.
# 30:4 --> Firmware length.
# 34:m --> Firmware Image data.
# Upgrade action N
# Image checksum 0:16
#
#----------------------------------------------------------------#
sub hpm_data_parse {
sub _read_hpm_file {
my ($hpm_filefd, $size, $pos_ptr, $hpm_context_string_ptr) = @_;
my $tmp = undef;
my $rt = 0;
while ($rt = read($hpm_filefd, $tmp, $size)) {
$size -= $rt;
$$pos_ptr += $rt;
$$hpm_context_string_ptr .= $tmp;
if ($size == 0) {
last;
}
}
if ($size != 0) {
$callback->({ error => "Parse hpm file error.",
errorcode => 1 });
return -1;
}
return 0;
}
my $hpm_file_name = shift;
my $hpm_context_string = "";
my $hpm_filefd;
#relative path
if ($hpm_file_name !~ /^\//) {
$hpm_file_name = xCAT::Utils->full_path($hpm_file_name, $::cwd);
}
unless (-f $hpm_file_name) {
$callback->({ error => "File $hpm_file_name can not be found.",
errorcode => 1 });
return -1;
}
unless (open($hpm_filefd, "<", $hpm_file_name)) {
$callback->({ error => "Open file $hpm_file_name failed.",
errorcode => 1 });
return -1;
}
binmode($hpm_filefd);
my $size = 34;
my $pos = 0;
my $ret = 0;
seek($hpm_filefd, 0, 2);
my $filesize = tell($hpm_filefd);
seek($hpm_filefd, 0, 0);
$ret = _read_hpm_file($hpm_filefd, $size, \$pos, \$hpm_context_string);
return $ret if $ret < 0;
# Parse hpm image header
my @hpm_context = unpack("C34", $hpm_context_string);
$hpm_data_hash{deviceID} = $hpm_context[9];
$hpm_data_hash{manufactureID} = $hpm_context[10] + $hpm_context[11] * 0x100 +
$hpm_context[12] * 0x10000;
$hpm_data_hash{productID} = $hpm_context[13] + $hpm_context[14] * 0x100;
$hpm_data_hash{componentes} = $hpm_context[20];
$hpm_data_hash{compatible_rev_min} = $hpm_context[24];
$hpm_data_hash{compatible_rev_maj} = $hpm_context[25];
$hpm_data_hash{oem_des_len} = $hpm_context[32] + $hpm_context[33] * 0x100;
seek($hpm_filefd, 34 + $hpm_data_hash{oem_des_len} + 1, 0);
# parse hpm action structure
$pos = 34 + $hpm_data_hash{oem_des_len} + 1;
while ($pos < $filesize - 16) {
$size = 3;
$hpm_context_string = "";
$ret = _read_hpm_file($hpm_filefd, $size, \$pos, \$hpm_context_string);
return $ret if $ret < 0;
@hpm_context = unpack("C3", $hpm_context_string);
my $action_type = $hpm_context[0];
my $component_id = $hpm_context[1];
$hpm_data_hash{$component_id}{component_id} = $component_id;
$hpm_data_hash{$component_id}{action_type} = $action_type;
if ($action_type eq '2') {
$hpm_context_string = "";
$size = 31;
$ret = _read_hpm_file($hpm_filefd, $size, \$pos, \$hpm_context_string);
return $ret if $ret < 0;
@hpm_context = unpack("C31", $hpm_context_string);
@{ $hpm_data_hash{$component_id}{action_version} } = splice(@hpm_context, 0, 6);
@{ $hpm_data_hash{$component_id}{action_des_str} } = splice(@hpm_context, 0, 21);
$hpm_data_hash{$component_id}{action_length} = $hpm_context[0] + $hpm_context[1] * 0x100 +
$hpm_context[2] * 0x10000 +
$hpm_context[3] * 0x1000000;
seek($hpm_filefd, $hpm_data_hash{$component_id}{action_length}, 1);
$pos += $hpm_data_hash{$component_id}{action_length};
}
}
close($hpm_filefd);
# We suppose component 2 and component 4 must exists in the HPM file
if (!exists($hpm_data_hash{2}) || !exists($hpm_data_hash{4})) {
$callback->({ error => "Parse hpm file error, component 2 and component 4 do not exist",
errorcode => 1 });
return -1;
}
if (!exists($hpm_data_hash{deviceID}) || !exists($hpm_data_hash{manufactureID})
|| !exists($hpm_data_hash{productID})) {
$callback->({ error => "Parse hpm file error",
errorcode => 1 });
return -1;
}
# The last 16 bytes are image checksum
@hpm_context = ();
return 0;
}
sub hpm_action_version {
if (!exists($hpm_data_hash{2}) || !exists($hpm_data_hash{4})) {
return -1;
}
my $version = $hpm_data_hash{1}{action_version};
my $ver = sprintf("%3d.%02x %02X%02X%02X%02X", $version->[0], $version->[1], $version->[2],
$version->[3], $version->[4], $version->[5]);
$callback->({ data => "HPM firmware version for BOOT component:$ver" });
$version = $hpm_data_hash{2}{action_version};
$ver = sprintf("%3d.%02x %02X%02X%02X%02X", $version->[0], $version->[1], $version->[2],
$version->[3], $version->[4], $version->[5]);
$callback->({ data => "HPM firmware version for APP component:$ver" });
$version = $hpm_data_hash{4}{action_version};
$ver = sprintf("%3d.%02x %02X%02X%02X%02X", $version->[0], $version->[1], $version->[2],
$version->[3], $version->[4], $version->[5]);
$callback->({ data => "HPM firmware version for BIOS component:$ver" });
}
sub process_request {
my $request = shift;
$callback = shift;
my $subreq = shift;
my $noderange = $request->{node}; #Should be arrayref
my $command = $request->{command}->[0];
my $extrargs = $request->{arg};
my @exargs = ($request->{arg});
$::cwd = $request->{cwd}->[0];
if (ref($extrargs)) {
@exargs = @$extrargs;
}
my $ipmiuser = 'USERID';
my $ipmipass = 'PASSW0RD';
my $ipmitrys = 3;
my $ipmitimeout = 2;
my $ipmitab = xCAT::Table->new('ipmi');
my $tmp;
if ($::XCATSITEVALS{ipmitimeout}) { $ipmitimeout = $::XCATSITEVALS{ipmitimeout} }
if ($::XCATSITEVALS{ipmiretries}) { $ipmitrys = $::XCATSITEVALS{ipmitretries} }
if ($::XCATSITEVALS{ipmisdrcache}) { $enable_cache = $::XCATSITEVALS{ipmisdrcache} }
if ($::XCATSITEVALS{xcatdebugmode}) { $xcatdebugmode = $::XCATSITEVALS{xcatdebugmode} }
#my @threads;
my @donargs = ();
if ($request->{command}->[0] =~ /fru/) {
my $vpdtab = xCAT::Table->new('vpd');
$vpdhash = $vpdtab->getNodesAttribs($noderange, [qw(serial mtm asset)]);
}
my $ipmihash = $ipmitab->getNodesAttribs($noderange, [ 'bmc', 'username', 'password' ]);
my $authdata = xCAT::PasswordUtils::getIPMIAuth(noderange => $noderange, ipmihash => $ipmihash);
foreach (@$noderange) {
my $node = $_;
my $nodeuser = $authdata->{$node}->{username};
my $nodepass = $authdata->{$node}->{password};
my $nodeip = $node;
my $ent;
if (defined($ipmitab)) {
$ent = $ipmihash->{$node}->[0];
if (ref($ent) and defined $ent->{bmc}) { $nodeip = $ent->{bmc}; }
}
if ($nodeip =~ /,/ and grep ({ $_ eq $request->{command}->[0] } qw/rinv reventlog rvitals rspconfig/)) { #multi-node x3950 X5, for example
my $bmcnum = 1;
foreach (split /,/, $nodeip) {
push @donargs, [ $node, $_, $nodeuser, $nodepass, $bmcnum ];
$bmcnum += 1;
}
} else {
$nodeip =~ s/,.*//; #stri
push @donargs, [ $node, $nodeip, $nodeuser, $nodepass, 1 ];
}
}
if ($request->{command}->[0] eq "getipmicons") {
foreach (@donargs) {
getipmicons($_, $callback);
}
while (xCAT::IPMI->waitforrsp()) { yield }
return;
}
if ($request->{command}->[0] eq "rspconfig") {
my $updatepasswd = 0;
my $index = 0;
foreach (@{ $request->{arg} }) {
if ($_ =~ /^USERID=\*$/) {
$updatepasswd = 1;
last;
}
$index++;
}
if ($updatepasswd) {
splice(@{ $request->{arg} }, $index, 1);
@exargs = @{ $request->{arg} };
foreach (@donargs) {
my $cliuser = $authdata->{ $_->[0] }->{cliusername};
my $clipass = $authdata->{ $_->[0] }->{clipassword};
xCAT::IMMUtils::setupIMM($_->[0], curraddr => $_->[1], skipbmcidcheck => 1, skipnetconfig => 1, cliusername => $cliuser, clipassword => $clipass, callback => $callback);
}
if ($#exargs == -1) {
return;
}
}
}
# handle the rscan to scan the mic on the target host
if ($request->{command}->[0] eq "rscan") {
scan($request, $subreq, $noderange, $extrargs);
return;
}
if ($request->{command}->[0] eq "rflash") {
my %args_hash;
if (!defined($extrargs)) {
$callback->({ error => "No option or hpm file is provided.",
errorcode => 1 });
return;
}
foreach my $opt (@$extrargs) {
if ($opt =~ /^(-c|--check)$/i) {
if (exists($args_hash{check})) {
$callback->({ error => "Error command: Multiple opption $opt is given.",
errorcode => 1 });
return;
}
$args_hash{check} = 1;
} elsif ($opt =~ /.*\.hpm$/i) {
if (exists($args_hash{hpm})) {
$callback->({ error => "Error command: Multiple hpm file is given.",
errorcode => 1 });
return;
}
$args_hash{hpm} = $opt;
}
}
if (exists($args_hash{hpm})) {
if (hpm_data_parse($args_hash{hpm}) < 0) {
return;
}
}
if (exists($args_hash{check})) {
hpm_action_version();
}
}
#get new node status
my %oldnodestatus = (); #saves the old node status
my $check = 0;
my $global_check = 1;
if (defined $::XCATSITEVALS{nodestatus} and $::XCATSITEVALS{nodestatus} =~ /0|n|N/) { $global_check = 0; }
if ($command eq 'rpower') {
%newnodestatus = ();
}
# NOTE (chenglch) rflash for one node need about 5-10 minutes. There is no need to rflash node
# one by one, fork a process for each node.
if ($command eq 'rflash') {
start_rflash_processes(\@donargs, $ipmitimeout, $ipmitrys, $command, -args => \@exargs);
}
else {
foreach (@donargs) {
donode($_->[0], $_->[1], $_->[2], $_->[3], $_->[4], $ipmitimeout, $ipmitrys, $command, -args => \@exargs);
}
while (xCAT::IPMI->waitforrsp()) { yield }
}
my $node;
foreach $node (keys %sessiondata) {
if ($sessiondata{$node}->{ipmisession}) {
$sessiondata{$node}->{ipmisession}->logout();
}
}
while (xCAT::IPMI->waitforrsp()) { yield }
if ($command eq 'rpower' and %newnodestatus) {
xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%newnodestatus, 1);
}
}
#sub updateNodeStatus {
# my $handlednodes=shift;
# my $allerrornodes=shift;
# foreach my $node (keys(%$handlednodes)) {
# if ($handlednodes->{$node} == -1) { push(@$allerrornodes, $node); }
# }
#}
#sub forward_data { #unserialize data from pipe, chunk at a time, use magic to determine end of data structure
# my $callback = shift;
# my $fds = shift;
# my $errornodes=shift;
# my @ready_fds = $fds->can_read(1);
# my $rfh;
# my $rc = @ready_fds;
# foreach $rfh (@ready_fds) {
# my $data;
# if ($data = <$rfh>) {
# while ($data !~ /ENDOFFREEZE6sK4ci/) {
# $data .= <$rfh>;
# }
# eval { print $rfh "ACK\n"; }; # Ignore ack loss to child that has given up and exited
# my $responses=thaw($data);
# foreach (@$responses) {
# #save the nodes that has errors and the ones that has no-op for use by the node status monitoring
# my $no_op=0;
# if (exists($_->{node}->[0]->{errorcode})) { $no_op=1; }
# else {
# my $text=$_->{node}->[0]->{data}->[0]->{contents}->[0];
# #print "data:$text\n";
# if (($text) && ($text =~ /$status_noop/)) {
# $no_op=1;
# #remove the symbols that meant for use by node status
# $_->{node}->[0]->{data}->[0]->{contents}->[0] =~ s/ $status_noop//;
# }
# }
# #print "data:". $_->{node}->[0]->{data}->[0]->{contents}->[0] . "\n";
# if ($no_op) {
# if ($errornodes) { $errornodes->{$_->{node}->[0]->{name}->[0]}=-1; }
# } else {
# if ($errornodes) { $errornodes->{$_->{node}->[0]->{name}->[0]}=1; }
# }
# $callback->($_);
# }
# } else {
# $fds->remove($rfh);
# close($rfh);
# }
# }
# yield; #Avoid useless loop iterations by giving children a chance to fill pipes
# return $rc;
#}
sub donode {
my $node = shift;
my $bmcip = shift;
my $user = shift;
my $pass = shift;
my $bmcnum = shift;
my $timeout = shift;
my $retries = shift;
my $command = shift;
my %namedargs = @_;
my $extra = $namedargs{-args};
my @exargs = @$extra;
$sessiondata{$node} = {
node => $node, #this seems redundant, but some code will not be privy to what the key was
bmcnum => $bmcnum,
#ipmisession => xCAT::IPMI->new(bmc => $bmcip, userid => $user, password => $pass),
command => $command,
extraargs => \@exargs,
subcommand => $exargs[0],
xcatdebugmode => $xcatdebugmode,
outfunc => $callback,
};
if ($command eq "rpower" and $exargs[0] eq "reseat") {
on_bmc_connect(0, $sessiondata{$node});
return 0;
}
$sessiondata{$node}->{ipmisession} = xCAT::IPMI->new(bmc => $bmcip, userid => $user, password => $pass);
if ($sessiondata{$node}->{ipmisession}->{error}) {
xCAT::SvrUtils::sendmsg([ 1, $sessiondata{$node}->{ipmisession}->{error} ], $callback, $node, %allerrornodes);
} else {
my ($rc, @output) = ipmicmd($sessiondata{$node});
sendoutput($rc, @output);
yield;
return $rc;
}
#my $msgtoparent=freeze(\@outhashes);
# print $outfd $msgtoparent;
}
sub sendoutput {
my $rc = shift;
foreach (@_) {
my %output;
(my $desc, my $text) = split(/:/, $_, 2);
unless ($text) {
$text = $desc;
} else {
$desc =~ s/^\s+//;
$desc =~ s/\s+$//;
if ($desc) {
$output{node}->[0]->{data}->[0]->{desc}->[0] = $desc;
}
}
$text =~ s/^\s+//;
$text =~ s/\s+$//;
$output{node}->[0]->{name}->[0] = "BADCODE";
if ($rc) {
$output{node}->[0]->{errorcode} = [$rc];
$output{node}->[0]->{error}->[0] = $text;
} else {
$output{node}->[0]->{data}->[0]->{contents}->[0] = $text;
}
$callback->(\%output);
#push @outhashes,\%output; #Save everything for the end, don't know how to be slicker with Storable and a pipe
# print $outfd freeze([\%output]);
# print $outfd "\nENDOFFREEZE6sK4ci\n";
# yield;
# waitforack($outfd);
}
}
##########################################################################
# generate hardware tree, called from lstree.
##########################################################################
sub genhwtree
{
my $nodelist = shift; # array ref
my $callback = shift;
my %hwtree;
my $bmchash;
# read ipmi.bmc
my $ipmitab = xCAT::Table->new('ipmi');
if ($ipmitab)
{
$bmchash = $ipmitab->getNodesAttribs($nodelist, ['bmc']);
}
else
{
my $rsp = {};
$rsp->{data}->[0] = "Can not open ipmi table.\n";
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
}
foreach my $node (@$nodelist)
{
if ($bmchash->{$node}->[0]->{'bmc'})
{
push @{ $hwtree{ $bmchash->{$node}->[0]->{'bmc'} } }, $node;
}
}
return \%hwtree;
}
1;