# 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 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 POSIX qw(ceil floor); use Storable qw(store_fd retrieve_fd thaw freeze); use xCAT::Utils; use xCAT::SvrUtils; use xCAT::Usage; use Thread qw(yield); use LWP 5.64; use HTTP::Request::Common; my $tfactor = 0; my $vpdhash; my %bmc_comm_pids; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw( ipmiinit ipmicmd ); sub handled_commands { return { rpower => 'nodehm:power,mgt', getipmicons => 'ipmi', rspconfig => 'nodehm:mgt', rvitals => 'nodehm:mgt', rinv => 'nodehm:mgt', rsetboot => 'nodehm:mgt', rbeacon => 'nodehm:mgt', reventlog => 'nodehm:mgt', rfrurewrite => 'nodehm:mgt', getrvidparms => 'nodehm:mgt' } } #use Data::Dumper; 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 @rmcp = (0x06,0x00,0xff,0x07); my $auth; my $rssa = 0x20; my $rqsa = 0x81; my $seqlun = 0x00; my @session_id = (0,0,0,0); my @challenge = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); my @seqnum = (0,0,0,0); my $outfd; #File descriptor for children to send messages to parent my $currnode; #string to describe current node, presumably nodename my $globrc=0; my $userid; my $passwd; my $ipmi_bmcipaddr; my $timeout; my $port; my $debug; my $ndebug = 0; my @cmdargv; my $sock; my @user; my @pass; my $channel_number; my %sdr_hash; my %fru_hash; 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 = 2; my $frudex; #iterator for initfru to use my $status_noop="XXXno-opXXX"; 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", ); 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", ); 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_rep_info => { version => '$', rec_count => '$', resv_sdr => '$', }; 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 => '$', }; 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]); 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 ipmiinit { my $ipmimaxp = 80; my $ipmitimeout = 3; my $ipmitrys = 3; my $ipmiuser = 'USERID'; my $ipmipass = 'PASSW0RD'; my $tmp; my $table = xCAT::Table->new('site'); if ($table) { ($tmp)=$table->getAttribs({'key'=>'ipmimaxp'},'value'); if (defined($tmp)) { $ipmimaxp=$tmp->{value}; } ($tmp)=$table->getAttribs({'key'=>'ipmitimeout'},'value'); if (defined($tmp)) { $ipmitimeout=$tmp->{value}; } ($tmp)=$table->getAttribs({'key'=>'ipmiretries'},'value'); if (defined($tmp)) { $ipmitrys=$tmp->{value}; } ($tmp)=$table->getAttribs({'key'=>'ipmisdrcache'},'value'); } $table = xCAT::Table->new('passwd'); if ($table) { ($tmp)=$table->getAttribs({'key'=>'ipmi'},'username','password'); if (defined($tmp)) { $ipmiuser = $tmp->{username}; $ipmipass = $tmp->{password}; } } return($ipmiuser,$ipmipass,$ipmimaxp,$ipmitimeout,$ipmitrys); } sub ipmicmd { my $node = shift; $port = shift; $userid = shift; $passwd = shift; $timeout = shift; $localtrys = shift; $debug = shift; $localdebug = $debug; if($userid eq "(null)") { $userid = ""; } if($passwd eq "(null)") { $passwd = ""; } @user = dopad16($userid); @pass = dopad16($passwd); $seqlun = 0x00; @session_id = (0,0,0,0); @challenge = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); @seqnum = (0,0,0,0); $authoffset=0; my $command = shift; @cmdargv = @_; my $subcommand = shift; my $rc=0; my $text=""; my $error=""; my @output; my $noclose=0; my $packed_ip = gethostbyname($node); if(!defined($packed_ip)) { $text = "failed to get IP for $node"; return(2,$text); } $ipmi_bmcipaddr=inet_ntoa($packed_ip); $sock = IO::Socket::INET->new( Proto => 'udp', PeerHost => $ipmi_bmcipaddr, PeerPort => $port, ); if(!defined($sock)) { $text = "failed to get socket: $@\n"; return(2,$text); } $error = getchanauthcap(); if($error) { return(1,$error); } if($debug) { print "$node: gotchanauthcap\n"; } if($command eq "ping") { return(0,"ping"); } $error = getsessionchallenge(); if($error) { return(1,$error); } if($debug) { print "$node: gotsessionchallenge\n"; } $error = activatesession(); if($error) { return(1,$error); } if($debug) { print "$node: active session\n"; } $error = setprivlevel(); if($error) { return(1,$error); } if($debug) { print "$node: priv level set\n"; } if($command eq "rpower") { if($subcommand eq "stat" || $subcommand eq "state" || $subcommand eq "status") { ($rc,$text) = power("stat"); } elsif($subcommand eq "on") { my ($oldrc,$oldtext) = power("stat"); ($rc,$text) = power("on"); if(($rc == 0) && ($text eq "on") && ($oldtext eq "on")) { $text .= " $status_noop"; } } elsif($subcommand eq "nmi") { ($rc,$text) = power("nmi"); } elsif($subcommand eq "off" or $subcommand eq "softoff") { my ($oldrc,$oldtext) = power("stat"); ($rc,$text) = power($subcommand); if(($rc == 0) && ($text eq "off") && ($oldtext eq "off")) { $text .= " $status_noop"; } # if($text0 ne "") { # $text = $text0 . " " . $text; # } } elsif($subcommand eq "reset") { my ($oldrc,$oldtext) = power("stat"); ($rc,$text) = power("reset"); $noclose = 0; if(($rc == 0) && ($text eq "off") && ($oldtext eq "off")) { $text .= " $status_noop"; } } elsif($subcommand eq "cycle") { my $text2; ($rc,$text) = power("stat"); if($rc == 0 && $text eq "on") { ($rc,$text) = power("off"); if($rc == 0) { sleep(5); } } if($rc == 0 && $text eq "off") { ($rc,$text2) = power("on"); } if($rc == 0) { $text = $text . " " . $text2 } } elsif($subcommand eq "boot") { my $text2; ($rc,$text) = power("stat"); if($rc == 0) { if($text eq "on") { ($rc,$text2) = power("reset"); $noclose = 0; } elsif($text eq "off") { ($rc,$text2) = power("on"); } else { $rc = 1; } $text = $text . " " . $text2 } } else { $rc = 1; $text = "unsupported command $command $subcommand"; } } elsif($command eq "rbeacon") { ($rc,$text) = beacon($subcommand); } elsif($command eq "getrvidparms") { ($rc,@output) = getrvidparms($subcommand); } # elsif($command eq "info") { # if($subcommand eq "sensorname") { # ($rc,$text) = initsdr(); # if($rc == 0) { # my $key; # $text=""; # foreach $key (keys %sdr_hash) { # my $sdr = $sdr_hash{$key}; # if($sdr->sensor_number == @_) { # $text = $sdr_hash{$key}->id_string; # last; # } # } ## if(defined $sdr_hash{@_}) { ## $text = $sdr_hash{@_}->id_string; ## } # } # } # } elsif($command eq "rvitals") { ($rc,@output) = vitals($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 "sol.command") { my $dc=0; $@ = ""; eval { my $cc=0; my $kid; my $pid=$$; $SIG{USR1} = sub {$cc=0;}; $SIG{USR2} = sub {$dc++;}; $SIG{CHLD} = sub {while(waitpid(-1,WNOHANG) > 0) { sleep(1); }}; mkfifo("/tmp/.sol.$pid",0666); my $child = xCAT::Utils->xfork(); if(!defined $child) { die; } if($child > 0) { $cc=1; } else { system("$subcommand /tmp/.sol.$pid"); if($?/256 == 1) { kill(12,$pid); } if($?/256 == 2) { kill(12,$pid); sleep(1); kill(12,$pid); } kill(10,$pid); exit(0); } open(FH,"< /tmp/.sol.$pid"); my $kpid = <FH>; close(FH); unlink("/tmp/.sol.$pid"); while($cc == 1) { sleep(5); ($rc,$text) = power("stat"); $text=""; if($rc != 0) { kill(15,$kpid); $cc=0; } } do { $kid = waitpid(-1,WNOHANG); sleep(1); } until($kid == -1); }; if($@) { @output = $@; } $rc = $dc; if($rc == 1) { $noclose = 1; } } 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 "rspconfig") { foreach ($subcommand,@_) { my @coutput; ($rc,@coutput) = setnetinfo($_); if($rc == 0) { ($rc,@coutput) = getnetinfo($_); } push(@output,@coutput); } } elsif($command eq "sete325cli") { ($rc,@output) = sete325cli($subcommand); } elsif($command eq "sete326cli") { ($rc,@output) = sete325cli($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($noclose == 0) { $error = closesession(); #In most cases where closesession fails to receive a response, #the BMC is ignoring us because it managed to get the first one #and the network dropped it on the way back #if($error) { # return(1,"$text, session close: $error"); #} if($debug) { print "$node: session closed.\n"; } } if($text) { push(@output,$text); } $sock->close(); return($rc,@output); } sub resetbmc { my $netfun = 0x18; my @cmd = (0x02); my @returnd = (); my $rc = 0; my $text; my $error; $error = docmd( $netfun, \@cmd, \@returnd ); if ($error) { $rc = 1; $text = $error; } else { if (0 == $returnd[36]) { $text = "BMC reset"; } else { if ($codes{$returnd[36]}) { $text = $codes{$returnd[36]}; } else { $text = sprintf("BMC Responded with code %d",$returnd[36]); } } } return($rc,$text); } sub setnetinfo { my $subcommand = shift; my $argument; ($subcommand,$argument) = split(/=/,$subcommand); my @input = @_; my $netfun = 0x30; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; my $match; 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 = 0x10; @cmd = (0x12,0x9,0x1,0x18,0x11,0x00); } elsif ($subcommand eq "alert" and $argument eq "off" or $argument =~ /^dis/ or $argument =~ /^disable/) { $netfun = 0x10; @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); 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 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"); } $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; } else { if($subcommand eq "garp" or $subcommand =~ m/snmpdest\d+/ or $subcommand eq "alert" or $subcommand =~ /community/) { $code = $returnd[36]; if($code == 0x00) { $text = "ok"; } } if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } } return($rc,$text); } sub getnetinfo { my $subcommand = shift; $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,36-$authoffset; @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 $netfun = 0x30; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; my $format = "%-25s"; if ($subcommand eq "snmpdest") { $subcommand = "snmpdest1"; } if ($subcommand eq "alert") { $netfun = 0x10; @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 "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"); } $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; } else { # response format: # 4 bytes (RMCP header) # 1 byte (auth type) # 4 bytes (session sequence) # 4 bytes (session id) # 16 bytes (message auth code, not present if auth type is 0, $authoffset=16) # 1 byte (ipmi message length) # 1 byte (requester's address # 1 byte (netfun, req lun) # 1 byte (checksum) # 1 byte (Responder's slave address) # 1 byte (Sequence number, generated by the requester) # 1 byte (command) # 1 byte (return code) # 1 byte (param revision) # N bytes (data) # 1 byte (checksum) if($subcommand eq "garp") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $code = $returnd[38-$authoffset] / 2; $text = sprintf("$format %d","Gratuitous ARP seconds:",$code); } else { $rc = 1; $text = $codes{$code}; } } elsif($subcommand eq "alert") { if ($returnd[39-$authoffset] & 0x8) { $text = "SP Alerting: enabled"; } else { $text = "SP Alerting: disabled"; } } elsif($subcommand =~ m/^snmpdest(\d+)/ ) { $text = sprintf("$format %d.%d.%d.%d", "SP SNMP Destination $1:", $returnd[41-$authoffset], $returnd[42-$authoffset], $returnd[43-$authoffset], $returnd[44-$authoffset]); } elsif($subcommand eq "ip") { $text = sprintf("$format %d.%d.%d.%d", "BMC IP:", $returnd[38-$authoffset], $returnd[39-$authoffset], $returnd[40-$authoffset], $returnd[41-$authoffset]); } elsif($subcommand eq "netmask") { $text = sprintf("$format %d.%d.%d.%d", "BMC Netmask:", $returnd[38-$authoffset], $returnd[39-$authoffset], $returnd[40-$authoffset], $returnd[41-$authoffset]); } elsif($subcommand eq "gateway") { $text = sprintf("$format %d.%d.%d.%d", "BMC Gateway:", $returnd[38-$authoffset], $returnd[39-$authoffset], $returnd[40-$authoffset], $returnd[41-$authoffset]); } elsif($subcommand eq "backupgateway") { $text = sprintf("$format %d.%d.%d.%d", "BMC Backup Gateway:", $returnd[38-$authoffset], $returnd[39-$authoffset], $returnd[40-$authoffset], $returnd[41-$authoffset]); } elsif ($subcommand eq "community") { $text = sprintf("$format ","SP SNMP Community:"); my $l = 38-$authoffset; while ($returnd[$l] ne 0) { $l = $l + 1; } my $i=38-$authoffset; while ($i<$l) { $text = $text . sprintf("%c",$returnd[$i]); $i = $i + 1; } } if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } } return($rc,$text); } sub sete325cli { my $subcommand = shift; my $netfun = 0xc8; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; if($subcommand eq "disable") { @cmd = (0x00); } elsif($subcommand eq "cli") { @cmd = (0x02); } else { return(1,"unsupported command sete325cli $subcommand"); } $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; } else { if($code == 0x00) { $rc = 0; $text = "$subcommand"; } else { $rc = 1; $text = $codes{$code}; } if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } } return($rc,$text); } sub setboot { my $subcommand=shift; my $netfun = 0x00; my @cmd = (0x08,0x3,0x8); my @returnd = (); my $error; my $rc = 0; my $text = ""; my $code; my $skipset = 0; my %bootchoices = ( 0 => 'BIOS default', 1 => 'Network', 2 => 'Hard Drive', 5 => 'CD/DVD', 6 => 'BIOS Setup', 15 => 'Floppy' ); #This disables the 60 second timer $error = docmd( $netfun, \@cmd, \@returnd ); if ($subcommand eq "net") { @cmd=(0x08,0x5,0x80,0x4,0x0,0x0,0x0); } elsif ($subcommand eq "hd" ) { @cmd=(0x08,0x5,0x80,0x8,0x0,0x0,0x0); } elsif ($subcommand eq "cd" ) { @cmd=(0x08,0x5,0x80,0x14,0x0,0x0,0x0); } elsif ($subcommand eq "floppy" ) { @cmd=(0x08,0x5,0x80,0x3c,0x0,0x0,0x0); } elsif ($subcommand =~ m/^def/) { @cmd=(0x08,0x5,0x0,0x0,0x0,0x0,0x0); } elsif ($subcommand eq "setup" ) { #Not supported by BMCs I've checked so far.. @cmd=(0x08,0x5,0x18,0x0,0x0,0x0,0x0); } elsif ($subcommand =~ m/^stat/) { $skipset=1; } else { return(1,"unsupported command setboot $subcommand"); } unless ($skipset) { $error = docmd( $netfun, \@cmd, \@cmd, \@returnd ); if($error) { return(1,$error); } $code = $returnd[36-$authoffset]; unless ($code == 0x00) { return(1,$codes{$code}); } } @cmd=(0x09,0x5,0x0,0x0); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { return(1,$error); } $code = $returnd[36-$authoffset]; unless ($code == 0x00) { return(1,$codes{$code}); } unless ($returnd[39-$authoffset] & 0x80) { $text = "boot override inactive"; return($rc,$text); } my $boot=($returnd[40-$authoffset] & 0x3C) >> 2; $text = $bootchoices{$boot}; return($rc,$text); } 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 getrvidparms { my $netfun = 0x3a; my @mcinfo=getdevid(); unless ($mcinfo[2] == 2) { #Only implemented for IBM servers return(1,"Remote video is not supported on this system"); } #TODO: use get bmc capabilities to see if rvid is actually supported before bothering the client java app my @build_id; my $localerror = docmd( 0xe8, [0x50], \@build_id ); if ($localerror) { return(1,$localerror); } @build_id=splice @build_id,36-$authoffset; 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 return(1,"Remote video is not supported on this system"); } #wvid should be a possiblity, time to do the http... my $browser = LWP::UserAgent->new(); my $message = "$userid,$passwd"; $browser->cookie_jar({}); my $baseurl = "http://".$ipmi_bmcipaddr."/"; my $response = $browser->request(POST $baseurl."/session/create",'Content-Type'=>"text/xml",Content=>$message); unless ($response->content eq "ok") { return (1,"Server returned unexpected data"); } $response = $browser->request(GET $baseurl."/kvm/kvm/jnlp"); my $jnlp = $response->content; if ($jnlp =~ /This advanced option requires the purchase and installation/) { return (1,"Node does not have feature key for remote video"); } $jnlp =~ s!argument>title=.*Video Viewer</argument>!argument>title=$currnode wvid</argument>!; my @return=("method:imm","jnlp:$jnlp"); if (grep /-m/,@cmdargv) { $response = $browser->request(GET $baseurl."/kvm/vm/jnlp"); push @return,"mediajnlp:".$response->content; } return (0,@return); } sub power { my $subcommand = shift; my $netfun = 0x00; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; if($subcommand eq "stat") { @cmd = (0x01); } elsif($subcommand eq "on") { @cmd = (0x02,0x01); } elsif($subcommand eq "softoff") { @cmd = (0x02,0x05); } elsif($subcommand eq "off") { @cmd = (0x02,0x00); } elsif($subcommand eq "reset") { @cmd = (0x02,0x03); } elsif($subcommand eq "nmi") { @cmd = (0x02,0x04); } else { return(1,"unsupported command power $subcommand"); } $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; } else { if($subcommand eq "stat") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $code = $returnd[37-$authoffset]; if($code & 0b00000001) { $text = "on"; } else { $text = "off"; } } else { $rc = 1; $text = $codes{$code}; } } if($subcommand eq "nmi") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text="nmi"; } else { $rc = 1; $text = $codes{$code}; } } if($subcommand eq "on") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text="on"; } else { $rc = 1; $text = $codes{$code}; } } if($subcommand eq "softoff") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text="softoff"; } else { $rc = 1; $text = $codes{$code}; } } if($subcommand eq "off") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text="off"; } elsif($code == 0xd5) { $text="off"; } else { $rc = 1; $text = $codes{$code}; } } if($subcommand eq "reset") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text="reset"; } elsif($code == 0xd5) { $text="off"; } else { $rc = 1; $text = $codes{$code}; } } if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } } return($rc,$text); } 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[36-$authoffset]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } printf("return code: 0x%02x\n\n",$code); print "return data:\n"; my @rdata = @returnd[37-$authoffset..@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 $subcommand = shift; my $netfun = 0x00; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; if($subcommand eq ""){ return(1,"please specify on or off for ipmi nodes"); } if($subcommand eq "on") { if ($ipmiv2) { @cmd = (0x04,0x0,0x01); } else { @cmd = (0x04,0xFF); } } elsif($subcommand eq "off") { if ($ipmiv2) { @cmd = (0x04,0x0,0x00); } else { @cmd = (0x04,0x00); } } else { return(1,"unsupported command beacon $subcommand"); } $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; } else { if($subcommand eq "on") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text="on"; } else { $rc = 1; $text = $codes{$code}; } } if($subcommand eq "off") { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text="off"; } else { $rc = 1; $text = $codes{$code}; } } if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } } return($rc,$text); } sub inv { my $subcommand = shift; my $rc = 0; my $text; my @output; my @types; my $format = "%-20s %s"; ($rc,$text) = initsdr(); #Look for those precious locator reconds if($rc != 0) { return($rc,$text); } ($rc,$text) = initfru(); if($rc != 0) { return($rc,$text); } unless ($subcommand) { $subcommand = "all"; } if($subcommand eq "all") { @types = qw(model serial deviceid mprom guid misc hw asset); } elsif($subcommand eq "asset") { @types = qw(asset); } elsif($subcommand eq "model") { @types = qw(model); } elsif($subcommand eq "serial") { @types = qw(serial); } elsif($subcommand eq "vpd") { @types = qw(model serial deviceid mprom); } elsif($subcommand eq "mprom") { @types = qw(mprom); } elsif($subcommand eq "misc") { @types = qw(misc); } elsif($subcommand eq "deviceid") { @types = qw(deviceid); } elsif($subcommand eq "guid") { @types = qw(guid); } elsif($subcommand eq "uuid") { @types = qw(guid); } else { @types = ($subcommand); #return(1,"unsupported BMC inv argument $subcommand"); } my $otext; my $key; foreach $key (sort keys %fru_hash) { my $fru = $fru_hash{$key}; my $type; foreach $type (split /,/,$fru->rec_type) { if(grep {$_ eq $type} @types) { $otext = sprintf($format,$fru_hash{$key}->desc . ":",$fru_hash{$key}->value); #print $otext; push(@output,$otext); last; } } } return($rc,@output); } sub initoemfru { my $mfg_id = shift; my $prod_id = shift; my $device_id = shift; my $netfun; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my @output; my $code; if($mfg_id == 2 && ($prod_id == 34869 or $prod_id == 31081 or $prod_id==34888)) { $netfun = 0xc8; @cmd=(0x05); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my @oem_fru_data = @returnd[37-$authoffset..@returnd-2]; my $model_type = getascii(@oem_fru_data[0..3]); my $model_number = getascii(@oem_fru_data[4..6]); my $serial = getascii(@oem_fru_data[7..13]); my $model = "$model_type-$model_number"; my $fru = FRU->new(); $fru->rec_type("serial"); $fru->desc("Serial Number"); $fru->value($serial); $fru_hash{1} = $fru; $fru = FRU->new(); $fru->rec_type("model"); $fru->desc("Model Number"); $fru->value($model); $fru_hash{2} = $fru; return(2,""); } if($mfg_id == 2 && $prod_id == 4 && 0) { $netfun = 0x3a; @cmd=(0x0b,0x0,0x0,0x0,0x1,0x8); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } hexadump(\@returnd); return(2,""); my @oem_fru_data = @returnd[37-$authoffset..@returnd-2]; my $model_type = getascii(@oem_fru_data[0..3]); my $model_number = getascii(@oem_fru_data[4..6]); my $serial = getascii(@oem_fru_data[7..13]); my $model = "$model_type-$model_number"; my $fru = FRU->new(); $fru->rec_type("serial"); $fru->desc("Serial Number"); $fru->value($serial); $fru_hash{1} = $fru; $fru = FRU->new(); $fru->rec_type("model"); $fru->desc("Model Number"); $fru->value($model); $fru_hash{2} = $fru; return(2,""); } if($mfg_id == 2 && $prod_id == 20) { my $serial = "unknown"; my $model = "x3655"; my $fru = FRU->new(); $fru->rec_type("serial"); $fru->desc("Serial Number"); $fru->value($serial); $fru_hash{1} = $fru; $fru = FRU->new(); $fru->rec_type("model"); $fru->desc("Model Number"); $fru->value($model); $fru_hash{2} = $fru; return(2,""); } if($mfg_id == 2 && $prod_id == 3) { my $serial = "unknown"; my $model = "x346"; my $fru = FRU->new(); $fru->rec_type("serial"); $fru->desc("Serial Number"); $fru->value($serial); $fru_hash{1} = $fru; $fru = FRU->new(); $fru->rec_type("model"); $fru->desc("Model Number"); $fru->value($model); $fru_hash{2} = $fru; return(2,""); } if($mfg_id == 2 && $prod_id == 4) { my $serial = "unknown"; my $model = "x336"; my $fru = FRU->new(); $fru->rec_type("serial"); $fru->desc("Serial Number"); $fru->value($serial); $fru_hash{1} = $fru; $fru = FRU->new(); $fru->rec_type("model"); $fru->desc("Model Number"); $fru->value($model); $fru_hash{2} = $fru; return(2,""); } my $serial = "unkown"; my $model = "unkown"; my $fru = FRU->new(); $fru->rec_type("serial"); $fru->desc("Serial Number"); $fru->value($serial); $fru_hash{1} = $fru; $fru = FRU->new(); $fru->rec_type("model"); $fru->desc("Model Number"); $fru->value($model); $fru_hash{2} = $fru; return(2,""); return(1,"No OEM FRU Support"); } sub add_textual_fru { my $parsedfru = shift; my $description = shift; my $category = shift; my $subcategory = shift; my $types = shift; 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}) } foreach (@subfrus) { $fru = FRU->new(); $fru->rec_type($types); $fru->desc($description); if (not ref $_) { $fru->value($_); } else { if ($_->{encoding} == 3) { $fru->value($_->{value}); } else { $fru->value(phex($_->{value})); } } $fru_hash{$frudex++} = $fru; } } } sub add_textual_frus { my $parsedfru = shift; my $desc = shift; my $categorydesc = shift; my $category = shift; my $type = shift; unless ($type) { $type = 'hw'; } add_textual_fru($parsedfru,$desc." ".$categorydesc."Part Number",$category,"partnumber","hw"); add_textual_fru($parsedfru,$desc." ".$categorydesc."Manufacturer",$category,"manufacturer","hw"); add_textual_fru($parsedfru,$desc." ".$categorydesc."Serial Number",$category,"serialnumber","hw"); add_textual_fru($parsedfru,$desc." ".$categorydesc."",$category,"name","hw"); if ($parsedfru->{$category}->{builddate}) { add_textual_fru($parsedfru,$desc." ".$categorydesc."Manufacture Date",$category,"builddate","hw"); } if ($parsedfru->{$category}->{buildlocation}) { add_textual_fru($parsedfru,$desc." ".$categorydesc."Manufacture Location",$category,"buildlocation","hw"); } if ($parsedfru->{$category}->{model}) { add_textual_fru($parsedfru,$desc." ".$categorydesc."Model",$category,"model","hw"); } add_textual_fru($parsedfru,$desc." ".$categorydesc."Additional Info",$category,"extra","hw"); } sub initfru { my $netfun = 0x28; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my @output; my $code; my $mfg_id; my $prod_id; my $device_id; my $dev_rev; my $fw_rev1; my $fw_rev2; my $mprom; my $fru; my $guid; my @guidcmd; ($rc,$text,$mfg_id,$prod_id,$device_id,$dev_rev,$fw_rev1,$fw_rev2) = getdevid(); if($rc != 0) { return($rc,$text); } @guidcmd = (0x18,0x37); if($mfg_id == 2 && $prod_id == 34869) { @guidcmd = (0x18,0x08); } if($mfg_id == 2 && $prod_id == 4) { @guidcmd = (0x18,0x08); } if($mfg_id == 2 && $prod_id == 3) { @guidcmd = (0x18,0x08); } ($rc,$text,$guid) = getguid(\@guidcmd); if($rc != 0) { return($rc,$text); } if($mfg_id == 2 && $prod_id == 34869) { $mprom = sprintf("%x.%x",$fw_rev1,$fw_rev2); } elsif ($mfg_id == 2) { my @lcmd = (0x50); my @lreturnd = (); my $lerror = docmd( 0xe8, \@lcmd, \@lreturnd ); if ($lerror eq "" && $lreturnd[36-$authoffset] == 0) { my @a = ($fw_rev2); my @b= @lreturnd[37-$authoffset .. $#lreturnd-1]; $mprom = sprintf("%d.%s (%s)",$fw_rev1,decodebcd(\@a),getascii(@b)); } else { my @a = ($fw_rev2); $mprom = sprintf("%d.%s",$fw_rev1,decodebcd(\@a)); } } else { my @a = ($fw_rev2); $mprom = sprintf("%d.%s",$fw_rev1,decodebcd(\@a)); } $fru = FRU->new(); $fru->rec_type("mprom"); $fru->desc("BMC Firmware"); $fru->value($mprom); $fru_hash{mprom} = $fru; $fru = FRU->new(); $fru->rec_type("guid"); $fru->desc("GUID"); $fru->value($guid); $fru_hash{guid} = $fru; $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); $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); $fru_hash{prod_id} = $fru; $fru = FRU->new(); $fru->rec_type("deviceid"); $fru->desc("Device ID"); $fru->value($device_id); $fru_hash{device_id} = $fru; # ($rc,$text)=initoemfru($mfg_id,$prod_id,$device_id); # if($rc == 1) { # return($rc,$text); # } # if($rc == 2) { # return(0,""); # } $netfun = 0x28; # Storage (0x0A << 2) my @bytes; @cmd=(0x10,0x00); $error = docmd($netfun,\@cmd,\@bytes); if ($error) { return (1,$error); } @bytes=splice @bytes,36-$authoffset; pop @bytes; unless (defined $bytes[0] and $bytes[0] == 0) { if ($codes{$bytes[0]}) { return (1,"FRU device 0 inaccessible".$codes{$bytes[0]}); } else { return (1,"FRU device 0 inaccessible"); } } my $frusize=($bytes[2]<<8)+$bytes[1]; ($rc,@bytes) = frudump(0,$frusize,16); if($rc != 0) { return($rc,@bytes); } my $fruhash; ($error,$fruhash) = parsefru(\@bytes); if ($error) { ($rc,$text)=initoemfru($mfg_id,$prod_id,$device_id); if($rc == 1) { $text = "FRU format unknown"; return($rc,$text); } if($rc == 2) { return(0,""); } } $frudex=0; if (defined $fruhash->{product}->{manufacturer}->{value}) { $fru = FRU->new(); $fru->rec_type("misc"); $fru->desc("System Manufacturer"); if ($fruhash->{product}->{product}->{encoding}==3) { $fru->value($fruhash->{product}->{manufacturer}->{value}); } else { $fru->value(phex($fruhash->{product}->{manufacturer}->{value})); } $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})); } $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})); } $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})); } $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})); } $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})); } $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})); } $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})); } $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})); } $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})); } $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}); $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})); } $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})); } $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})); } $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})); } $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})); } $fru_hash{$frudex++} = $fru; } #Ok, done with fru 0, on to the other fru devices from SDR my $key; my $subrc; 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 and $sdr->fru_type == 0x10) { #skip non fru sdr stuff and frus I don't understand next; } if ($sdr->fru_type == 0x10) { #supported if ($sdr->fru_subtype == 0x1) { #DIMM $fru = FRU->new(); $fru->rec_type("hw,dimm"); $fru->desc($sdr->id_string); ($subrc,@bytes) = frudump(0,get_frusize($sdr->sensor_number),16,$sdr->sensor_number); if ($subrc) { print $sdr->id_string.":".$bytes[0]."\n"; $fru->value($bytes[0]); $fru_hash{$frudex++} = $fru; next; } my $parsedfru = decode_spd(@bytes); add_textual_frus($parsedfru,$sdr->id_string,"",'product','dimm,hw'); } elsif ($sdr->fru_subtype == 0 or $sdr->fru_subtype == 2) { ($subrc,@bytes) = frudump(0,get_frusize($sdr->sensor_number),16,$sdr->sensor_number); if ($subrc) { $fru = FRU->new(); $fru->value($bytes[0]); $fru->rec_type("hw"); $fru->desc($sdr->id_string); $fru_hash{$frudex++} = $fru; next; } my $parsedfru=parsefru(\@bytes); add_textual_frus($parsedfru,$sdr->id_string,"Board ",'board'); add_textual_frus($parsedfru,$sdr->id_string,"Product ",'product'); add_textual_frus($parsedfru,$sdr->id_string,"Chassis ",'chassis'); } } } return($rc,$text); } sub get_frusize { my $fruid=shift; my $netfun = 0x28; # Storage (0x0A << 2) my @cmd=(0x10,$fruid); my @bytes; my $error = docmd($netfun,\@cmd,\@bytes); @bytes=splice @bytes,36-$authoffset; 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 $phash = shift; #Product hash 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 $prod=shift; mergefru($prod); 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[36-$authoffset]; 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[37-$authoffset]; my $fru_size_ms = $returnd[38-$authoffset]; 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 frudump { my $offset = shift; my $length = shift; my $chunk = shift; my $fruid = shift; unless (defined $fruid) { $fruid = 0; } unless ($length) { return (1,$chunk); } #chunk happens to get the error text 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; my $reqsize = $chunk; if ($c+$chunk > $length+$offset) { $reqsize = ($length+$offset-$c); } @cmd=(0x11,$fruid,$ls,$ms,$reqsize); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my $count = $returnd[37-$authoffset]; if($count != $reqsize) { $rc = 1; $text = "FRU read error (bytes requested: $reqsize, got: $count)"; return($rc,$text); } my @data = @returnd[38-$authoffset..@returnd-2]; @fru_data = (@fru_data,@data); } return(0,@fru_data); } 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 ($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 "unknown",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; @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; @currarea=@{$bytes}[$curridx..($curridx+$currsize-1)]; $fruhash->{board} = parseboard(@currarea); } 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; @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; while (not $last) { if ($bytes->[$curridx+1] & 128) { $last=1; } $currsize=$bytes->[$curridx+2]; push @{$fruhash->{extra}},$bytes->[$curridx..$curridx+4+$currsize-1]; } } 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); } 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); } 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); } 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; 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); @bytes=splice @bytes,36-$authoffset; 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[36-$authoffset]; 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[37-$authoffset]; if($count != $chunk) { $rc = 1; $text = "FRU write error (bytes requested: $chunk, wrote: $count)"; return($rc,$text); } } return(0); } sub decodealert { my $trap = shift; my $skip_sdrinit=0; if ($trap =~ /xCAT_plugin::ipmi/) { $trap=shift; $skip_sdrinit=1; } my $node = shift; my @pet = @_; my $rc; my $text; if (!$skip_sdrinit) { ($rc,$text) = initsdr(); if($rc != 0) { return($rc,$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); foreach $key (keys %sdr_hash) { my $sdr = $sdr_hash{$key}; if($sdr->sensor_number == $sensor_num) { $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; } @data=splice @data,36-$authoffset; 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 $subcommand = shift; my $netfun = 0x28; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; my @output; my $num; my $entry; my $skiptail=0; my @sel; #my $ipmisensoreventtab = "$ENV{XCATROOT}/lib/GUMI/ipmisensorevent.tab"; #my $ipmigenericeventtab = "$ENV{XCATROOT}/lib/GUMI/ipmigenericevent.tab"; my $mfg_id; my $prod_id; my $device_id; ($rc,$text,$mfg_id,$prod_id,$device_id) = getdevid(); $rc=0; unless (defined($subcommand)) { $subcommand = 'all'; } if($subcommand eq "all") { $skiptail=1; $num = 0x100 * 0x100; } elsif($subcommand eq "clear") { } elsif($subcommand =~ /^\d+$/) { $num = $subcommand; } else { return(1,"unsupported command eventlog $subcommand"); } #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 $error = docmd( $netfun, [0x48], \@returnd ); $tfactor = $returnd[40]<<24 | $returnd[39]<<16 | $returnd[38]<<8 | $returnd[37]; if ($tfactor > 0x20000000) { $tfactor -= time(); } else { $tfactor = 0; } @cmd=(0x40); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code == 0x00) { } elsif($code == 0x81) { $rc = 1; $text = "cannot execute command, SEL erase in progress"; } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my $sel_version = $returnd[37-$authoffset]; if($sel_version != 0x51) { $rc = 1; $text = sprintf("SEL version 51h support only, version reported: %x",$sel_version); return($rc,$text); } my $num_entries = $returnd[39-$authoffset]*256 + $returnd[38-$authoffset]; if($num_entries <= 0) { $rc = 1; $text = "no SEL entries"; return($rc,$text); } my $canres = $returnd[50-$authoffset] & 0b00000010; if(!$canres) { $rc = 1; $text = "SEL reservation not supported"; return($rc,$text); } my $res_id_ls=0; my $res_id_ms=0; my %auxloginfo; 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 @cmd=(0x42); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code == 0x00) { } elsif($code == 0x81) { $rc = 1; $text = "cannot execute command, SEL erase in progress"; } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } $res_id_ls = $returnd[37-$authoffset]; $res_id_ms = $returnd[38-$authoffset]; } elsif ($mfg_id == 2) { #For requests other than clear, we check for IBM extended auxillary log data my @auxdata; my $netfn = 0xa << 2; my @auxlogcmd = (0x5a,1); $error = docmd( $netfn, \@auxlogcmd, \@auxdata); @auxdata=splice @auxdata,36-$authoffset; #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 ($auxloginfo{$entry}) { $auxloginfo{$entry}.="!".$extdata; } else { $auxloginfo{$entry}=$extdata; } } } if ($auxloginfo{0}) { if ($skiptail) { foreach (split /!/,$auxloginfo{0}) { sendoutput(0,":Unassociated auxillary data detected:"); foreach (split /\n/,$_) { sendoutput(0,$_); } } } } #print Dumper(\%auxloginfo); } } if($subcommand eq "clear") { @cmd=(0x47,$res_id_ls,$res_id_ms,0x43,0x4c,0x52,0xaa); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my $erase_status = $returnd[37-$authoffset] & 0b00000001; #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[36-$authoffset]; 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[37-$authoffset] & 0b00000001; } $text = "SEL cleared"; return($rc,$text); } ($rc,$text) = initsdr(); if($rc != 0) { return($rc,$text); } @cmd=(0x43,$res_id_ls,$res_id_ms,0x00,0x00,0x00,0xFF); while(1) { $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; if ($skiptail) { sendoutput($rc,$text); return; } push(@output,$text); return($rc,@output); } $code = $returnd[36-$authoffset]; if($code == 0x00) { } elsif($code == 0x81) { $rc = 1; $text = "cannot execute command, SEL erase in progress"; } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } if ($skiptail) { sendoutput($rc,$text); return; } push(@output,$text); return($rc,@output); } my $next_rec_ls = $returnd[37-$authoffset]; my $next_rec_ms = $returnd[38-$authoffset]; my @sel_data = @returnd[39-$authoffset..39-$authoffset+16]; @cmd=(0x43,$res_id_ls,$res_id_ms,$next_rec_ls,$next_rec_ms,0x00,0xFF); $entry++; if ($debug) { print "$entry: "; hexdump(\@sel_data); } my $record_id = $sel_data[0] + $sel_data[1]*256; my $record_type = $sel_data[2]; if($record_type == 0x02) { } else { $text=getoemevent($record_type,$mfg_id,\@sel_data); if ($auxloginfo{$entry}) { $text.=" With additional data:\n".$auxloginfo{$entry}; } if ($skiptail) { sendoutput($rc,$text); } else { push(@output,$text); } if($next_rec_ms == 0xFF && $next_rec_ls == 0xFF) { last; } next; } 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 -= $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"; $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; ($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; # } # } 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"; } if ($auxloginfo{$entry}) { $text.=" with additional data:"; if ($skiptail) { sendoutput($rc,$text); foreach (split /\n/,$auxloginfo{$entry}) { sendoutput(0,$_); } } else { push(@output,$text); push @output,split /\n/,$auxloginfo{$entry}; } } else { if ($skiptail) { sendoutput($rc,$text); } else { push(@output,$text); } } if($next_rec_ms == 0xFF && $next_rec_ls == 0xFF) { last; } } my @routput = reverse(@output); my @noutput; my $c; foreach(@routput) { $c++; if($c > $num) { last; } push(@noutput,$_); } @output = reverse(@noutput); return($rc,@output); } sub getoemevent { my $record_type = shift; my $mfg_id = shift; my $sel_data = shift; 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 -= $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", ); 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 operation system wake-up vector", 0x13 => "Starting operation 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"; } } if($sensor_type == 0x12) { if($offset == 0x03) { } 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 ($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 checkleds { my $netfun = 0xe8; #really 0x3a 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; my $prod_id; ($rc,$text,$mfg_id,$prod_id) = getdevid(); if ($mfg_id != 2) { return (0,"LED status not supported on this system"); } ($rc,$text) = initsdr(); if($rc != 0) { return($rc,$text); } 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; } @cmd=(0xc0,$led_id_ms,$led_id_ls); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } if ($returnd[36-$authoffset] == 0xc9) { my $tmp; #we probably guessed endianness wrong. $tmp=$led_id_ls; $led_id_ls=$led_id_ms; $led_id_ms=$tmp; @cmd=(0xc0,$led_id_ms,$led_id_ls); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } } if ($returnd[38-$authoffset]) { # != 0) { #It's on... if ($returnd[42-$authoffset] == 4) { push(@output,sprintf("BIOS or admininstrator has %s lit",getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds"))); } elsif ($returnd[42-$authoffset] == 3) { push(@output,sprintf("A user has manually requested LED 0x%04x (%s) be active",$sdr->led_id,getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds"))); } elsif ($returnd[42-$authoffset] == 1 && $sdr->led_id !=0) { push(@output,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"),$returnd[40-$authoffset],$returnd[41-$authoffset],getsensorname($mfg_id,$prod_id,($returnd[40-$authoffset]<<8)+$returnd[41-$authoffset],"ibmleds"))); } elsif ($sdr->led_id ==0) { push(@output,sprintf("LED 0x0000 (%s) active to indicate system error condition.",getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds"))); } elsif ($returnd[42-$authoffset] == 2) { my $sensor_desc; #Ok, LED is tied to a sensor.. my $sensor_num=$returnd[41-$authoffset]; foreach $key (keys %sdr_hash) { my $osdr = $sdr_hash{$key}; if($osdr->sensor_number == $sensor_num) { $sensor_desc = $sdr_hash{$key}->id_string; if($osdr->rec_type == 0x01) { last; } } } $rc=0; #push(@output,sprintf("Sensor 0x%02x (%s) has activated LED 0x%04x",$sensor_num,$sensor_desc,$sdr->led_id)); push(@output,sprintf("LED 0x%02x%02x active to indicate Sensor 0x%02x (%s) error.",$led_id_ms,$led_id_ls,$sensor_num,$sensor_desc)); } } } } if ($#output==-1) { push(@output,"No active error LEDs detected"); } return($rc,@output); } sub vitals { my $subcommand = shift; my $rc = 0; my $text; my $key; my @sensor_filters=(0x00); my @output; my $reading; my $unitdesc; my $value; my $extext; my $format = "%-30s%8s %-20s"; my $per = " "; my $doall; $doall=0; $rc=0; if($subcommand eq "all") { @sensor_filters=(0x01); #,0x02,0x03,0x04); $doall=1; } elsif($subcommand =~ /temp/) { @sensor_filters=(0x01); } elsif($subcommand eq "voltage") { @sensor_filters=(0x02); } elsif($subcommand =~ /watt/) { @sensor_filters=(0x03); } elsif($subcommand eq "fanspeed") { @sensor_filters=(0x04); } elsif($subcommand eq "power") { ($rc,$text) = power("stat"); $text = sprintf($format,"Power Status:",$text); return($rc,$text); } elsif($subcommand eq "leds") { my @cleds; ($rc,@cleds) = checkleds(); foreach $text (@cleds) { push(@output,$text); } } else { return(1,"unsupported command vitals $subcommand"); } ($rc,$text) = initsdr(); if($rc != 0) { return($rc,$text); } foreach(@sensor_filters) { my $filter = $_; foreach $key (sort {$sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string} keys %sdr_hash) { 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; ($rc,$reading,$extext) = readsensor($sdr); $unitdesc = ""; if($rc == 0) { $unitdesc = $units{$sdr->sensor_units_2}; $value = $reading; if ($sdr->rec_type==1) { $value = (($sdr->M * $reading) + ($sdr->B * (10**$sdr->B_exp))) * (10**$sdr->R_exp); } if($sdr->rec_type != 1 or $sdr->linearization == 0) { $reading = $value; if($value == int($value)) { $lformat = "%-30s%8d%s"; } else { $lformat = "%-30s%8.3f%s"; } } elsif($sdr->linearization == 7) { if($value > 0) { $reading = 1/$value; } else { $reading = 0; } $lformat = "%-30s%8d %s"; } 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 here.. } 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)"; } 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)"; } } #$unitdesc.= sprintf(" %x",$sdr->sensor_type); $text = sprintf($lformat,$sdr->id_string . ":",$reading,$per.$unitdesc); if ($extext) { $text="$text ($extext)"; } push(@output,$text); } # else { # printf("%x %s %d\n",$sdr->sensor_number,$sdr->id_string,$sdr->sensor_type); # } } } if($subcommand eq "all") { my @cleds; ($rc,$text) = power("stat"); $text = sprintf($format,"Power Status:",$text,""); push(@output,$text); ($rc,@cleds) = checkleds(); foreach $text (@cleds) { push(@output,$text); } } return($rc,@output); } sub readsensor { my $sdr = shift; my $sensor = $sdr->sensor_number; my $netfun = 0x10; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; @cmd = (0x2d,$sensor); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code != 0x00) { $rc = 1; $text = $codes{$code}; if(!$text) { $text = sprintf("unknown response %02x",$code); } chomp $text; return($rc,$text); } if ($returnd[38-$authoffset] & 0x20) { $rc = 1; $text = "N/A"; return($rc,$text); } $text = $returnd[37-$authoffset]; my $exdata1 = $returnd[39-$authoffset]; my $exdata2 = $returnd[39-$authoffset]; my $extext; my @exparts; 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) { if ($sdr->sensor_type == 0x10) { @exparts=(); 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"; } if (@exparts) { $extext = join(",",@exparts); } } 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"; } } 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 (@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 (@exparts) { $extext = join(",",@exparts); } } 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 (@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"; } if (@exparts) { $extext = join(",",@exparts); } } elsif ($sdr->sensor_type == 0x9) { @exparts=(); if ($exdata1 & 1) { push @exparts,"Power off"; } if ($exdata1 & 1<<1) { push @exparts,"Power off"; } 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"; } if (@exparts) { $extext = join(",",@exparts); } } else { $extext = "xCAT needs to add support for ".$sdr->sensor_type; } } return($rc,$text,$extext); } sub initsdr { my $netfun; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; my $sdr_rep_info = SDR_rep_info->new(); my $resv_id_ls; my $resv_id_ms; my $nrid_ls = 0; my $nrid_ms = 0; my $rid_ls = 0; my $rid_ms = 0; my $sdr_ver; my $sdr_type; my $sdr_offset; my $sdr_len; my @sdr_data = (); my $offset; my $len; my $i; # my $numbytes = 27; my $numbytes = 22; my $override_string; my $ipmisensortab = "$ENV{XCATROOT}/lib/GUMI/ipmisensor.tab"; my $byte_format; my $cache_file; my $mfg_id; my $prod_id; my $device_id; my $dev_rev; my $fw_rev1; my $fw_rev2; ($rc,$text,$mfg_id,$prod_id,$device_id,$dev_rev,$fw_rev1,$fw_rev2) = getdevid(); if($rc != 0) { return($rc,$text); } $cache_file = "$cache_dir/sdr_$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2.$cache_version"; if($enable_cache eq "yes") { $rc = loadsdrcache($cache_file); if($rc == 0) { return($rc); } $rc = 0; } ($rc,$text) = get_sdr_rep_info($sdr_rep_info); if($rc != 0) { return($rc,$text); } if($sdr_rep_info->version != 0x51) { $rc = 1; $text = "SDR version 51h support only."; return($rc,$text); } if($sdr_rep_info->resv_sdr != 1) { $rc = 1; $text = "SDR reservation unsupported."; return($rc,$text); } ($rc,$text,$resv_id_ls,$resv_id_ms) = resv_sdr_repo(); if($rc != 0) { return($rc,$text); } 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"; } foreach(1..$sdr_rep_info->rec_count) { $netfun = 0x28; @cmd = (0x23,$resv_id_ls,$resv_id_ms,$nrid_ls,$nrid_ms,0,5); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code != 0x00) { $rc = 1; $text = $codes{$code}; if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } return($rc,$text); } ###x336 hack $rid_ls = $nrid_ls; $rid_ms = $nrid_ms; ### $nrid_ls = $returnd[37-$authoffset]; $nrid_ms = $returnd[38-$authoffset]; ### correct IPMI code # $rid_ls = $returnd[39-$authoffset]; # $rid_ms = $returnd[40-$authoffset]; ### $sdr_ver = $returnd[41-$authoffset]; $sdr_type = $returnd[42-$authoffset]; $sdr_len = $returnd[43-$authoffset] + 5; if($sdr_type == 0x01) { $sdr_offset = 0; } elsif($sdr_type == 0x02) { $sdr_offset = 16; } elsif($sdr_type == 0xC0) { #LED descriptor, maybe } elsif($sdr_type == 0x11) { #FRU locator } elsif($sdr_type == 0x12) { next; } else { next; } @sdr_data = (0,0,0,$sdr_ver,$sdr_type,$sdr_len); $offset = 5; for($i=5;$i<$sdr_len;$i+=$numbytes) { $len = $numbytes; if($offset+$len > $sdr_len) { $len = $sdr_len - $offset; } @cmd = (0x23,$resv_id_ls,$resv_id_ms,$rid_ls,$rid_ms,$offset,$len); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[36-$authoffset]; if($code != 0x00) { $rc = 1; $text = $codes{$code}; if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } return($rc,$text); } @sdr_data = (@sdr_data,@returnd[39-$authoffset..@returnd-2]); $offset += $len; } if($sdr_type == 0x11) { #FRU locator my $sdr = decode_fru_locator(@sdr_data); if ($sdr) { $sdr_hash{$sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number} = $sdr; } next; } if($debug) { hexadump(\@sdr_data); } if($sdr_type == 0x12) { hexadump(\@sdr_data); next; } my $sdr = SDR->new(); if ($mfg_id == 2 && $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]); } #$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.... $sdr_hash{"260.260.".$sdr->led_id} = $sdr; next; } $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-$sdr_offset]); $override_string = getsensorname($mfg_id,$prod_id,$sdr->sensor_number,$ipmisensortab); if($override_string ne "") { $sdr->id_string($override_string); } else { unless (defined $sdr->id_string_type) { next; } $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-$sdr_offset..49-$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 { next; } } } $sdr_hash{$sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number} = $sdr; } if($debug) { my $key; # foreach $key (sort {$sdr_hash{$a}->sensor_number <=> $sdr_hash{$b}->sensor_number} keys %sdr_hash) { foreach $key (sort {$sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string} keys %sdr_hash) { my $sdr = $sdr_hash{$key}; # printf("%d %x %s\n",$sdr->rec_type,$sdr->sensor_number,$sdr->id_string); # printf("%x %x %x %s\n",$sdr->sensor_owner_id,$sdr->sensor_owner_lun,$sdr->sensor_number,$sdr->id_string); printf("%x %x %x %s %d\n",$sdr->sensor_owner_id,$sdr->sensor_owner_lun,$sdr->sensor_number,$sdr->id_string,$sdr->linearization); } # printf("\n%x %s\n",$sdr_hash{0x70}->sensor_number,$sdr_hash{0x70}->id_string); } if($enable_cache eq "yes") { storsdrcache($cache_file); } return($rc,$text); } sub getsensorname { my $mfgid = shift; my $prodid = shift; my $sensor = shift; my $file = shift; my $mfg; my $prod; my $type; my $num; my $desc; my $name=""; if ($file eq "ibmleds") { 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[36-$authoffset]; 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[37-$authoffset..@returnd-2]); } sub getdevid { my $netfun = 0x18; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; @cmd = (0x01); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } else { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text = ""; } else { $rc = 1; $text = $codes{$code}; if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } return($rc,$text); } } my $device_id = $returnd[37-$authoffset]; my $device_rev = $returnd[38-$authoffset] & 0b00001111; my $firmware_rev1 = $returnd[39-$authoffset] & 0b01111111; my $firmware_rev2 = $returnd[40-$authoffset]; my $ipmi_ver = $returnd[41-$authoffset]; my $dev_support = $returnd[42-$authoffset]; 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; } my $mfg_id = $returnd[43-$authoffset] + $returnd[44-$authoffset]*0x100 + $returnd[45-$authoffset]*0x10000; my $prod_id = $returnd[46-$authoffset] + $returnd[47-$authoffset]*0x100; my @data = @returnd[48-$authoffset..@returnd-2]; return($rc,$text,$mfg_id,$prod_id,$device_id,$device_rev,$firmware_rev1,$firmware_rev2); } sub getguid { my $guidcmd = shift; my $netfun = @$guidcmd[0] || 0x18; my @cmd = @$guidcmd[1] || 0x37; my @returnd = (); my $error; my $rc = 0; my $text; my $code; $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } else { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text = ""; } else { $rc = 1; $text = $codes{$code}; if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } return($rc,$text); } } my @guid = @returnd[37-$authoffset..52-$authoffset]; my $guidtext = sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",@guid); $guidtext =~ tr/[a-z]/[A-Z]/; return($rc,$text,$guidtext); } sub get_sdr_rep_info { my $sdr_rep_info = shift; my $netfun = 0x28; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; @cmd = (0x20); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } else { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text = ""; } else { $rc = 1; $text = $codes{$code}; if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } return($rc,$text); } } $sdr_rep_info->version($returnd[37-$authoffset]); $sdr_rep_info->rec_count($returnd[38-$authoffset] + $returnd[39-$authoffset]*0x100); $sdr_rep_info->resv_sdr(($returnd[50-$authoffset] & 0b00000010) ? 1 : 0); return($rc,$text); } sub resv_sdr_repo { my $netfun = 0x28; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; @cmd = (0x22); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } else { $code = $returnd[36-$authoffset]; if($code == 0x00) { $text = ""; } else { $rc = 1; $text = $codes{$code}; if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } return($rc,$text); } } my $resv_id_ls = $returnd[37-$authoffset]; my $resv_id_ms = $returnd[38-$authoffset]; return($rc,$text,$resv_id_ls,$resv_id_ms); } sub docmd { my $netfun = shift; my $cmd = shift; my $response = shift; my @rn; my $length; my @msg; my @message; my $error = ""; my @response; my @data; incseqlun(); @data = ($rqsa,$seqlun,@$cmd); @rn = ($rssa,$netfun); $length = (scalar @data)+4; @message = ($rssa,$netfun,dochksum(\@rn),@data,dochksum(\@data)); incseqnum(); @msg = ( @rmcp, $auth, @seqnum, @session_id, authcode(2,\@message), $length, @message ); ($error,@$response) = domsg($sock,\@msg,$timeout,1); return($error); } sub getchanauthcap { $auth = 0x00; my $netfun = 0x18; my @data; my @rn; my $length; my @msg; my $error = ""; my @response; my $code; @data = ($rqsa,$seqlun,0x38,0x8e,0x04); @rn = ($rssa,$netfun); $length = (scalar @data)+4; @msg = ( @rmcp, $auth, @seqnum, @session_id, $length, $rssa, $netfun, dochksum(\@rn), @data, dochksum(\@data) ); ($error,@response) = domsg($sock,\@msg,$timeout,0); if($error) { return($error); } $code = $response[20]; if ($code == 0xcc) { #Despite the fact that the IPMI 1.5 spec declared the high bits to be #reserved, some 1.5 BMCs checked the value anyway (erroneously) #This retries with the IPMI 2.0 bit cleared @data = ($rqsa,$seqlun,0x38,0x0e,0x04); @rn = ($rssa,$netfun); $length = (scalar @data)+4; @msg = ( @rmcp, $auth, @seqnum, @session_id, $length, $rssa, $netfun, dochksum(\@rn), @data, dochksum(\@data) ); ($error,@response) = domsg($sock,\@msg,$timeout,0); if($error) { return($error); } $code = $response[20]; } if($code != 0x00) { $error = $codes{$code}; if(!$error) { $error = "Unknown get channel authentication capabilities error $code" } return($error); } $channel_number=$response[21]; if($response[22] & 0b10000000 and $response[24] & 0b00000010) { $ipmiv2=1; } if($response[22] & 0b00000100) { $auth=0x02; } elsif($response[22] & 0b00010000) { $auth=0x04; } else { $error = "unsupported Authentication Type Support"; } return($error); } sub getsessionchallenge { my $tauth = 0x00; my $netfun = 0x18; my @data; my @rn; my $length; my @msg; my $error = ""; my @response; my $code; incseqlun(); @data = ($rqsa,$seqlun,0x39,$auth,@user); @rn = ($rssa,$netfun); $length = (scalar @data)+4; @msg = ( @rmcp, $tauth, @seqnum, @session_id, $length, $rssa, $netfun, dochksum(\@rn), @data, dochksum(\@data) ); ($error,@response) = domsg($sock,\@msg,$timeout,0); if(!$error) { $code = $response[20]; if($code != 0x00) { $error = $codes{$code}; if(!$error) { $error = "Unknown get session challenge error $code" } } if($code == 0x81) { $error = "Invalid user name"; } elsif($code == 0x82) { $error = "null user name not enabled"; } @session_id = @response[21,22,23,24]; for (my $i=0;$i<16;$i++){ $challenge[$i] = $response[25+$i]; } } return($error); } sub activatesession { my $netfun = 0x18; my @data; my @rn; my $length; my @msg; my @message; my $error = ""; my @response; my $code; incseqlun(); @data = ($rqsa,$seqlun,0x3A,$auth,0x04,@challenge,0x01,0x00,0x00,0x00); @rn = ($rssa,$netfun); $length = (scalar @data)+4; @message = ($rssa,$netfun,dochksum(\@rn),@data,dochksum(\@data)); @msg = ( @rmcp, $auth, @seqnum, @session_id, authcode(2,\@message), $length, @message ); ($error,@response) = domsg($sock,\@msg,$timeout,0); if(!$error) { $code = $response[36]; if($code != 0x00) { $error = $codes{$code}; if(!$error) { $error = "Unknown activate session error $code" } } if($code == 0x81) { $error = "No session slot available"; } elsif($code == 0x82) { $error = "No slot available for given user"; } elsif($code == 0x83) { $error = "No slot available to support user due to maximum privilege capability"; } elsif($code == 0x84) { $error = "Session sequence number out-of-range"; } elsif($code == 0x85) { $error = "Invalid session ID in request"; } elsif($code == 0x86) { $error = "Requested maximum privilege level exceeds user and/of channel privilege limit"; } unless ($error) { $auth = $response[37]; if($auth == 0x00) { $authoffset=16; } elsif($auth == 0x02) { } elsif($auth == 0x04) { } else { $error = "activate session requested unsupported Authentication Type Support"; } } ###check @session_id = @response[38,39,40,41]; @seqnum = @response[42,43,44,45]; } return($error); } sub setprivlevel() { my $netfun = 0x18; my @data; my @rn; my $length; my @msg; my @message; my $error = ""; my @response; my $code; incseqlun(); @data = ($rqsa,$seqlun,0x3B,0x04); @rn = ($rssa,$netfun); $length = (scalar @data)+4; @message = ($rssa,$netfun,dochksum(\@rn),@data,dochksum(\@data)); @msg = ( @rmcp, $auth, @seqnum, @session_id, authcode(2,\@message), $length, @message ); ($error,@response) = domsg($sock,\@msg,$timeout,1); if(!$error) { $code = $response[36-$authoffset]; if($code != 0x00) { $error = $codes{$code}; if(!$error) { $error = "Unknown set session privilege level error $code" } } if($code == 0x80) { $error = "Requested level not available for this user"; } elsif($code == 0x81) { $error = "Requested level exceeds channel and/or user privilege limit"; } elsif($code == 0x82) { $error = "Cannot disable user level authentication"; } } return($error); } sub closesession() { incseqnum(); my $netfun = 0x18; my @data; my @rn; my $length; my @msg; my @message; my $error = ""; my @response; my $code; incseqlun(); @data = ($rqsa,$seqlun,0x3C,@session_id); @rn = ($rssa,$netfun); $length = (scalar @data)+4; @message = ($rssa,$netfun,dochksum(\@rn),@data,dochksum(\@data)); @msg = ( @rmcp, $auth, @seqnum, @session_id, authcode(2,\@message), $length, @message ); ($error,@response) = domsg($sock,\@msg,$timeout,1); #Have a quicker retry on the close session if(!$error) { $code = $response[36-$authoffset]; if($code != 0x00) { $error = $codes{$code}; if(!$error) { $error = "Unknown close session error $code" } } if($code == 0x87) { $error = "Invalid session ID in request"; } } return($error); } sub domsg { my $sock = shift; my $msg = shift; my $timeout = shift; my $seq = shift || 0; my $debug = $localdebug; my $trys = $localtrys; my $send; my $quit = 0; my $error=""; my $recv; my @response; my $timedout; my @foo; my @message; $send = pack('C*',@$msg); while($trys > 0) { $trys--; $error = ""; $timedout = 0; if($debug) { print "try: $trys, timeout: $timeout\n"; } if(!$sock->send($send)) { $error = $!; sleep(1); next; } my $s = IO::Select->new($sock); #local $SIG{ALRM} = sub { $timedout = 1 and die }; #alarm($timeout); my $received = $s->can_read($timeout); if($received and $received > 0) { if ($sock->recv($recv,1300)) { if($recv) { @response = unpack("C*",$recv); last; } } else { $error = $!; } } else { $error = "timeout"; } ###ugly updated hack to support md5. if($seq) { incseqnum(); @$msg[5..8] = @seqnum[0..3]; @message = @$msg[30..@$msg-1]; if($auth != 0x00) { @$msg[13..28] = authcode(2,\@message); } $send = pack('C*',@$msg); } } if($timedout == 1) { if($error) { $error = "timeout $error" } else { $error = "timeout" } } return($error,@response); } sub dochksum() { my $data = shift; my $sum = 0; foreach(@$data) { $sum += $_; } $sum = ~$sum + 1; return($sum & 0xFF); } sub dopad16 { my @pad16 = unpack("C*",shift); for(my $i=@pad16;$i<16;$i++) { $pad16[$i] = 0; } return(@pad16); } 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] !~ /[\/\w\-:\[\.\]]/) { 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($alpha[$c] !~ /\w/) { $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 incseqnum { my $i; for($i = 0;$i < 4;$i++) { if($seqnum[$i] < 0xFF) { $seqnum[$i]++; last; } $seqnum[$i] = 0; } if($seqnum[3] > 0xFF) { @seqnum = (0,0,0,0); } } sub incseqlun { $seqlun += 4; if($seqlun > 0xFF) { $seqlun = 0; } } sub authcode { my $type = shift; my $message = shift; my @authcode; if($auth == 0x02) { if($type == 1) { @authcode = unpack("C*",md5(pack("C*",@pass,@session_id,@challenge,@pass))); } elsif($type == 2) { @authcode = unpack("C*",md5(pack("C*",@pass,@session_id,@$message,@seqnum,@pass))); } } elsif($auth == 0x04) { @authcode = @pass; } elsif($auth == 0x00) { @authcode = (); } return(@authcode); } 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 $key; my $fh; system("mkdir -p $cache_dir"); if(!open($fh,">$file")) { return(1); } flock($fh,LOCK_EX) || return(1); foreach $key (keys %sdr_hash) { my $r = $sdr_hash{$key}; store_fd($r,$fh); } close($fh); return(0); } sub loadsdrcache { my $file = shift; my $r; my $c=0; my $fh; if(!open($fh,"<$file")) { return(1); } flock($fh,LOCK_SH) || return(1); while() { eval { $r = retrieve_fd($fh); } || last; $sdr_hash{$r->sensor_owner_id . "." . $r->sensor_owner_lun . "." . $r->sensor_number} = $r; } close($fh); return(0); } sub preprocess_request { my $request = shift; if ($request->{_xcatpreprocessed}->[0] == 1) { return [$request]; } #exit if preprocessed my $callback=shift; my @requests; my $noderange = $request->{node}; #Should be arrayref my $command = $request->{command}->[0]; my $extrargs = $request->{arg}; my @exargs=($request->{arg}); 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]}); $request = {}; return 0; } if ( ($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')) { $callback->({data=>["Unsupported command: $command $subcmd", $usage_string]}); $request = {}; return; } } if (!$noderange) { $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 $service = "xcat"; my $sn = xCAT::Utils->get_ServiceNode($noderange, $service, "MN"); # build each request for each service node foreach my $snkey (keys %$sn) { #print "snkey=$snkey\n"; my $reqcopy = {%$request}; $reqcopy->{node} = $sn->{$snkey}; $reqcopy->{'_xcatdest'} = $snkey; $reqcopy->{_xcatpreprocessed}->[0] = 1; push @requests, $reqcopy; } 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]; $cb->($ipmicons); } sub process_request { my $request = shift; my $callback = shift; my $noderange = $request->{node}; #Should be arrayref my $command = $request->{command}->[0]; my $extrargs = $request->{arg}; my @exargs=($request->{arg}); if (ref($extrargs)) { @exargs=@$extrargs; } my $ipmiuser = 'USERID'; my $ipmipass = 'PASSW0RD'; my $ipmitrys = 3; my $ipmitimeout = 2; my $ipmimaxp = 64; my $sitetab = xCAT::Table->new('site'); my $ipmitab = xCAT::Table->new('ipmi'); my $tmp; if ($sitetab) { ($tmp)=$sitetab->getAttribs({'key'=>'ipmimaxp'},'value'); if (defined($tmp)) { $ipmimaxp=$tmp->{value}; } ($tmp)=$sitetab->getAttribs({'key'=>'ipmitimeout'},'value'); if (defined($tmp)) { $ipmitimeout=$tmp->{value}; } ($tmp)=$sitetab->getAttribs({'key'=>'ipmiretries'},'value'); if (defined($tmp)) { $ipmitrys=$tmp->{value}; } ($tmp)=$sitetab->getAttribs({'key'=>'ipmisdrcache'},'value'); if (defined($tmp)) { $enable_cache=$tmp->{value}; } } my $passtab = xCAT::Table->new('passwd'); if ($passtab) { ($tmp)=$passtab->getAttribs({'key'=>'ipmi'},'username','password'); if (defined($tmp)) { $ipmiuser = $tmp->{username}; $ipmipass = $tmp->{password}; } } #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']) ; foreach(@$noderange) { my $node=$_; my $nodeuser=$ipmiuser; my $nodepass=$ipmipass; my $nodeip = $node; my $ent; if (defined($ipmitab)) { $ent=$ipmihash->{$node}->[0]; if (ref($ent) and defined $ent->{bmc}) { $nodeip = $ent->{bmc}; } if (ref($ent) and defined $ent->{username}) { $nodeuser = $ent->{username}; } if (ref($ent) and defined $ent->{password}) { $nodepass = $ent->{password}; } } push @donargs,[$node,$nodeip,$nodeuser,$nodepass]; } if ($request->{command}->[0] eq "getipmicons") { foreach (@donargs) { getipmicons($_,$callback); } return; } #get new node status my %oldnodestatus=(); #saves the old node status my @allerrornodes=(); my $check=0; my $global_check=1; if ($sitetab) { (my $ref) = $sitetab->getAttribs({key => 'nodestatus'}, 'value'); if ($ref) { if ($ref->{value} =~ /0|n|N/) { $global_check=0; } } } if ($command eq 'rpower') { if (($global_check) && ($extrargs->[0] ne 'stat') && ($extrargs->[0] ne 'status') && ($extrargs->[0] ne 'state')) { $check=1; my @allnodes=(); foreach (@donargs) { push(@allnodes, $_->[0]); } #save the old status my $nodelisttab = xCAT::Table->new('nodelist'); if ($nodelisttab) { my $tabdata = $nodelisttab->getNodesAttribs(\@allnodes, ['node', 'status']); foreach my $node (@allnodes) { my $tmp1 = $tabdata->{$node}->[0]; if ($tmp1) { if ($tmp1->{status}) { $oldnodestatus{$node}=$tmp1->{status}; } else { $oldnodestatus{$node}=""; } } } } #print "oldstatus:" . Dumper(\%oldnodestatus); #set the new status to the nodelist.status my %newnodestatus=(); my $newstat; if (($extrargs->[0] eq 'off') || ($extrargs->[0] eq 'softoff')) { my $newstat=$::STATUS_POWERING_OFF; $newnodestatus{$newstat}=\@allnodes; } else { #get the current nodeset stat if (@allnodes>0) { my $nsh={}; my ($ret, $msg)=xCAT::SvrUtils->getNodesetStates(\@allnodes, $nsh); if (!$ret) { foreach (keys %$nsh) { my $newstat=xCAT_monitoring::monitorctrl->getNodeStatusFromNodesetState($_, "rpower"); $newnodestatus{$newstat}=$nsh->{$_}; } } else { $callback->({data=>$msg}); } } } #print "newstatus" . Dumper(\%newnodestatus); xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%newnodestatus, 1); } } my $children = 0; $SIG{INT} = $SIG{TERM} = sub { #prepare to process job termination and propogate it down foreach (keys %bmc_comm_pids) { kill 2, $_; } exit 0; }; $SIG{CHLD} = sub {my $kpid; do { $kpid = waitpid(-1, WNOHANG); if ($bmc_comm_pids{$kpid}) { delete $bmc_comm_pids{$kpid}; $children--; } } while $kpid > 0; }; my $sub_fds = new IO::Select; foreach (@donargs) { while ($children > $ipmimaxp) { my $handlednodes={}; forward_data($callback,$sub_fds,$handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } } $children++; my $cfd; my $pfd; socketpair($pfd, $cfd,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die "socketpair: $!"; $cfd->autoflush(1); $pfd->autoflush(1); my $child = xCAT::Utils->xfork(); unless (defined $child) { die "Fork failed" }; if ($child == 0) { close($cfd); my $rrc=donode($pfd,$_->[0],$_->[1],$_->[2],$_->[3],$ipmitimeout,$ipmitrys,$command,-args=>\@exargs); close($pfd); exit(0); } $bmc_comm_pids{$child}=1; close ($pfd); $sub_fds->add($cfd) } while ($sub_fds->count > 0 and $children > 0) { my $handlednodes={}; forward_data($callback,$sub_fds,$handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } } #Make sure they get drained, this probably is overkill but shouldn't hurt my $rc=1; while ( $rc>0 ) { my $handlednodes={}; $rc=forward_data($callback,$sub_fds,$handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } } if ($check) { #print "allerrornodes=@allerrornodes\n"; #revert the status back for there is no-op for the nodes my %old=(); foreach my $node (@allerrornodes) { my $stat=$oldnodestatus{$node}; if (exists($old{$stat})) { my $pa=$old{$stat}; push(@$pa, $node); } else { $old{$stat}=[$node]; } } xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%old, 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 { $outfd = shift; my $node = shift; $currnode=$node; my $bmcip = shift; my $user = shift; my $pass = shift; my $timeout = shift; my $retries = shift; my $command = shift; my %namedargs=@_; my $transid = $namedargs{-transid}; my $extra=$namedargs{-args}; my @exargs=@$extra; my ($rc,@output) = ipmicmd($bmcip,623,$user,$pass,$timeout,$retries,0,$command,@exargs); my @outhashes; sendoutput($rc,@output); yield; #my $msgtoparent=freeze(\@outhashes); # print $outfd $msgtoparent; return $rc; } 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]=$currnode; if ($rc) { $output{node}->[0]->{errorcode}=[$rc]; $output{node}->[0]->{error}->[0]=$text; } else { $output{node}->[0]->{data}->[0]->{contents}->[0]=$text; } #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); } } 1;