#!/usr/bin/perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html #egan@us.ibm.com #modified by jbjohnso@us.ibm.com #(C)IBM Corp package xCAT_plugin::ipmi; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } use lib "$::XCATROOT/lib/perl"; use strict; use warnings "all"; use xCAT::GlobalDef; use xCAT_monitoring::monitorctrl; use xCAT::SPD qw/decode_spd/; use xCAT::IPMI; use 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 $iem_support; my $vpdhash; eval { require IBM::EnergyManager; $iem_support=1; }; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw( ipmicmd ); sub handled_commands { return { rpower => 'nodehm:power,mgt', #done renergy => 'nodehm:power,mgt', getipmicons => 'ipmi', #done rspconfig => 'nodehm:mgt', #done rspreset => 'nodehm:mgt', #done rvitals => 'nodehm:mgt', rinv => 'nodehm:mgt', rsetboot => 'nodehm:mgt', #done rbeacon => 'nodehm:mgt', #done reventlog => 'nodehm:mgt', rfrurewrite => 'nodehm:mgt', getrvidparms => 'nodehm:mgt' #done } } #use Data::Dumper; use POSIX "WNOHANG"; use IO::Handle; use IO::Socket; use IO::Select; use Class::Struct; use Digest::MD5 qw(md5); use Digest::SHA1 qw(sha1); use POSIX qw(WNOHANG mkfifo strftime); use Fcntl qw(:flock); #local to module my $callback; my $ipmi_bmcipaddr; my $timeout; my $port; my $debug; my $ndebug = 0; my @cmdargv; my $sock; my $noclose; my %fru_hash; my %sessiondata; #hold per session variables, in preparation for single-process strategy my %pendingtransactions; #list of peers with callbacks, callback arguments, and timer expiry data my $ipmiv2=0; my $authoffset=0; my $enable_cache="yes"; my $cache_dir = "/var/cache/xcat"; #my $ibmledtab = $ENV{XCATROOT}."/lib/GUMI/ibmleds.tab"; use xCAT::data::ibmleds; use xCAT::data::ipmigenericevents; use xCAT::data::ipmisensorevents; my $cache_version = 3; my $frudex; #iterator for initfru to use my %sdr_caches; #store sdr cachecs in memory indexed such that identical nodes do not hit the disk multiple times my $status_noop="XXXno-opXXX"; my %idpxthermprofiles = ( '0z' => [0x37,0x41,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], '1a' => [0x30,0x3c,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], '2b' => [0x30,0x3c,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], '3c' => [0x30,0x3c,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], '4d' => [0x37,0x44,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], '5e' => [0x37,0x44,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], '6f' => [0x35,0x44,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], ); my %codes = ( 0x00 => "Command Completed Normal", 0xC0 => "Node busy, command could not be processed", 0xC1 => "Invalid or unsupported command", 0xC2 => "Command invalid for given LUN", 0xC3 => "Timeout while processing command, response unavailable", 0xC4 => "Out of space, could not execute command", 0xC5 => "Reservation canceled or invalid reservation ID", 0xC6 => "Request data truncated", 0xC7 => "Request data length invalid", 0xC8 => "Request data field length limit exceeded", 0xC9 => "Parameter out of range", 0xCA => "Cannot return number of requested data bytes", 0xCB => "Requested Sensor, data, or record not present", 0xCB => "Not present", 0xCC => "Invalid data field in Request", 0xCD => "Command illegal for specified sensor or record type", 0xCE => "Command response could not be provided", 0xCF => "Cannot execute duplicated request", 0xD0 => "Command reqponse could not be provided. SDR Repository in update mode", 0xD1 => "Command response could not be provided. Device in firmware update mode", 0xD2 => "Command response could not be provided. BMC initialization or initialization agent in progress", 0xD3 => "Destination unavailable", 0xD4 => "Insufficient privilege level", 0xD5 => "Command or request parameter(s) not supported in present state", 0xFF => "Unspecified error", ); #Payload types: # 0 => IPMI (format 1 0) # 1 => SOL 1 0 # 0x10 => rmcp+ open req 1 0 # 0x11 => rmcp+ response 1 0 # 0x12 => rakp1 (all 1 0) # 0x13 => rakp2 # 0x14 => rakp3 # 0x15 => rakp4 my %units = ( 0 => "", #"unspecified", 1 => "C", 2 => "F", 3 => "K", 4 => "Volts", 5 => "Amps", 6 => "Watts", 7 => "Joules", 8 => "Coulombs", 9 => "VA", 10 => "Nits", 11 => "lumen", 12 => "lux", 13 => "Candela", 14 => "kPa", 15 => "PSI", 16 => "Newton", 17 => "CFM", 18 => "RPM", 19 => "Hz", 20 => "microsecond", 21 => "millisecond", 22 => "second", 23 => "minute", 24 => "hour", 25 => "day", 26 => "week", 27 => "mil", 28 => "inches", 29 => "feet", 30 => "cu in", 31 => "cu feet", 32 => "mm", 33 => "cm", 34 => "m", 35 => "cu cm", 36 => "cu m", 37 => "liters", 38 => "fluid ounce", 39 => "radians", 40 => "steradians", 41 => "revolutions", 42 => "cycles", 43 => "gravities", 44 => "ounce", 45 => "pound", 46 => "ft-lb", 47 => "oz-in", 48 => "gauss", 49 => "gilberts", 50 => "henry", 51 => "millihenry", 52 => "farad", 53 => "microfarad", 54 => "ohms", 55 => "siemens", 56 => "mole", 57 => "becquerel", 58 => "PPM", 59 => "reserved", 60 => "Decibels", 61 => "DbA", 62 => "DbC", 63 => "gray", 64 => "sievert", 65 => "color temp deg K", 66 => "bit", 67 => "kilobit", 68 => "megabit", 69 => "gigabit", 70 => "byte", 71 => "kilobyte", 72 => "megabyte", 73 => "gigabyte", 74 => "word", 75 => "dword", 76 => "qword", 77 => "line", 78 => "hit", 79 => "miss", 80 => "retry", 81 => "reset", 82 => "overflow", 83 => "underrun", 84 => "collision", 85 => "packets", 86 => "messages", 87 => "characters", 88 => "error", 89 => "correctable error", 90 => "uncorrectable error", ); my %chassis_types = ( 0 => "Unspecified", 1 => "Other", 2 => "Unknown", 3 => "Desktop", 4 => "Low Profile Desktop", 5 => "Pizza Box", 6 => "Mini Tower", 7 => "Tower", 8 => "Portable", 9 => "LapTop", 10 => "Notebook", 11 => "Hand Held", 12 => "Docking Station", 13 => "All in One", 14 => "Sub Notebook", 15 => "Space-saving", 16 => "Lunch Box", 17 => "Main Server Chassis", 18 => "Expansion Chassis", 19 => "SubChassis", 20 => "Bus Expansion Chassis", 21 => "Peripheral Chassis", 22 => "RAID Chassis", 23 => "Rack Mount Chassis", ); my %MFG_ID = ( 2 => "IBM", 343 => "Intel", ); my %PROD_ID = ( "2:34869" => "e325", "2:3" => "x346", "2:4" => "x336", "343:258" => "Tiger 2", "343:256" => "Tiger 4", ); my $localtrys = 3; my $localdebug = 0; struct SDR => { rec_type => '$', sensor_owner_id => '$', sensor_owner_lun => '$', sensor_number => '$', entity_id => '$', entity_instance => '$', sensor_init => '$', sensor_cap => '$', sensor_type => '$', event_type_code => '$', ass_event_mask => '@', deass_event_mask => '@', dis_read_mask => '@', sensor_units_1 => '$', sensor_units_2 => '$', sensor_units_3 => '$', linearization => '$', M => '$', tolerance => '$', B => '$', accuracy => '$', accuracy_exp => '$', R_exp => '$', B_exp => '$', analog_char_flag => '$', nominal_reading => '$', normal_max => '$', normal_min => '$', sensor_max_read => '$', sensor_min_read => '$', upper_nr_threshold => '$', upper_crit_thres => '$', upper_ncrit_thres => '$', lower_nr_threshold => '$', lower_crit_thres => '$', lower_ncrit_thres => '$', pos_threshold => '$', neg_threshold => '$', id_string_type => '$', id_string => '$', #LED id led_id => '$', fru_type => '$', fru_subtype => '$', }; 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 ipmicmd { my $sessdata = shift; my $rc=0; my $text=""; my $error=""; my @output; my $noclose=0; $sessdata->{ipmisession}->login(callback=>\&on_bmc_connect,callback_args=>$sessdata); } sub on_bmc_connect { my $status = shift; my $sessdata = shift; my $command = $sessdata->{command}; @cmdargv = @{$sessdata->{extraargs}}; if ($status =~ /ERROR:/) { sendmsg([1,$status],$sessdata->{node}); return; } #ok, detect some common prereqs here, notably: #getdevid if ($command eq "getrvidparms") { unless (defined $sessdata->{device_id}) { $sessdata->{ipmisession}->subcmd(netfn=>6,command=>1,data=>[],callback=>\&gotdevid,callback_args=>$sessdata); return; } getrvidparms($sessdata); } #initsdr if ($command eq "rinv" or $command eq "reventlog" or $command eq "rvitals") { unless (defined $sessdata->{device_id}) { $sessdata->{ipmisession}->subcmd(netfn=>6,command=>1,data=>[],callback=>\&gotdevid,callback_args=>$sessdata); return; } unless ($sessdata->{sdr_hash}) { initsdr($sessdata); return; } } if($command eq "ping") { sendmsg("ping",$sessdata->{node}); return; } if ($command eq "rpower") { return power($sessdata); } elsif ($command eq "rspreset") { return resetbmc($sessdata); } elsif($command eq "rbeacon") { return beacon($sessdata); } elsif($command eq "rsetboot") { return setboot($sessdata); } elsif($command eq "rspconfig") { shift @{$sessdata->{extraargs}}; if ($sessdata->{subcommand} =~ /=/) { setnetinfo($sessdata); } else { getnetinfo($sessdata); } } elsif($command eq "rvitals") { vitals($sessdata); } return; my @output; my $rc; #in for testing, evaluated as a TODO my $text; my $error; my $node; my $subcommand = ""; if($command eq "rvitals") { ($rc,@output) = vitals($subcommand); } elsif($command eq "renergy") { ($rc,@output) = renergy($subcommand); } elsif($command eq "rspreset") { ($rc,@output) = resetbmc(); $noclose=1; } elsif($command eq "reventlog") { if($subcommand eq "decodealert") { ($rc,$text) = decodealert(@_); } else { ($rc,@output) = eventlog($subcommand); } } elsif($command eq "rinv") { ($rc,@output) = inv($subcommand); } elsif($command eq "fru") { ($rc,@output) = fru($subcommand); } elsif($command eq "rgetnetinfo") { my @subcommands = ($subcommand); if($subcommand eq "all") { @subcommands = ( "ip", "netmask", "gateway", "backupgateway", "snmpdest1", "snmpdest2", "snmpdest3", "snmpdest4", "community", ); my @coutput; foreach(@subcommands) { $subcommand = $_; ($rc,@output) = getnetinfo($subcommand); push(@coutput,@output); } @output = @coutput; } else { ($rc,@output) = getnetinfo($subcommand); } } elsif($command eq "generic") { ($rc,@output) = generic($subcommand); } elsif($command eq "rfrurewrite") { ($rc,@output) = writefru($subcommand,shift); } elsif($command eq "fru") { ($rc,@output) = fru($subcommand); } elsif($command eq "rsetboot") { ($rc,@output) = setboot($subcommand); } else { $rc = 1; $text = "unsupported command $command $subcommand"; } if($debug) { print "$node: command completed\n"; } if($text) { push(@output,$text); } return($rc,@output); } sub resetbmc { my $sessdata = shift; $sessdata->{ipmisession}->subcmd(netfn=>6,command=>2,data=>[],callback=>\&resetedbmc,callback_args=>$sessdata); } sub resetedbmc { my $rsp = shift; my $sessdata = shift; if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); } else { if ($rsp->{code}) { if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}],$sessdata->{node}); } else { sendmsg([1,sprintf("Unknown error %02xh",$rsp->{code})],$sessdata->{node}); } return; } sendmsg("BMC reset",$sessdata->{node}); $sessdata->{ipmisession} = undef; #throw away now unusable session } } sub setnetinfo { my $sessdata = shift; my $subcommand = $sessdata->{subcommand}; 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; my $channel_number = $sessdata->{ipmisession}->{currentchannel}; if($subcommand eq "snmpdest") { $subcommand = "snmpdest1"; } unless(defined($argument)) { return 0; } if ($subcommand eq "thermprofile") { return idpxthermprofile($argument); } if ($subcommand eq "alert" and $argument eq "on" or $argument =~ /^en/ or $argument =~ /^enable/) { $netfun = 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); $dstip = inet_ntoa(inet_aton($dstip)); my @dip = split /\./, $dstip; @cmd = (0x01,$channel_number,0x13,$1,0x00,0x00,$dip[0],$dip[1],$dip[2],$dip[3],0,0,0,0,0,0); } #elsif($subcommand 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"); } my $command = shift @cmd; $sessdata->{ipmisession}->subcmd(netfn=>0x0c,command=>$command,data=>\@cmd,callback=>\&netinfo_set,callback_args=>$sessdata); } sub netinfo_set { my $rsp = shift; my $sessdata = shift; if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); return; } if ($rsp->{code}) { if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}],$sessdata->{node}); } else { sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$sessdata->{node}); } return; } getnetinfo($sessdata); return; } sub getnetinfo { my $sessdata = shift; my $subcommand = $sessdata->{subcommand}; my $channel_number = $sessdata->{ipmisession}->{currentchannel}; $subcommand =~ s/=.*//; if ($subcommand eq "thermprofile") { my $code; my @returnd; my $thermdata; my $netfun=0x2e<<2; #currently combined netfun & lun, to be simplified later my @cmd = (0x41,0x4d,0x4f,0x00,0x6f,0xff,0x61,0x00); my @bytes; my $error = docmd($netfun,\@cmd,\@bytes); @bytes=splice @bytes,16; my $validprofiles=""; foreach (keys %idpxthermprofiles) { if (sprintf("%02x %02x %02x %02x %02x %02x %02x",@bytes) eq sprintf("%02x %02x %02x %02x %02x %02x %02x",@{$idpxthermprofiles{$_}})) { $validprofiles.="$_,"; } } if ($validprofiles) { chop($validprofiles); return (0,"The following thermal profiles are in effect: ".$validprofiles); } return (1,sprintf("Unable to identify current thermal profile: \"%02x %02x %02x %02x %02x %02x %02x\"",@bytes)); } my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; if ($subcommand eq "snmpdest") { $subcommand = "snmpdest1"; } my $netfun = 0x0c; if ($subcommand eq "alert") { $netfun = 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"); } my $command = shift @cmd; $sessdata->{ipmisession}->subcmd(netfn=>$netfun,command=>$command,data=>\@cmd,callback=>\&getnetinfo_response,callback_args=>$sessdata); } sub getnetinfo_response { my $rsp = shift; my $sessdata = shift; my $subcommand = $sessdata->{subcommand}; $sessdata->{subcommand} = shift @{$sessdata->{extraargs}}; if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); return; } if ($rsp->{code}) { if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}],$sessdata->{node}); } else { sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$sessdata->{node}); } return; } if ($subcommand eq "snmpdest") { $subcommand = "snmpdest1"; } my @returnd = (0,@{$rsp->{data}}); my $format = "%-25s"; if($subcommand eq "garp") { my $code = $returnd[2] / 2; sendmsg(sprintf("$format %d","Gratuitous ARP seconds:",$code),$sessdata->{node}); } elsif($subcommand eq "alert") { if ($returnd[3] & 0x8) { sendmsg("SP Alerting: enabled",$sessdata->{node}); } else { sendmsg("SP Alerting: disabled",$sessdata->{node}); } } elsif($subcommand =~ m/^snmpdest(\d+)/ ) { sendmsg(sprintf("$format %d.%d.%d.%d", "SP SNMP Destination $1:", $returnd[5], $returnd[6], $returnd[7], $returnd[8]),$sessdata->{node}); } elsif($subcommand eq "ip") { sendmsg(sprintf("$format %d.%d.%d.%d", "BMC IP:", $returnd[2], $returnd[3], $returnd[4], $returnd[5]),$sessdata->{node}); } elsif($subcommand eq "netmask") { sendmsg(sprintf("$format %d.%d.%d.%d", "BMC Netmask:", $returnd[2], $returnd[3], $returnd[4], $returnd[5]),$sessdata->{node}); } elsif($subcommand eq "gateway") { sendmsg(sprintf("$format %d.%d.%d.%d", "BMC Gateway:", $returnd[2], $returnd[3], $returnd[4], $returnd[5]),$sessdata->{node}); } elsif($subcommand eq "backupgateway") { sendmsg(sprintf("$format %d.%d.%d.%d", "BMC Backup Gateway:", $returnd[2], $returnd[3], $returnd[4], $returnd[5]),$sessdata->{node}); } elsif ($subcommand eq "community") { my $text = sprintf("$format ","SP SNMP Community:"); my $l = 2; while ($returnd[$l] ne 0) { $l = $l + 1; } my $i=2; while ($i<$l) { $text = $text . sprintf("%c",$returnd[$i]); $i = $i + 1; } sendmsg($text,$sessdata->{node}); } if ($sessdata->{subcommand}) { if ($sessdata->{subcommand} =~ /=/) { setnetinfo($sessdata); } else { getnetinfo($sessdata); } } return; } sub setboot { my $sessdata = shift; #This disables the 60 second timer $sessdata->{ipmisession}->subcmd(netfn=>0,command=>8,data=>[3,8],callback=>\&setboot_timerdisabled,callback_args=>$sessdata); } sub setboot_timerdisabled { my $rsp = shift; my $sessdata = shift; my $subcommand=$sessdata->{subcommand}; if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); return; } if ($rsp->{code}) { if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}],$sessdata->{node}); } else { sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$sessdata->{node}); } return; } my $error; my @cmd; if ($subcommand eq "net") { @cmd=(0x5,0x80,0x4,0x0,0x0,0x0); } elsif ($subcommand eq "hd" ) { @cmd=(0x5,0x80,0x8,0x0,0x0,0x0); } elsif ($subcommand eq "cd" ) { @cmd=(0x5,0x80,0x14,0x0,0x0,0x0); } elsif ($subcommand eq "floppy" ) { @cmd=(0x5,0x80,0x3c,0x0,0x0,0x0); } elsif ($subcommand =~ m/^def/) { @cmd=(0x5,0x0,0x0,0x0,0x0,0x0); } elsif ($subcommand eq "setup" ) { #Not supported by BMCs I've checked so far.. @cmd=(0x5,0x18,0x0,0x0,0x0,0x0); } elsif ($subcommand =~ m/^stat/) { setboot_stat("NOQUERY",$sessdata); return; } else { sendmsg([1,"unsupported command setboot $subcommand"],$sessdata->{node}); } $sessdata->{ipmisession}->subcmd(netfn=>0,command=>8,data=>\@cmd,callback=>\&setboot_stat,callback_args=>$sessdata); } sub setboot_stat { my $rsp = shift; my $sessdata = shift; if (ref $rsp) { if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); } elsif ($rsp->{code}) { if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}],$sessdata->{node}); } else { sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$sessdata->{node}); } return; } } $sessdata->{ipmisession}->subcmd(netfn=>0,command=>9,data=>[5,0,0],callback=>\&setboot_gotstat,callback_args=>$sessdata); } sub setboot_gotstat { my $rsp = shift; my $sessdata = shift; if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); } elsif ($rsp->{code}) { if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}],$sessdata->{node}); } else { sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$sessdata->{node}); } return; } my %bootchoices = ( 0 => 'BIOS default', 1 => 'Network', 2 => 'Hard Drive', 5 => 'CD/DVD', 6 => 'BIOS Setup', 15 => 'Floppy' ); my @returnd = ($rsp->{code},@{$rsp->{data}}); unless ($returnd[3] & 0x80) { sendmsg("boot override inactive",$sessdata->{node}); return; } my $boot=($returnd[4] & 0x3C) >> 2; sendmsg($bootchoices{$boot},$sessdata->{node}); return; } sub idpxthermprofile { #iDataplex thermal profiles as of 6/10/2008 my $subcommand = lc(shift); my @returnd; my $netfun = 0xb8; my @cmd = (0x41,0x4d,0x4f,0x00,0x6f,0xfe,0x60,0,0,0,0,0,0,0,0xff); if ($idpxthermprofiles{$subcommand}) { push @cmd,@{$idpxthermprofiles{$subcommand}}; } else { return (1,"Not an understood thermal profile, expected a 2 hex digit value corresponding to chassis label on iDataplex server"); } docmd( $netfun, \@cmd, \@returnd ); return (0,"OK"); } sub getrvidparms { my $sessdata = shift; unless ($sessdata) { die "not fixed yet" } #check devide id unless ($sessdata->{mfg_id} == 2) { #Only implemented for IBM servers sendmsg([1,"Remote video is not supported on this system"],$sessdata->{node}); return; } #TODO: use get bmc capabilities to see if rvid is actually supported before bothering the client java app print "my turn\n"; $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x50,data=>[],callback=>\&getrvidparms_with_buildid,callback_args=>$sessdata); } sub check_rsp_errors { #TODO: pass in command-specfic error code translation table my $rsp = shift; my $sessdata = shift; if($rsp->{error}) { #non ipmi error sendmsg([1,$rsp->{error}],$sessdata->{node}); return 1; } if ($rsp->{code}) { #ipmi error if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}]); } else { sendmsg([1,sprintf("Unknown error code %02xh",$rsp->{code})],$sessdata->{node}); } return 1; } return 0; } sub getrvidparms_with_buildid { if (check_rsp_errors(@_)) { return; } my $rsp = shift; my $sessdata = shift; my @build_id = (0,@{$rsp->{data}}); 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 sendmsg([1,"Remote video is not supported on this system"],$sessdata->{node}); return; } #wvid should be a possiblity, time to do the http... my $browser = LWP::UserAgent->new(); my $message = $sessdata->{ipmisession}->{userid}.",".$sessdata->{ipmisession}->{password}; $browser->cookie_jar({}); my $baseurl = "http://".$sessdata->{ipmisession}->{bmc}."/"; my $response = $browser->request(POST $baseurl."/session/create",'Content-Type'=>"text/xml",Content=>$message); unless ($response->content eq "ok") { sendmsg ([1,"Server returned unexpected data"],$sessdata->{node}); return; } $response = $browser->request(GET $baseurl."/kvm/kvm/jnlp"); my $jnlp = $response->content; if ($jnlp =~ /This advanced option requires the purchase and installation/) { sendmsg ([1,"Node does not have feature key for remote video"],$sessdata->{node}); } my $currnode = $sessdata->{node}; $jnlp =~ s!argument>title=.*Video Viewer!argument>title=$currnode wvid!; sendmsg("method:imm",$sessdata->{node}); sendmsg("jnlp:$jnlp",$sessdata->{node}); print "jnlp:$jnlp"; my @cmdargv = @{$sessdata->{extraargs}}; if (grep /-m/,@cmdargv) { $response = $browser->request(GET $baseurl."/kvm/vm/jnlp"); sendmsg("mediajnlp:".$response->content,$sessdata->{node});; } return; } sub power { my $sessdata = shift; my $netfun = 0x00; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; $sessdata->{ipmisession}->subcmd(netfn=>0,command=>1,data=>[],callback=>\&power_with_context,callback_args=>$sessdata); } sub power_with_context { my $rsp = shift; my $sessdata = shift; my $text=""; if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); return; } if ($rsp->{code} != 0) { $text = $codes{$rsp->{code}}; unless ($text) { $text = sprintf("Unknown error code %02xh",$rsp->{code}); } sendmsg([1,$text],$sessdata->{node}); return; } $sessdata->{powerstatus} = ($rsp->{data}->[0] & 1 ? "on" : "off"); if ($sessdata->{subcommand} eq "stat" or $sessdata->{subcommand} eq "state" or $sessdata->{subcommand} eq "status") { sendmsg($sessdata->{powerstatus},$sessdata->{node}); return; } my $subcommand = $sessdata->{subcommand}; if ($sessdata->{subcommand} eq "boot") { $text = $sessdata->{powerstatus}. " "; $subcommand = ($sessdata->{powerstatus} eq "on" ? "reset" : "on"); $sessdata->{subcommand}=$subcommand; #lazy typing.. } my %argmap = ( #english to ipmi dictionary "on" => 1, "off" => 2, "softoff" => 5, "reset" => 3, "nmi" => 4 ); if($subcommand eq "on") { if ($sessdata->{powerstatus} eq "on") { sendmsg("on $status_noop",$sessdata->{node}); return; # don't bother sending command } } elsif ($subcommand eq "softoff" or $subcommand eq "off" or $subcommand eq "reset") { if ($sessdata->{powerstatus} eq "off") { sendmsg("off $status_noop",$sessdata->{node}); return; } } elsif (not $argmap{$subcommand}) { sendmsg([1,"unsupported command power $subcommand"]); return; } $sessdata->{ipmisession}->subcmd(netfn=>0,command=>2,data=>[$argmap{$subcommand}],callback=>\&power_response,callback_args=>$sessdata); } sub power_response { my $rsp = shift; my $sessdata = shift; my @returnd = ($rsp->{code},@{$rsp->{data}}); if($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); return; } if ($rsp->{code}) { my $text = $codes{$rsp->{code}}; unless ($text) { $text = sprintf("Unknown response %02xh",$rsp->{code}); } sendmsg([1,$text],$sessdata->{node}); } sendmsg($sessdata->{subcommand},$sessdata->{node}); } sub generic { my $subcommand = shift; my $netfun; my @args; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; ($netfun,@args) = split(/-/,$subcommand); $netfun=oct($netfun); printf("netfun: 0x%02x\n",$netfun); print "command: "; foreach(@args) { push(@cmd,oct($_)); printf("0x%02x ",oct($_)); } print "\n\n"; $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; } $code = $returnd[0]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } printf("return code: 0x%02x\n\n",$code); print "return data:\n"; my @rdata = @returnd[1..@returnd-2]; hexadump(\@rdata); print "\n"; print "full output:\n"; hexadump(\@returnd); print "\n"; # if(!$text) { # $rc = 1; # $text = sprintf("unknown response %02x",$code); # } return($rc,$text); } sub beacon { my $sessdata = shift; my $subcommand = $sessdata->{subcommand}; my $ipmiv2=0; if ($sessdata->{ipmisession}->{ipmiversion} eq '2.0') { $ipmiv2 = 1; } if($subcommand ne "on" and $subcommand ne "off"){ sendmsg([1,"please specify on or off for ipmi nodes (stat impossible)"],$sessdata->{node}); } #if stuck with 1.5, say light for 255 seconds. In 2.0, specify to turn it on forever if($subcommand eq "on") { if ($ipmiv2) { $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0,1],callback=>\&beacon_answer,callback_args=>$sessdata); } else { $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0xff],callback=>\&beacon_answer,callback_args=>$sessdata); } } elsif($subcommand eq "off") { if ($ipmiv2) { $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0,0],callback=>\&beacon_answer,callback_args=>$sessdata); } else { $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0x0],callback=>\&beacon_answer,callback_args=>$sessdata); } } else { return; } } sub beacon_answer { my $rsp = shift; my $sessdata = shift; if($rsp->{error}) { #non ipmi error sendmsg([1,$rsp->{error}],$sessdata->{node}); return; } if ($rsp->{code}) { #ipmi error if ($codes{$rsp->{code}}) { sendmsg([1,$codes{$rsp->{code}}]); } else { sendmsg([1,sprintf("Unknown error code %02xh",$rsp->{code})],$sessdata->{node}); } return; } sendmsg($sessdata->{subcommand},$sessdata->{node}); } sub inv { my $subcommand = shift; my $rc = 0; my $text; my @output; my @types; my $format = "%-20s %s"; ($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[0]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my @oem_fru_data = @returnd[1..@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[0]; 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[1..@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 $sessdata = shift; unless ($sessdata) { die "not fixed yet"; } 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; #TODO: device id needed here @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[0] == 0) { my @a = ($fw_rev2); my @b= @lreturnd[1 .. $#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); } 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; my %sdr_hash = %{$sessdata->{sdr_hash}}; 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); pop @bytes; unless (defined $bytes[0] and $bytes[0] == 0) { if ($codes{$bytes[0]}) { return (0,$codes{$bytes[0]}); } return (0,"FRU device $fruid inaccessible"); } return ($bytes[2]<<8)+$bytes[1]; } sub formfru { my $fruhash = shift; my $frusize = shift; $frusize-=8; #consume 8 bytes for mandatory header my $availindex=1; my @bytes=(1,0,0,0,0,0,0,0); # if ($fruhash->{internal}) { #Allocate the space at header time $bytes[1]=$availindex; $availindex+=ceil((scalar @{$fruhash->{internal}})/8); $frusize-=(scalar @{$fruhash->{internal}}); #consume internal bytes push @bytes,@{$fruhash->{internal}}; } if ($fruhash->{chassis}) { $bytes[2]=$availindex; push @bytes,@{$fruhash->{chassis}->{raw}}; $availindex+=ceil((scalar @{$fruhash->{chassis}->{raw}})/8); $frusize -= ceil((scalar @{$fruhash->{chassis}->{raw}})/8)*8; } if ($fruhash->{board}) { $bytes[3]=$availindex; push @bytes,@{$fruhash->{board}->{raw}}; $availindex+=ceil((scalar @{$fruhash->{board}->{raw}})/8); $frusize -= ceil((scalar @{$fruhash->{board}->{raw}})/8)*8; } #xCAT will always have a product FRU in this process $bytes[4]=$availindex; unless (defined $fruhash->{product}) { #Make sure there is a data structure #to latch onto.. $fruhash->{product}={}; } my @prodbytes = buildprodfru($fruhash->{product}); push @bytes,@prodbytes; $availindex+=ceil((scalar @prodbytes)/8); $frusize -= ceil((scalar @prodbytes)/8)*8;; #End of product fru setup if ($fruhash->{extra}) { $bytes[5]=$availindex; push @bytes,@{$fruhash->{extra}}; $frusize -= ceil((scalar @{$fruhash->{extra}})/8)*8; #Don't need to track availindex anymore } $bytes[7] = dochksum([@bytes[0..6]]); if ($frusize<0) { return undef; } else { return \@bytes; } } sub transfieldtobytes { my $hashref=shift; unless (defined $hashref) { return (0xC0); } my @data; my $size; if ($hashref->{encoding} ==3) { @data=unpack("C*",$hashref->{value}); } else { @data=@{$hashref->{value}}; } $size=scalar(@data); if ($size > 64) { die "Field too large for IPMI FRU specification"; } unshift(@data,$size|($hashref->{encoding}<<6)); return @data; } sub mergefru { my $sessdata = shift; my $phash = shift; #Product hash unless ($phash) { die "here" } my $currnode = $sessdata->{node}; if ($vpdhash->{$currnode}->[0]->{mtm}) { $phash->{model}->{encoding}=3; $phash->{model}->{value}=$vpdhash->{$currnode}->[0]->{mtm}; } if ($vpdhash->{$currnode}->[0]->{serial}) { $phash->{serialnumber}->{encoding}=3; $phash->{serialnumber}->{value}=$vpdhash->{$currnode}->[0]->{serial}; } if ($vpdhash->{$currnode}->[0]->{asset}) { $phash->{asset}->{encoding}=3; $phash->{asset}->{value}=$vpdhash->{$currnode}->[0]->{asset}; } } sub buildprodfru { my $sessdata = shift; my $prod=shift; mergefru($sessdata,$prod); my $currnode = $sessdata->{node}; my @bytes=(1,0,0); my @data; my $padsize; push @bytes,transfieldtobytes($prod->{manufacturer}); push @bytes,transfieldtobytes($prod->{product}); push @bytes,transfieldtobytes($prod->{model}); push @bytes,transfieldtobytes($prod->{version}); push @bytes,transfieldtobytes($prod->{serialnumber}); push @bytes,transfieldtobytes($prod->{asset}); push @bytes,transfieldtobytes($prod->{fruid}); push @bytes,transfieldtobytes($prod->{fruid}); foreach (@{$prod->{extra}}) { my $sig=getascii(transfieldtobytes($_)); unless ($sig and $sig =~ /FRU by xCAT/) { push @bytes,transfieldtobytes($_); } } push @bytes,transfieldtobytes({encoding=>3,value=>"$currnode FRU by xCAT ".xCAT::Utils::Version('short')}); push @bytes,(0xc1); $bytes[1]=ceil((scalar(@bytes)+1)/8); $padsize=(ceil((scalar(@bytes)+1)/8)*8)-scalar(@bytes)-1; while ($padsize--) { push @bytes,(0x00); } $padsize=dochksum(\@bytes);#reuse padsize for a second to store checksum push @bytes,$padsize; return @bytes; } sub fru { my $subcommand = shift; my $netfun = 0x28; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my @output; my $code; @cmd=(0x10,0x00); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[0]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my $fru_size_ls = $returnd[1]; my $fru_size_ms = $returnd[2]; my $fru_size = $fru_size_ms*256 + $fru_size_ls; if($subcommand eq "dump") { print "FRU Size: $fru_size\n"; my ($rc,@output) = frudump(0,$fru_size,8); if($rc) { return($rc,@output); } hexadump(\@output); return(0,""); } if($subcommand eq "wipe") { my @bytes = (); for(my $i = 0;$i < $fru_size;$i++) { push(@bytes,0xff); } my ($rc,$text) = fruwrite(0,\@bytes,8); if($rc) { return($rc,$text); } return(0,"FRU $fru_size bytes wiped"); } return(0,""); } sub 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[0]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my $count = $returnd[1]; if($count != $reqsize) { $rc = 1; $text = "FRU read error (bytes requested: $reqsize, got: $count)"; return($rc,$text); } my @data = @returnd[2..@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); pop @bytes; unless (defined $bytes[0] and $bytes[0] == 0) { return (1,"FRU device 0 inaccessible"); } my $frusize=($bytes[2]<<8)+$bytes[1]; ($error,@bytes) = frudump(0,$frusize,16); if ($error) { return (1,"Error retrieving FRU: ".$error); } my $fruhash; ($error,$fruhash) = parsefru(\@bytes); my $newfru=formfru($fruhash,$frusize); unless ($newfru) { return (1,"FRU data will not fit in BMC FRU space, fields too long"); } my $rc=1; my $writeattempts=0; my $text; while ($rc and $writeattempts<15) { if ($writeattempts) { sleep 1; } ($rc,$text) = fruwrite(0,$newfru,8); if ($text =~ /rotected/) { last; } $writeattempts++; } if($rc) { return($rc,$text); } return(0,"FRU Updated"); } sub fruwrite { my $offset = shift; my $bytes = shift; my $chunk = shift; my $length = @$bytes; my $netfun = 0x28; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my @output; my $code; my @fru_data=(); for(my $c=$offset;$c < $length+$offset;$c += $chunk) { my $ms = int($c / 0x100); my $ls = $c - $ms * 0x100; @cmd=(0x12,0x00,$ls,$ms,@$bytes[$c-$offset..$c-$offset+$chunk-1]); $error = docmd( $netfun, \@cmd, \@returnd ); if($error) { $rc = 1; $text = $error; return($rc,$text); } $code = $returnd[0]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if ($code == 0x80) { $text = "Write protected FRU"; } if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my $count = $returnd[1]; if($count != $chunk) { $rc = 1; $text = "FRU write error (bytes requested: $chunk, wrote: $count)"; return($rc,$text); } } return(0); } sub decodealert { my $sessdata = shift; unless ($sessdata) { die "not fixed yet"; } 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; my $type; my $desc; #my $ipmisensoreventtab = "$ENV{XCATROOT}/lib/GUMI/ipmisensorevent.tab"; #my $ipmigenericeventtab = "$ENV{XCATROOT}/lib/GUMI/ipmigenericevent.tab"; my $offsetmask = 0b00000000000000000000000000001111; my $offsetrmask = 0b00000000000000000000000001110000; my $assertionmask = 0b00000000000000000000000010000000; my $eventtypemask = 0b00000000000000001111111100000000; my $sensortypemask = 0b00000000111111110000000000000000; my $reservedmask = 0b11111111000000000000000000000000; my $offset = $trap & $offsetmask; my $offsetr = $trap & $offsetrmask; my $event_dir = $trap & $assertionmask; my $event_type = ($trap & $eventtypemask) >> 8; my $sensor_type = ($trap & $sensortypemask) >> 16; my $reserved = ($trap & $reservedmask) >> 24; if($debug >= 2) { printf("offset: %02xh\n",$offset); printf("offsetr: %02xh\n",$offsetr); printf("assertion: %02xh\n",$event_dir); printf("eventtype: %02xh\n",$event_type); printf("sensortype: %02xh\n",$sensor_type); printf("reserved: %02xh\n",$reserved); } my @hex = (0,@pet); my $pad = $hex[0]; my @uuid = @hex[1..16]; my @seqnum = @hex[17,18]; my @timestamp = @hex[19,20,21,22]; my @utcoffset = @hex[23,24]; my $trap_source_type = $hex[25]; my $event_source_type = $hex[26]; my $sev = $hex[27]; my $sensor_device = $hex[28]; my $sensor_num = $hex[29]; my $entity_id = $hex[30]; my $entity_instance = $hex[31]; my $event_data_1 = $hex[32]; my $event_data_2 = $hex[33]; my $event_data_3 = $hex[34]; my @event_data = @hex[35..39]; my $langcode = $hex[40]; my $mfg_id = $hex[41] + $hex[42] * 0x100 + $hex[43] * 0x10000 + $hex[44] * 0x1000000; my $prod_id = $hex[45] + $hex[46] * 0x100; my @oem = $hex[47..@hex-1]; if($sev == 0x00) { $sev = "LOG"; } elsif($sev == 0x01) { $sev = "MONITOR"; } elsif($sev == 0x02) { $sev = "INFORMATION"; } elsif($sev == 0x04) { $sev = "OK"; } elsif($sev == 0x08) { $sev = "WARNING"; } elsif($sev == 0x10) { $sev = "CRITICAL"; } elsif($sev == 0x20) { $sev = "NON-RECOVERABLE"; } else { $sev = "UNKNOWN-SEVERITY:$sev"; } $text = "$sev:"; ($rc,$type,$desc) = getsensorevent($sensor_type,$offset,"ipmisensorevents"); if($rc == 1) { $type = "Unknown Type $sensor_type"; $desc = "Unknown Event $offset"; $rc = 0; } if($event_type <= 0x0c) { my $gtype; my $gdesc; ($rc,$gtype,$gdesc) = getsensorevent($event_type,$offset,"ipmigenericevents"); if($rc == 1) { $gtype = "Unknown Type $gtype"; $gdesc = "Unknown Event $offset"; $rc = 0; } $desc = $gdesc; } if($type eq "" || $type eq "-") { $type = "OEM Sensor Type $sensor_type" } if($desc eq "" || $desc eq "-") { $desc = "OEM Sensor Event $offset" } if($type eq $desc) { $desc = ""; } my $extra_info = getaddsensorevent($sensor_type,$offset,$event_data_1,$event_data_2,$event_data_3); if($extra_info) { if($desc) { $desc = "$desc $extra_info"; } else { $desc = "$extra_info"; } } $text = "$text $type,"; $text = "$text $desc"; my $key; my $sensor_desc = sprintf("Sensor 0x%02x",$sensor_num); my %sdr_hash = %{$sessdata->{sdr_hash}}; foreach $key (keys %sdr_hash) { my $sdr = $sdr_hash{$key}; if($sdr->sensor_number == $sensor_num) { $sensor_desc = $sdr_hash{$key}->id_string; if($sdr->rec_type == 0x01) { last; } } } $text = "$text ($sensor_desc)"; if($event_dir) { $text = "$text - Recovered"; } return(0,$text); } sub readauxentry { my $netfn=0x2e<<2; my $entrynum = shift; my $entryls = ($entrynum&0xff); my $entryms = ($entrynum>>8); my @cmd = (0x93,0x4d,0x4f,0x00,$entryls,$entryms,0,0,0xff,0x5); #Get log size andup to 1275 bytes of data, keeping it under 1500 to accomodate mixed-mtu circumstances my @data; my $error = docmd( $netfn, \@cmd, \@data ); if ($error) { return $error; } if ($data[0]) { return $data[0]; } my $text; unless ($data[1] == 0x4d and $data[2] == 0x4f and $data[3] == 0) { return "Unrecognized response format" } $entrynum=$data[6]+($data[7]<<8); if (($data[10]&1) == 1) { $text="POSSIBLY INCOMPLETE DATA FOLLOWS:\n"; } my $addtext=""; if ($data[5] > 5) { $addtext="\nTODO:SUPPORT MORE DATA THAT WAS SEEN HERE"; } @data = splice @data,11; pop @data; while(scalar(@data)) { my @subdata = splice @data,0,30; my $numbytes = scalar(@subdata); my $formatstring="%02x"x$numbytes; $formatstring =~ s/%02x%02x/%02x%02x /g; $text.=sprintf($formatstring."\n",@subdata); } $text.=$addtext; return (0,$entrynum,$text); } sub eventlog { my $subcommand = shift; my $sessdata = shift; unless ($sessdata) { die "not fixed yet" } 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; #device id needed here $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[4]<<24 | $returnd[3]<<16 | $returnd[2]<<8 | $returnd[1]; 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[0]; 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[1]; 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[3]*256 + $returnd[2]; if($num_entries <= 0) { $rc = 1; $text = "no SEL entries"; return($rc,$text); } my $canres = $returnd[14] & 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[0]; 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[1]; $res_id_ms = $returnd[2]; } 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); #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[0]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } my $erase_status = $returnd[1] & 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[0]; if($code == 0x00) { } else { $rc = 1; $text = $codes{$code}; } if($rc != 0) { if(!$text) { $text = sprintf("unknown response %02x",$code); } return($rc,$text); } $erase_status = $returnd[1] & 0b00000001; } $text = "SEL cleared"; 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[0]; 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[1]; my $next_rec_ms = $returnd[2]; my @sel_data = @returnd[3..19]; @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; # } # } my %sdr_hash = %{$sessdata->{sdr_hash}}; if(defined $sdr_hash{$key}) { $sensor_desc = $sdr_hash{$key}->id_string; if ($sdr_hash{$key}->event_type_code == 1) { if (($event_data_1 & 0b11000000) == 0b01000000) { $sensor_desc .= " reading ".translate_sensor($event_data_2,$sdr_hash{$key}); if (($event_data_1 & 0b00110000) == 0b00010000) { $sensor_desc .= " with threshold " . translate_sensor($event_data_3,$sdr_hash{$key}); } } } } $text = "$text ($sensor_desc)"; if($event_dir) { $text = "$text - Recovered"; } 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 initiem { my $iem = IBM::EnergyManager->new(); my @payload = $iem->get_next_payload(); my $netfun = shift @payload; my @returnd; my $error = docmd( $netfun<<2, \@payload, \@returnd ); $iem->handle_next_payload(@returnd); return $iem; } sub readenergy { unless ($iem_support) { return (1,"IBM::EnergyManager package required for this value"); } my @entries; my $iem = initiem(); my $entry; $iem->prep_get_ac_energy(); $entry = process_data_from_iem($iem); $iem->prep_get_precision(); execute_iem_commands($iem); #this gets all precision data initialized $entry .= sprintf(" +/-%.1f%%",$iem->energy_ac_precision()*0.1); #note while \x{B1} would be cool, it's non-trivial to support push @entries,$entry; $iem->prep_get_dc_energy(); $entry = process_data_from_iem($iem); $entry .= sprintf(" +/-%.1f%%",$iem->energy_dc_precision()*0.1); push @entries,$entry; return (0,@entries); } sub execute_iem_commands { my $iem = shift; my @payload = $iem->get_next_payload(); my @returnd; while (scalar @payload) { my $netfun = shift @payload; my $error = docmd($netfun<<2,\@payload,\@returnd); if ($error) { return $error; } $iem->handle_next_payload(@returnd); @payload = $iem->get_next_payload(); } return 0; } sub process_data_from_iem { my $iem = shift; my @returnd; my @iemdata; if (!execute_iem_commands($iem)) { @iemdata = $iem->extract_data; } my $label = shift @iemdata; my $units = shift @iemdata; my $value=0; my $shift=0; while (scalar @iemdata) { #stuff the 64-bits of data into an int, would break in 32 bit $value+=pop(@iemdata)<<$shift; #$value.=sprintf("%02x ",shift @iemdata); $shift+=8; } if ($units eq "mJ") { $units = "kWh"; $value = $value / 3600000000; return sprintf("$label: %.4f $units",$value); } elsif ($units eq "mW") { $units = "W"; $value = $value / 1000.0; return sprintf("$label: %.1f $units",$value); } } sub checkleds { my $sessdata = shift; 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=$sessdata->{mfg_id}; #TODO device id if ($mfg_id != 2) { sendmsg("LED status not supported on this system",$sessdata->{node}); return; } my %sdr_hash = %{$sessdata->{sdr_hash}}; $sessdata->{doleds} = []; foreach $key (sort {$sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string} keys %sdr_hash) { my $sdr = $sdr_hash{$key}; if($sdr->rec_type == 0xC0 && $sdr->sensor_type == 0xED) { #this stuff is to help me build the file from spec paste #my $tehstr=sprintf("grep 0x%04X /opt/xcat/lib/x3755led.tab",$sdr->led_id); #my $tehstr=`$tehstr`; #$tehstr =~ s/^0x....//; #printf("%X.%X.0x%04x",$mfg_id,$prod_id,$sdr->led_id); #print $tehstr; #We are inconsistant in our spec, first try a best guess #at endianness, assume the smaller value is MSB if (($sdr->led_id&0xff) > ($sdr->led_id>>8)) { $led_id_ls=$sdr->led_id&0xff; $led_id_ms=$sdr->led_id>>8; } else { $led_id_ls=$sdr->led_id>>8; $led_id_ms=$sdr->led_id&0xff; } push @{$sessdata->{doleds}},[$led_id_ms,$led_id_ls,$sdr]; } } $sessdata->{doled} = shift @{$sessdata->{doleds}}; if ($sessdata->{doled}) { $sessdata->{current_led_sdr} = pop @{$sessdata->{doled}}; $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc0,data=>$sessdata->{doled},callback=>\&did_led,callback_args=>$sessdata); } else { sendmsg("No supported LEDs found in system",$sessdata->{node}); } # if ($#output==-1) { # push(@output,"No active error LEDs detected"); # } } sub did_led { my $rsp = $_[0]; my $sessdata = $_[1]; my $mfg_id = $sessdata->{mfg_id}; my $prod_id = $sessdata->{prod_id}; my $sdr = $sessdata->{current_led_sdr}; if (not $sessdata->{ledswappedendian} and $_[0]->{code} == 0xc9) { #missed an endian guess probably $sessdata->{ledswappedendian}=1; my @doled; $doled[0]=$sessdata->{doled}->[1]; $doled[1]=$sessdata->{doled}->[0]; $sessdata->{doled} = \@doled; $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc0,data=>$sessdata->{doled},callback=>\&did_led,callback_args=>$sessdata); return; } elsif ( $_[0]->{code} == 0xc9) { $_[0]->{code} = 0; #TODO: some system actually gives an led locator record that doesn't exist.... print "DEBUG: unfindable LED record\n"; } $sessdata->{ledswappedendian}=0; #reset ledswappedendian flag to allow future swaps if (check_rsp_errors(@_)) { return; } my $led_id_ls = $sessdata->{doled}->[1]; my $led_id_ms = $sessdata->{doled}->[0]; my @returnd = (0,@{$rsp->{data}}); if ($returnd[2]) { # != 0) { #It's on... if ($returnd[6] == 4) { sendmsg(sprintf("BIOS or admininstrator has %s lit",getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds")),$sessdata->{node}); $sessdata->{activeleds}=1; } elsif ($returnd[6] == 3) { sendmsg(sprintf("A user has manually requested LED 0x%04x (%s) be active",$sdr->led_id,getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds")),$sessdata->{node}); $sessdata->{activeleds}=1; } elsif ($returnd[6] == 1 && $sdr->led_id !=0) { sendmsg(sprintf("LED 0x%02x%02x (%s) active to indicate LED 0x%02x%02x (%s) is active",$led_id_ms,$led_id_ls,getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds"),$returnd[4],$returnd[5],getsensorname($mfg_id,$prod_id,($returnd[4]<<8)+$returnd[5],"ibmleds")),$sessdata->{node}); $sessdata->{activeleds}=1; } elsif ($sdr->led_id ==0) { sendmsg(sprintf("LED 0x0000 (%s) active to indicate system error condition.",getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds")),$sessdata->{node}); $sessdata->{activeleds}=1; } elsif ($returnd[6] == 2) { my $sensor_desc; #Ok, LED is tied to a sensor.. my $sensor_num=$returnd[5]; my %sdr_hash = %{$sessdata->{sdr_hash}}; foreach my $key (keys %sdr_hash) { my $osdr = $sdr_hash{$key}; if($osdr->sensor_number == $sensor_num) { $sensor_desc = $sdr_hash{$key}->id_string; if($osdr->rec_type == 0x01) { last; } } } #push(@output,sprintf("Sensor 0x%02x (%s) has activated LED 0x%04x",$sensor_num,$sensor_desc,$sdr->led_id)); sendmsg(sprintf("LED 0x%02x%02x active to indicate Sensor 0x%02x (%s) error.",$led_id_ms,$led_id_ls,$sensor_num,$sensor_desc),$sessdata->{node}); $sessdata->{activeleds}=1; } else { #an LED is on for some other reason print "DEBUG: unknown LED reason code ".$returnd[6]."\n"; #TODO: discern meaning of more 'reason' codes, 5 and ff have come up } } $sessdata->{doled} = shift @{$sessdata->{doleds}}; if ($sessdata->{doled}) { $sessdata->{current_led_sdr} = pop @{$sessdata->{doled}}; $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc0,data=>$sessdata->{doled},callback=>\&did_led,callback_args=>$sessdata); } elsif (not $sessdata->{activeleds}) { sendmsg("No active error LEDs detected",$sessdata->{node}); } if (scalar @{$sessdata->{sensorstoread}}) { $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}}; readsensor($sessdata); #next sensor } } sub renergy { my @subcommands = shift; my @output; my @settable_keys = qw/savingstatus cappingstatus cappingwatt/; unless ($iem_support) { return (1,"Command unsupported without IBM::EnergyManager installed"); } my $iem = initiem(); my @directives=(); foreach (@subcommands) { push @directives,split /,/,$_; } my $directive; my $value; my $key; foreach $directive (@directives) { $value=undef; $key=undef; if ($directive =~ /(.*)=(.*)\z/) { #todo: assigment $key = $1; $value = $2; unless (grep /$key/,@settable_keys and $value) { return (1,"Malformed argument $directive"); } if ($key eq "cappingwatt") { $value = $value*1000; #convert to milliwatts $iem->prep_set_cap($value); execute_iem_commands($iem); #this gets all precision data initialized } if ($key eq "cappingstatus") { if (grep /$value/,qw/enable on 1/) { $value = 1; } else { $value = 0; } $iem->prep_set_capenable($value); execute_iem_commands($iem); #this gets all precision data initialized } } if ($directive =~ /cappingmaxmin/) { my $entry; $iem->prep_get_mincap(); $entry = process_data_from_iem($iem); push @output,$entry; $iem->prep_get_maxcap(); $entry = process_data_from_iem($iem); push @output,$entry; } if ($directive =~ /cappingvalue/) { my $entry; $iem->prep_get_cap(); push @output,process_data_from_iem($iem); } if ($directive =~ /cappingstatus/) { my $entry; $iem->prep_get_powerstatus(); execute_iem_commands($iem); my $capenabled = $iem->capping_enabled(); push @output,"cappingstatus: ".($capenabled ? "on" : "off"); } if ($directive =~ /relhistogram/) { my $entry; $iem->prep_retrieve_histogram(); execute_iem_commands($iem); my @histdata = $iem->extract_relative_histogram; foreach (sort { $a <=> $b } keys %{$histdata[0]}) { push @output,"$_: ".$histdata[0]->{$_}; } } } return (0,@output); } sub vitals { my $sessdata = shift; my %sdr_hash = %{$sessdata->{sdr_hash}}; my @textfilters; foreach (@{$sessdata->{extraargs}}) { push @textfilters,(split /,/,$_); } unless (scalar @textfilters) { @textfilters = ("all"); } my $rc = 0; my $text; my $key; my %sensor_filters=(); my @output; my $reading; my $unitdesc; my $value; my $extext; my $format = "%-30s%8s %-20s"; my $doall; $doall=0; $rc=0; #filters: defined in sensor type codes and data table # 1 == temp, 2 == voltage 3== current (we lump in wattage here for lack of a better spot), 4 == fan if(grep { $_ eq "all"} @textfilters) { $sensor_filters{1}=1; #,0x02,0x03,0x04); rather than filtering, unfiltered results $sensor_filters{energy}=1; $doall=1; } if(grep /temp/,@textfilters) { $sensor_filters{0x01}=1; } if(grep /volt/,@textfilters) { $sensor_filters{0x02}=1; } if(grep /watt/,@textfilters) { $sensor_filters{0x03}=1; } if(grep /fan/,@textfilters) { $sensor_filters{0x04}=1; } if(grep /power/,@textfilters) { #power does not really include energy, but most people use 'power' to mean both $sensor_filters{0x03}=1; $sensor_filters{powerstate}=1; $sensor_filters{energy}=1; } if(grep /energy/,@textfilters) { $sensor_filters{energy}=1; } if(grep /led/,@textfilters) { $sensor_filters{leds}=1; } unless (keys %sensor_filters) { sendmsg([1,"Unrecognized rvitals arguments ".join(" ",@{$sessdata->{extraargs}})],$sessdata->{node});; } $sessdata->{sensorstoread} = []; foreach(keys %sensor_filters) { my $filter = $_; if ($filter eq "energy" or $filter eq "leds") { next; } foreach $key (sort {$sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string} keys %sdr_hash) { my $sdr = $sdr_hash{$key}; if(($doall and not $sdr->rec_type == 0x11 and not $sdr->sensor_type==0xed) or ($sdr->rec_type == 0x01 and $sdr->sensor_type == $filter)) { my $lformat = $format; push @{$sessdata->{sensorstoread}},$sdr; } } } if($sensor_filters{leds}) { push @{$sessdata->{sensorstoread}},"leds"; #my @cleds; #($rc,@cleds) = checkleds(); #push @output,@cleds; } if ($sensor_filters{powerstate}) { push @{$sessdata->{sensorstoread}},"powerstat"; #($rc,$text) = power("stat"); #$text = sprintf($format,"Power Status:",$text,""); #push(@output,$text); } if ($sensor_filters{energy}) { push @{$sessdata->{sensorstoread}},"energy"; #my @energies; #($rc,@energies)=readenergy(); #push @output,@energies; } if (scalar @{$sessdata->{sensorstoread}}) { $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}}; readsensor($sessdata); #and we are off } } sub sensorformat { my $sessdata = shift; my $sdr = $sessdata->{currsdr}; my $rc = shift; my $reading = shift; my $extext = shift; my $unitdesc = ""; my $value; my $lformat = "%-30s %-20s"; my $per = " "; my $data; if($rc == 0) { $data = translate_sensor($reading,$sdr); } else { $data = "N/A"; } #$unitdesc.= sprintf(" %x",$sdr->sensor_type); use Data::Dumper; print Dumper($lformat,$sdr->id_string,$data); my $text = sprintf($lformat,$sdr->id_string . ":",$data); if ($extext) { $text="$text ($extext)"; } sendmsg($text,$sessdata->{node}); if (scalar @{$sessdata->{sensorstoread}}) { $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}}; readsensor($sessdata); #next } } sub readsensor { my $sessdata = shift; if (not ref $sessdata->{currsdr}) { if ($sessdata->{currsdr} eq "leds") { checkleds($sessdata); #my @cleds; #($rc,@cleds) = checkleds(); #push @output,@cleds; } else { sendmsg([1,"TODO: make this work again"],$sessdata->{node}); } return; } my $sensor = $sessdata->{currsdr}->sensor_number; $sessdata->{ipmisession}->subcmd(netfn=>0x4,command=>0x2d,data=>[$sensor],callback=>\&sensor_was_read,callback_args=>$sessdata); } sub sensor_was_read { my $rsp = shift; my $sessdata = shift; if ($rsp->{error}) { sendmsg([1,$rsp->{error}],$sessdata->{node}); } if ($rsp->{code}) { my $text = $codes{$rsp->{code}}; unless ($text) { $text = sprintf("Unknown error %02xh",$rsp->{code}) }; return sensorformat($sessdata,1,$text); } my @returnd = (0,@{$rsp->{data}}); if ($returnd[2] & 0x20) { return sensorformat($sessdata,1,"N/A"); } my $text = $returnd[1]; my $exdata1 = $returnd[3]; my $exdata2 = $returnd[3]; my $extext; my @exparts; my $sdr = $sessdata->{currsdr}; if ($sdr->event_type_code == 0x1) { if ($exdata1 & 1<<5) { $extext = "At or above upper non-recoverable threshold"; } elsif ($exdata1 & 1<<4) { $extext = "At or above upper critical threshold"; } elsif ($exdata1 & 1<<3) { $extext = "At or above upper non-critical threshold"; } if ($exdata1 & 1<<2) { $extext = "At or below lower non-critical threshold"; } elsif ($exdata1 & 1<<1) { $extext = "At or below lower critical threshold"; } elsif ($exdata1 & 1) { $extext = "At or below lower non-recoverable threshold"; } } elsif ($sdr->event_type_code == 0x6f) { 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 sensorformat($sessdata,0,$text,$extext); } sub initsdr { my $sessdata=shift; my $netfun; my @cmd; my @returnd = (); my $error; my $rc = 0; my $text; my $code; my $resv_id_ls; my $resv_id_ms; my $sdr_type; my $sdr_offset; my $sdr_len; my @sdr_data = (); my $offset; my $len; my $i; # my $numbytes = 27; my $ipmisensortab = "$ENV{XCATROOT}/lib/GUMI/ipmisensor.tab"; my $byte_format; my $cache_file; #device id data TODO $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x20,data=>[],callback=>\&got_sdr_rep_info,callback_args=>$sessdata); } sub initsdr_withrepinfo { my $sessdata = shift; my $mfg_id=$sessdata->{mfg_id}; my $prod_id=$sessdata->{prod_id}; my $device_id=$sessdata->{device_id}; my $dev_rev=$sessdata->{device_rev}; my $fw_rev1=$sessdata->{firmware_rev1}; my $fw_rev2=$sessdata->{firmware_rev2}; #TODO: beware of dynamic SDR contents my $cache_file = "$cache_dir/sdr_$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2.$cache_version"; $sessdata->{sdrcache_file} = $cache_file; if($enable_cache eq "yes") { if ($sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2.$cache_version"}) { $sessdata->{sdr_hash} = $sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2.$cache_version"}; on_bmc_connect("SUCCESS",$sessdata); #retry bmc_connect since sdr_cache is validated return; #don't proceed to slow load } else { my $rc = loadsdrcache($sessdata,$cache_file); if($rc == 0) { $sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2.$cache_version"} = $sessdata->{sdr_hash}; on_bmc_connect("SUCCESS",$sessdata); #retry bmc_connect since sdr_cache is validated return; #don't proceed to slow load } } } if($sessdata->{sdr_info}->{version} != 0x51) { sendoutput(1,"SDR version unsupported."); return(1); #bail, do not try to continue } if($sessdata->{sdr_info}->{resv_sdr} != 1) { sendoutput(1,"SDR reservation unsupported."); return 1; } $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x22,data=>[],callback=>\&reserved_sdr_repo,callback_args=>$sessdata); } sub initsdr_withreservation { my $sessdata = shift; my $rid_ls = 0; my $rid_ms = 0; if ($sessdata->{sdr_nrid_ls}) { $rid_ls = $sessdata->{sdr_nrid_ls}; } if ($sessdata->{sdr_nrid_ms}) { $rid_ms = $sessdata->{sdr_nrid_ms}; } ####if($debug) { #### print "mfg,prod,dev: $mfg_id, $prod_id, $device_id\n"; #### printf("SDR info: %02x %d %d\n",$sdr_rep_info->version,$sdr_rep_info->rec_count,$sdr_rep_info->resv_sdr); #### print "resv_id: $resv_id_ls $resv_id_ms\n"; ####} my $resv_id_ls = $sessdata->{resv_id_ls}; my $resv_id_ms = $sessdata->{resv_id_ms}; if ( $rid_ls == 0xff and $rid_ms == 0xff) { if($enable_cache eq "yes") { #cache SDR repository for future use storsdrcache($sessdata->{sdrcache_file},$sessdata); } on_bmc_connect("SUCCESS",$sessdata); #go back armed with a capable reserviction return; #Have reached the end } $sessdata->{sdr_fetch_args} = [$resv_id_ls,$resv_id_ms,$rid_ls,$rid_ms,0,5]; $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x23,data=>$sessdata->{sdr_fetch_args},callback=>\&start_sdr_record,callback_args=>$sessdata); return; } sub start_sdr_record { my $rsp = shift; my $sessdata = shift; if($rsp->{error}) { sendoutput(1,$rsp->{error}); return; } my $resv_id_ls = shift @{$sessdata->{sdr_fetch_args}}; my $resv_id_ms = shift @{$sessdata->{sdr_fetch_args}}; my $rid_ls = shift @{$sessdata->{sdr_fetch_args}}; my $rid_ms = shift @{$sessdata->{sdr_fetch_args}}; my @returnd = ($rsp->{code},@{$rsp->{data}}); hexdump(\@returnd); my $code = $returnd[0]; if($code != 0x00) { my $text = $codes{$code}; if(!$text) { $text = sprintf("unknown response %02x",$code); } sendoutput(1,$text); return; } $sessdata->{sdr_nrid_ls} = $returnd[1]; $sessdata->{sdr_nrid_ms} = $returnd[2]; my $sdr_ver = $returnd[5]; my $sdr_type = $returnd[6]; $sessdata->{curr_sdr_type} = $sdr_type; $sessdata->{curr_sdr_len} = $returnd[7] + 5; if($sdr_type == 0x01) { $sessdata->{total_sdr_offset} = 0; } elsif($sdr_type == 0x02) { $sessdata->{total_sdr_offset} = 16; #TODO: understand this.. } elsif($sdr_type == 0xC0) { #LED descriptor, maybe } elsif($sdr_type == 0x11) { #FRU locator } elsif($sdr_type == 0x12) { initsdr_withreservation($sessdata); #next, skip this unsupported record type return; } else { initsdr_withreservation($sessdata); #next return; } $sessdata->{sdr_data} = [0,0,0,$sdr_ver,$sdr_type,$sessdata->{curr_sdr_len}]; $sessdata->{sdr_offset} = 5; my $offset=5; #why duplicate? to make for shorter typing my $numbytes = 22; if (5<$sessdata->{curr_sdr_len}) { #can't imagine this not bing the case,but keep logic in case if($offset+$numbytes > $sessdata->{curr_sdr_len}) { #scale back request for remainder $numbytes = $sessdata->{curr_sdr_len} - $offset; } $sessdata->{sdr_fetch_args} = [$resv_id_ls,$resv_id_ms,$rid_ls,$rid_ms,$offset,$numbytes]; $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x23,data=>$sessdata->{sdr_fetch_args},callback=>\&add_sdr_data,callback_args=>$sessdata); return; } else { initsdr_withreservation($sessdata); #next return; } } sub add_sdr_data { my $rsp = shift; my $sessdata = shift; my $numbytes = $sessdata->{sdr_fetch_args}->[5]; my $offset = $sessdata->{sdr_offset}; #shorten typing a little if ($rsp->{error}) { sendoutput([1,$rsp->{error}]); return; #give up } my @returnd = ($rsp->{code},@{$rsp->{data}}); my $code = $returnd[0]; if($code != 0x00) { my $text = $codes{$code}; if(!$text) { $text = sprintf("unknown response %02x",$code); } sendoutput([1,$text]); return; #abort the whole mess } push @{$sessdata->{sdr_data}},@returnd[3..@returnd-1]; $sessdata->{sdr_offset} += $numbytes; if($sessdata->{sdr_offset}+$numbytes > $sessdata->{curr_sdr_len}) { #scale back request for remainder $numbytes = $sessdata->{curr_sdr_len} - $sessdata->{sdr_offset}; } $sessdata->{sdr_fetch_args}->[4] = $sessdata->{sdr_offset}; $sessdata->{sdr_fetch_args}->[5] = $numbytes; if ($sessdata->{sdr_offset}<$sessdata->{curr_sdr_len}) { $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x23,data=>$sessdata->{sdr_fetch_args},callback=>\&add_sdr_data,callback_args=>$sessdata); return; } else { #in this case, time to parse the accumulated data parse_sdr($sessdata); } } sub parse_sdr { #parse sdr data, then cann initsdr_withreserveation to advance to next record my $sessdata = shift; my @sdr_data = @{$sessdata->{sdr_data}}; #not bothering trying to keep a packet pending concurrent with operation, harder to code that my $mfg_id=$sessdata->{mfg_id}; my $prod_id=$sessdata->{prod_id}; my $device_id=$sessdata->{device_id}; my $dev_rev=$sessdata->{device_rev}; my $fw_rev1=$sessdata->{firmware_rev1}; my $fw_rev2=$sessdata->{firmware_rev2}; my $sdr_type = $sessdata->{curr_sdr_type}; if($sdr_type == 0x11) { #FRU locator my $sdr = decode_fru_locator(@sdr_data); if ($sdr) { $sessdata->{sdr_hash}->{$sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number} = $sdr; } initsdr_withreservation($sessdata); #advance to next record return; } ####if($debug) { #### hexadump(\@sdr_data); ####} if($sdr_type == 0x12) { #if required, TODO support type 0x12 hexadump(\@sdr_data); initsdr_withreservation($sessdata); #next record return; } my $sdr = SDR->new(); if ($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.... $sessdata->{sdr_hash}->{"260.260.".$sdr->led_id} = $sdr; initsdr_withreservation($sessdata); #next record return; } $sdr->rec_type($sdr_type); $sdr->sensor_owner_id($sdr_data[6]); $sdr->sensor_owner_lun($sdr_data[7]); $sdr->sensor_number($sdr_data[8]); $sdr->entity_id($sdr_data[9]); $sdr->entity_instance($sdr_data[10]); $sdr->sensor_type($sdr_data[13]); $sdr->event_type_code($sdr_data[14]); $sdr->sensor_units_2($sdr_data[22]); $sdr->sensor_units_3($sdr_data[23]); if($sdr_type == 0x01) { $sdr->sensor_units_1($sdr_data[21]); $sdr->linearization($sdr_data[24] & 0b01111111); $sdr->M(comp2int(10,(($sdr_data[26] & 0b11000000) << 2) + $sdr_data[25])); $sdr->B(comp2int(10,(($sdr_data[28] & 0b11000000) << 2) + $sdr_data[27])); $sdr->R_exp(comp2int(4,($sdr_data[30] & 0b11110000) >> 4)); $sdr->B_exp(comp2int(4,$sdr_data[30] & 0b00001111)); } elsif ($sdr_type == 0x02) { $sdr->sensor_units_1($sdr_data[21]); } $sdr->id_string_type($sdr_data[48-$sessdata->{total_sdr_offset}]); my $override_string = getsensorname($mfg_id,$prod_id,$sdr->sensor_number); if($override_string ne "") { $sdr->id_string($override_string); } else { unless (defined $sdr->id_string_type) { initsdr_withreservation($sessdata); return; } my $byte_format = ($sdr->id_string_type & 0b11000000) >> 6; if($byte_format == 0b11) { my $len = ($sdr->id_string_type & 0b00011111) - 1; if($len > 1) { $sdr->id_string(pack("C*",@sdr_data[49-$sessdata->{total_sdr_offset}..49-$sessdata->{total_sdr_offset}+$len])); } else { $sdr->id_string("no description"); } } elsif($byte_format == 0b10) { $sdr->id_string("ASCII packed unsupported"); } elsif($byte_format == 0b01) { $sdr->id_string("BCD unsupported"); } elsif($byte_format == 0b00) { my $len = ($sdr->id_string_type & 0b00011111) - 1; if ($len > 1) { #It should be something, but need sample to code $sdr->id_string("unicode unsupported"); } else { initsdr_withreservation($sessdata); return; } } } $sessdata->{sdr_hash}->{$sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number} = $sdr; initsdr_withreservation($sessdata); return; } sub getsensorname { my $mfgid = shift; my $prodid = shift; my $sensor = shift; my $file = shift; my $mfg; my $prod; my $type; my $num; my $desc; my $name=""; if ($file and $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[0]; if($code == 0x00) { $text = ""; } else { $rc = 1; $text = $codes{$code}; if(!$text) { $rc = 1; $text = sprintf("unknown response %02x",$code); } return($rc,$text); } return($rc,@returnd[1..@returnd-2]); } sub gotdevid { #($rc,$text,$mfg_id,$prod_id,$device_id,$dev_rev,$fw_rev1,$fw_rev2) = getdevid(); my $rsp = shift; my $sessdata = shift; my $text; if($rsp->{error}) { sendoutput([1,$rsp->{error}]); return; } else { my $code = $rsp->{code}; if($code != 0x00) { my $text = $codes{$code}; if(!$text) { $text = sprintf("unknown response %02x",$code); } sendoutput([1,$text]); return; } } my @returnd = ($rsp->{code},@{$rsp->{data}}); $sessdata->{device_id} = $returnd[1]; $sessdata->{device_rev} = $returnd[2] & 0b00001111; $sessdata->{firmware_rev1} = $returnd[3] & 0b01111111; $sessdata->{firmware_rev2} = $returnd[4]; $sessdata->{ipmi_ver} = $returnd[5]; $sessdata->{dev_support} = $returnd[6]; ####my $sensor_device = 0; ####my $SDR = 0; ####my $SEL = 0; ####my $FRU = 0; ####my $IPMB_ER = 0; ####my $IPMB_EG = 0; ####my $BD = 0; ####my $CD = 0; ####if($dev_support & 0b00000001) { #### $sensor_device = 1; ####} ####if($dev_support & 0b00000010) { #### $SDR = 1; ####} ####if($dev_support & 0b00000100) { #### $SEL = 1; ####} ####if($dev_support & 0b00001000) { #### $FRU = 1; ####} ####if($dev_support & 0b00010000) { #### $IPMB_ER = 1; ####} ####if($dev_support & 0b00100000) { #### $IPMB_EG = 1; ####} ####if($dev_support & 0b01000000) { #### $BD = 1; ####} ####if($dev_support & 0b10000000) { #### $CD = 1; ####} $sessdata->{mfg_id} = $returnd[7] + $returnd[8]*0x100 + $returnd[9]*0x10000; $sessdata->{prod_id} = $returnd[10] + $returnd[11]*0x100; on_bmc_connect("SUCCESS",$sessdata); # my @data = @returnd[12..@returnd-2]; # return($rc,$text,$mfg_id,$prod_id,$device_id,$device_rev,$firmware_rev1,$firmware_rev2); } sub 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[0]; 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[1..16]; 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 got_sdr_rep_info { my $rsp = shift; my $sessdata = shift; if($rsp->{error}) { sendoutput([1,$rsp->{error}]); return; } else { my $code = $rsp->{code}; if($code != 0x00) { my $text = $codes{$code}; if(!$text) { $text = sprintf("unknown response %02x",$code); } sendoutput(1,$text); return; } } my @returnd = @{$rsp->{data}}; $sessdata->{sdr_info}->{version} = $returnd[0]; $sessdata->{sdr_info}->{rec_count} = $returnd[1] + $returnd[2]<<8; $sessdata->{sdr_info}->{resv_sdr} = ($returnd[13] & 0b00000010)>>1; initsdr_withrepinfo($sessdata); } sub reserved_sdr_repo { my $rsp = shift; my $sessdata = shift; if($rsp->{error}) { sendoutput([1,$rsp->{error}]);; return; } else { my $code = $rsp->{code}; if($code != 0x00) { my $text = $codes{$code}; if(!$text) { $text = sprintf("unknown response %02x",$code); } sendoutput([1,$text]); } } my @returnd = @{$rsp->{data}}; $sessdata->{resv_id_ls} = $returnd[0]; $sessdata->{resv_id_ms} = $returnd[1]; initsdr_withreservation($sessdata); } sub dochksum() { my $data = shift; my $sum = 0; foreach(@$data) { $sum += $_; } $sum = ~$sum + 1; return($sum & 0xFF); } sub hexdump { my $data = shift; foreach(@$data) { printf("%02x ",$_); } print "\n"; } sub getascii { my @alpha; my $text =""; my $c = 0; foreach(@_) { if (defined $_ and $_ < 128 and $_ > 0x20) { $alpha[$c] = sprintf("%c",$_); } else { $alpha[$c]=" "; } if($alpha[$c] !~ /[\/\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 comp2int { my $length = shift; my $bits = shift; my $neg = 0; if($bits & 2**($length - 1)) { $neg = 1; } $bits &= (2**($length - 1) - 1); if($neg) { $bits -= 2**($length - 1); } return($bits); } sub timestamp2datetime { my $ts = shift; if ($ts < 0x20000000) { return "BMC Uptime",sprintf("%6d s",$ts); } my @t = localtime($ts); my $time = strftime("%H:%M:%S",@t); my $date = strftime("%m/%d/%Y",@t); return($date,$time); } sub decodebcd { my $numbers = shift; my @bcd; my $text; my $ms; my $ls; foreach(@$numbers) { $ms = ($_ & 0b11110000) >> 4; $ls = ($_ & 0b00001111); push(@bcd,$ms); push(@bcd,$ls); } foreach(@bcd) { if($_ < 0x0a) { $text .= $_; } elsif($_ == 0x0a) { $text .= " "; } elsif($_ == 0x0b) { $text .= "-"; } elsif($_ == 0x0c) { $text .= "."; } } return($text); } sub storsdrcache { my $file = shift; my $sessdata = shift; unless ($sessdata) { die "need to fix this one too" } my $key; my $fh; system("mkdir -p $cache_dir"); if(!open($fh,">$file")) { return(1); } flock($fh,LOCK_EX) || return(1); foreach $key (keys %{$sessdata->{sdr_hash}}) { my $r = $sessdata->{sdr_hash}->{$key}; store_fd($r,$fh); } close($fh); return(0); } sub loadsdrcache { my $sessdata = shift; my $file = shift; my $r; my $c=0; my $fh; if(!open($fh,"<$file")) { return(1); } flock($fh,LOCK_SH) || return(1); while() { eval { $r = retrieve_fd($fh); } || last; $sessdata->{sdr_hash}->{$r->sensor_owner_id . "." . $r->sensor_owner_lun . "." . $r->sensor_number} = $r; } close($fh); return(0); } sub preprocess_request { my $request = shift; if (defined $request->{_xcatpreprocessed}->[0] and $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; $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; my $sub_fds = new IO::Select; foreach (@donargs) { donode($_->[0],$_->[1],$_->[2],$_->[3],$ipmitimeout,$ipmitrys,$command,-args=>\@exargs); } while (xCAT::IPMI->waitforrsp()) { yield }; my $node; foreach $node (keys %sessiondata) { if ($sessiondata{$node}->{ipmisession}) { $sessiondata{$node}->{ipmisession}->logout(); } } while (xCAT::IPMI->waitforrsp()) { yield }; return; 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 { my $node = shift; my $bmcip = shift; my $user = shift; my $pass = shift; my $timeout = shift; my $retries = shift; my $command = shift; my %namedargs=@_; my $extra=$namedargs{-args}; my @exargs=@$extra; $sessiondata{$node} = { node => $node, #this seems redundant, but some code will not be privy to what the key was ipmisession => xCAT::IPMI->new(bmc=>$bmcip,userid=>$user,password=>$pass), command => $command, extraargs => \@exargs, subcommand => $exargs[0], }; my ($rc,@output) = ipmicmd($sessiondata{$node}); my @outhashes; sendoutput($rc,@output); yield; #my $msgtoparent=freeze(\@outhashes); # print $outfd $msgtoparent; return $rc; } sub sendmsg { # my $callback = $output_handler; my $text = shift; my $node = shift; my $descr; my $rc; if (ref $text eq 'HASH') { die "not right now"; } elsif (ref $text eq 'ARRAY') { $rc = $text->[0]; $text = $text->[1]; } if ($text =~ /:/) { ($descr,$text) = split /:/,$text,2; } $text =~ s/^ *//; $text =~ s/ *$//; my $msg; my $curptr; if ($node) { $msg->{node}=[{name => [$node]}]; $curptr=$msg->{node}->[0]; } else { $msg = {}; $curptr = $msg; } if ($rc) { $curptr->{errorcode}=[$rc]; $curptr->{error}=[$text]; $curptr=$curptr->{error}->[0]; } else { $curptr->{data}=[{contents=>[$text]}]; $curptr=$curptr->{data}->[0]; if ($descr) { $curptr->{desc}=[$descr]; } } # print $outfd freeze([$msg]); # print $outfd "\nENDOFFREEZE6sK4ci\n"; # yield; # waitforack($outfd); $callback->($msg); } sub sendoutput { my $rc=shift; foreach (@_) { my %output; (my $desc,my $text) = split(/:/,$_,2); unless ($text) { $text=$desc; } else { $desc =~ s/^\s+//; $desc =~ s/\s+$//; if ($desc) { $output{node}->[0]->{data}->[0]->{desc}->[0]=$desc; } } $text =~ s/^\s+//; $text =~ s/\s+$//; $output{node}->[0]->{name}->[0]="BADCODE"; if ($rc) { $output{node}->[0]->{errorcode}=[$rc]; $output{node}->[0]->{error}->[0]=$text; } else { $output{node}->[0]->{data}->[0]->{contents}->[0]=$text; } $callback->(\%output); #push @outhashes,\%output; #Save everything for the end, don't know how to be slicker with Storable and a pipe # print $outfd freeze([\%output]); # print $outfd "\nENDOFFREEZE6sK4ci\n"; # yield; # waitforack($outfd); } } 1;