#!/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>title=$currnode wvid!; 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>title=$currnode wvid!; 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 # -I lan -U -P # # 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 --recover '" ], $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 [[all] [-s] | [-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 -U -P raw 0x2c 0x10 0xdc 0x01 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 -U -P 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;