#!/usr/bin/perl -w #****************************************************************************** # Copyright (c) 2013 Mauro Carvalho Chehab # # This tool is a modification of the edac-ctl, written as part of the # edac-utils: # Copyright (C) 2003-2006 The Regents of the University of California. # Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). # Written by Mark Grondona # UCRL-CODE-230739. # # This version uses the new EDAC v 3.0.0 and upper API, with adds proper # representation for the memory controllers found on Intel designs after # 2002. It requires Linux Kernel 3.5 or upper to work. # # This is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #****************************************************************************/ use strict; use File::Basename; use File::Find; use Getopt::Long; use POSIX; my $dbname = "@RASSTATEDIR@/@RAS_DB_FNAME@"; my $prefix = "@prefix@"; my $sysconfdir = "@sysconfdir@"; my $dmidecode = find_prog ("dmidecode"); my $modprobe = find_prog ("modprobe") or exit (1); my $has_aer = 0; my $has_arm = 0; my $has_devlink = 0; my $has_disk_errors = 0; my $has_extlog = 0; my $has_mem_failure = 0; my $has_mce = 0; @WITH_AER_TRUE@$has_aer = 1; @WITH_ARM_TRUE@$has_arm = 1; @WITH_DEVLINK_TRUE@$has_devlink = 1; @WITH_DISKERROR_TRUE@$has_disk_errors = 1; @WITH_EXTLOG_TRUE@$has_extlog = 1; @WITH_MEMORY_FAILURE_TRUE@$has_mem_failure = 1; @WITH_MCE_TRUE@$has_mce = 1; my %conf = (); my %bus = (); my %dimm_size = (); my %dimm_node = (); my %dimm_label_file = (); my %dimm_location = (); my %csrow_size = (); my %rank_size = (); my %csrow_ranks = (); my %dimm_ce_count = (); my %dimm_ue_count = (); my @layers; my @max_pos; my @max_csrow; my $item_size; my $prog = basename $0; $conf{labeldb} = "$sysconfdir/ras/dimm_labels.db"; $conf{labeldir} = "$sysconfdir/ras/dimm_labels.d"; $conf{mbconfig} = "$sysconfdir/ras/mainboard"; my $status = 0; my $usage = < Presents a summary of the vendor-specific logged errors. --vendor-errors Shows the vendor-specific errors stored in the error database. --vendor-platforms Shows the supported platforms with platform-ids for the vendor-specific errors. --help This help message. EOF parse_cmdline(); if ( $conf{opt}{mainboard} || $conf{opt}{print_labels} || $conf{opt}{register_labels} || $conf{opt}{display_memory_layout} || $conf{opt}{guess_dimm_label} || $conf{opt}{error_count}) { get_mainboard_info(); if ($conf{opt}{mainboard} eq "report") { print "$prog: mainboard: ", "$conf{mainboard}{vendor} model $conf{mainboard}{model}\n"; } if ($conf{opt}{print_labels}) { print_dimm_labels (); } if ($conf{opt}{register_labels}) { register_dimm_labels (); } if ($conf{opt}{display_memory_layout}) { display_memory_layout (); } if ($conf{opt}{guess_dimm_label}) { guess_dimm_label (); } if ($conf{opt}{error_count}) { display_error_count (); } } if ($conf{opt}{status}) { $status = print_status (); exit ($status ? 0 : 1); } if ($conf{opt}{summary}) { summary (); } if ($conf{opt}{errors}) { errors (); } if ($conf{opt}{vendor_errors_summary}) { vendor_errors_summary (); } if ($conf{opt}{vendor_errors}) { vendor_errors (); } if ($conf{opt}{vendor_platforms}) { vendor_platforms (); } exit (0); sub parse_cmdline { $conf{opt}{mainboard} = ''; $conf{opt}{print_labels} = 0; $conf{opt}{register_labels} = 0; $conf{opt}{status} = 0; $conf{opt}{quiet} = 0; $conf{opt}{delay} = 0; $conf{opt}{display_memory_layout} = 0; $conf{opt}{guess_dimm_label} = 0; $conf{opt}{summary} = 0; $conf{opt}{errors} = 0; $conf{opt}{error_count} = 0; $conf{opt}{vendor_errors_summary} = 0; $conf{opt}{vendor_errors} = 0; $conf{opt}{vendor_platforms} = 0; my $rref = \$conf{opt}{report}; my $mref = \$conf{opt}{mainboard}; Getopt::Long::Configure ("bundling"); my $rc = GetOptions ("mainboard:s" => sub { $$mref = $_[1]||"report" }, "help" => sub {usage (0)}, "quiet" => \$conf{opt}{quiet}, "print-labels" => \$conf{opt}{print_labels}, "guess-labels" => \$conf{opt}{guess_dimm_label}, "register-labels" => \$conf{opt}{register_labels}, "delay:s" => \$conf{opt}{delay}, "labeldb=s" => \$conf{labeldb}, "status" => \$conf{opt}{status}, "layout" => \$conf{opt}{display_memory_layout}, "summary" => \$conf{opt}{summary}, "errors" => \$conf{opt}{errors}, "error-count" => \$conf{opt}{error_count}, "vendor-errors-summary" => \$conf{opt}{vendor_errors_summary}, "vendor-errors" => \$conf{opt}{vendor_errors}, "vendor-platforms" => \$conf{opt}{vendor_platforms}, ); usage(1) if !$rc; usage (0) if !grep $conf{opt}{$_}, keys %{$conf{opt}}; if ($conf{opt}{delay} && !$conf{opt}{register_labels}) { log_error ("Only use --delay with --register-labels\n"); exit (1); } } sub usage { my ($rc) = @_; print "$usage\n"; exit ($rc); } sub run_cmd { my @args = @_; system ("@args"); return ($?>>8); } sub print_status { my $status = 0; open (MODULES, "/proc/modules") or die "Unable to open /proc/modules: $!\n"; while () { $status = 1 if /_edac/; } print "$prog: drivers ", ($status ? "are" : "not"), " loaded.\n" unless $conf{opt}{quiet}; return ($status); } sub parse_dimm_nodes { my $file = $File::Find::name; if (($file =~ /max_location$/)) { open IN, $file; my $location = ; $location =~ s/\s+$//; close IN; my @temp = split(/ /, $location); $layers[0] = "mc"; if (m,/mc/mc(\d+),) { $max_pos[0] = $1 if (!exists($max_pos[0]) || $1 > $max_pos[0]); } else { $max_pos[0] = 0 if (!exists($max_pos[0])); } for (my $i = 0; $i < scalar(@temp); $i += 2) { $layers[$i / 2 + 1] = $temp[$i]; $max_pos[$i / 2 + 1] = $temp[$i + 1]; } return; } if ($file =~ /size_mb$/) { my $mc = $file; $mc =~ s,.*mc(\d+).*,$1,; my $csrow = $file; $csrow =~ s,.*csrow(\d+).*,$1,; open IN, $file; my $size = ; close IN; my $str_loc = join(':', $mc, $csrow); $csrow_size{$str_loc} = $size; return; } if ($file =~ /location$/) { my $mc = $file; $mc =~ s,.*mc(\d+).*,$1,; my $dimm = $file; $dimm =~ s,.*(rank|dimm)(\d+).*,$2,; open IN, $file; my $location = ; $location =~ s/\s+$//; close IN; my @pos; # Get the name of the hierarchy labels if (!@layers) { my @temp = split(/ /, $location); $max_pos[0] = 0; $layers[0] = "mc"; for (my $i = 0; $i < scalar(@temp); $i += 2) { $layers[$i / 2 + 1] = $temp[$i]; $max_pos[$i / 2 + 1] = 0; } } my @temp = split(/ /, $location); for (my $i = 1; $i < scalar(@temp); $i += 2) { $pos[$i / 2] = $temp[$i]; if ($pos[$i / 2] > $max_pos[$i / 2 + 1]) { $max_pos[$i / 2 + 1] = $pos[$i / 2]; } } if ($mc > $max_pos[0]) { $max_pos[0] = $mc; } # Get DIMM size $file =~ s/dimm_location/size/; open IN, $file; my $size = ; close IN; my $str_loc = join(':', $mc, @pos); $dimm_size{$str_loc} = $size; $dimm_node{$str_loc} = $dimm; $file =~ s/size/dimm_label/; $dimm_label_file{$str_loc} = $file; $dimm_location{$str_loc} = $location; my $count; $file =~s/dimm_label/dimm_ce_count/; if (-e $file) { open IN, $file; chomp($count = ); close IN; } else { log_error ("dimm_ce_count not found in sysfs. Old kernel?\n"); exit -1; } $dimm_ce_count{$str_loc} = $count; $file =~s/dimm_ce_count/dimm_ue_count/; if (-e $file) { open IN, $file; chomp($count = ); close IN; } else { log_error ("dimm_ue_count not found in sysfs. Old kernel?\n"); exit -1; } $dimm_ue_count{$str_loc} = $count; return; } } sub guess_product { my $pvendor = undef; my $pname = undef; if (open (VENDOR, "/sys/class/dmi/id/product_vendor")) { $pvendor = ; close VENDOR; chomp($pvendor); } if (open (NAME, "/sys/class/dmi/id/product_name")) { $pname = ; close NAME; chomp($pname); } return ($pvendor, $pname); } sub get_mainboard_info { my ($vendor, $model); my ($pvendor, $pname); if ($conf{opt}{mainboard} && $conf{opt}{mainboard} ne "report") { ($vendor, $model) = split (/[: ]/, $conf{opt}{mainboard}, 2); } if (!$vendor || !$model) { ($vendor, $model) = guess_vendor_model (); } $conf{mainboard}{vendor} = $vendor; $conf{mainboard}{model} = $model; ($pvendor, $pname) = guess_product (); # since product vendor is rare, use mainboard's vendor if ($pvendor) { $conf{mainboard}{product_vendor} = $pvendor; } else { $conf{mainboard}{product_vendor} = $vendor; } $conf{mainboard}{product_name} = $pname if $pname; } sub guess_vendor_model_dmidecode { my ($vendor, $model); my ($system_vendor, $system_model); my $line = 0; $< == 0 || die "Must be root to run dmidecode\n"; open (DMI, "$dmidecode |") or die "failed to run $dmidecode: $!\n"; $vendor = $model = ""; LINE: while () { $line++; /^(\s*)(board|base board|system) information/i || next LINE; my $indent = $1; my $type = $2; while ( ) { /^(\s*)/; $1 lt $indent && last LINE; $indent = $1; if ($type eq "system") { /(?:manufacturer|vendor):\s*(.*\S)\s*/i && ( $system_vendor = $1 ); /product(?: name)?:\s*(.*\S)\s*/i && ( $system_model = $1 ); } else { /(?:manufacturer|vendor):\s*(.*\S)\s*/i && ( $vendor = $1 ); /product(?: name)?:\s*(.*\S)\s*/i && ( $model = $1 ); } last LINE if ($vendor && $model); } } close (DMI); $vendor = $system_vendor if ($vendor eq ""); $model = $system_model if ($model eq ""); return ($vendor, $model); } sub guess_vendor_model_sysfs { # # Try to look up DMI information in sysfs # open (VENDOR, "/sys/class/dmi/id/board_vendor") or return undef; open (MODEL, "/sys/class/dmi/id/board_name") or return undef; my ($vendor, $model) = (, ); close (VENDOR); close (MODEL); return undef unless ($vendor && $model); chomp ($vendor, $model); return ($vendor, $model); } sub parse_mainboard_config { my ($file) = @_; my %hash = (); my $line = 0; open (CFG, "$file") or die "Failed to read mainboard config: $file: $!\n"; while () { $line++; chomp; # remove newline s/^((?:[^'"#]*(?:(['"])[^\2]*\2)*)*)#.*/$1/; # remove comments s/^\s+//; # remove leading space s/\s+$//; # remove trailing space next unless length; # skip blank lines if (my ($key, $val) = /^\s*([-\w]+)\s*=\s*(.*)/) { $hash{$key}{val} = $val; $hash{$key}{line} = $line; next; } return undef; } close (CFG) or &log_error ("close $file: $!\n"); return \%hash; } sub guess_vendor_model { my ($vendor, $model); # # If mainboard config file exists then parse it # to get the vendor and model information. # if (-f $conf{mbconfig} ) { my $cfg = &parse_mainboard_config ($conf{mbconfig}); # If mainboard config file specified a script, then try to # run the specified script or executable: # if ($cfg->{"script"}) { $cfg = &parse_mainboard_config ("$cfg->{script}{val} |"); die "Failed to run mainboard script\n" if (!$cfg); } return ($cfg->{vendor}{val}, $cfg->{model}{val}); } ($vendor, $model) = &guess_vendor_model_sysfs (); return ($vendor, $model) if ($vendor && $model); return (&guess_vendor_model_dmidecode ()); } sub guess_dimm_label { open (DMI, "$dmidecode |") or die "failed to run $dmidecode: $!\n"; LINE: while () { /^(\s*)memory device$/i || next LINE; my ($dimm_label, $dimm_addr); while () { if (/^\s*(locator|bank locator)/i) { my $indent = $1; $indent =~ tr/A-Z/a-z/; if ($indent eq "locator") { /(?:locator):\s*(.*\S)\s*/i && ( $dimm_label = $1 ); } if ($indent eq "bank locator") { /(?:bank locator):\s*(.*\S)\s*/i && ( $dimm_addr = $1 ); } } if ($dimm_label && $dimm_addr) { printf "memory stick '%s' is located at '%s'\n", $dimm_label, $dimm_addr; next LINE; } next LINE if (/^\s*\n/); } } close (DMI); } sub parse_dimm_labels_file { my ($lh, $num_layers, $lh_prod, $num_layers_prod, $file) = (@_); my $line = -1; my $vendor = ""; my @models = (); my @products = (); my $num; open (LABELS, "$file") or die "Unable to open label database: $file: $!\n"; while () { $line++; next if /^#/; chomp; s/^\s+//; s/\s+$//; next unless length; if (/vendor\s*:\s*(.*\S)\s*/i) { $vendor = lc $1; @models = (); @products = (); $num = 0; next; } if (/(model|board)\s*:\s*(.*)$/i) { !$vendor && die "$file: line $line: MB model without vendor\n"; @models = grep { s/\s*(.*)\s*$/$1/ } split(/[,;]+/, $2); @products = (); $num = 0; next; } if (/(product)\s*:\s*(.*)$/i) { !$vendor && die "$file: line $line: product without vendor\n"; @models = (); @products = grep { s/\s*(.*)\s*$/$1/ } split(/[,;]+/, $2); $num = 0; next; } # Allow multiple labels to be specified on a single line, # separated by ; for my $str (split /;/) { $str =~ s/^\s*(.*)\s*$/$1/; next unless (my ($label, $info) = ($str =~ /^(.*)\s*:\s*(.*)$/i)); unless ($info =~ /\d+(?:[\.\:]\d+)*/) { log_error ("$file: $line: Invalid syntax, ignoring: \"$_\"\n"); next; } for my $target (split (/[, ]+/, $info)) { my $n; my ($mc, $top, $mid, $low, $extra) = ($target =~ /(\d+)(?:[\.\:](\d+)){0,1}(?:[\.\:](\d+)){0,1}(?:[\.\:](\d+)){0,1}(?:[\.\:](\d+)){0,1}/); if (defined($extra)) { die ("Error: Only up to 3 layers are currently supported on label db \"$file\"\n"); return; } elsif (!defined($top)) { die ("Error: The label db \"$file\" is defining a zero-layers machine\n"); return; } else { $n = 3; if (!defined($low)) { $low = 0; $n--; } if (!defined($mid)) { $mid = 0; $n--; } map { $lh->{$vendor}{lc $_}{$mc}{$top}{$mid}{$low} = $label } @models; map { $lh_prod->{$vendor}{lc $_}{$mc}{$top}{$mid}{$low} = $label } @products; } if (!$num) { $num = $n; map { $num_layers->{$vendor}{lc $_} = $num } @models; map { $num_layers_prod->{$vendor}{lc $_} = $num } @products; } elsif ($num != $n) { die ("Error: Inconsistent number of layers at label db \"$file\"\n"); } } } } close (LABELS) or die "Error from label db \"$file\" : $!\n"; } sub parse_dimm_labels { my %labels = (); my %num_layers = (); my %labels_prod = (); my %num_layers_prod = (); # # Accrue all DIMM labels from the labels.db file, as # well as any files under the labels dir # for my $file ($conf{labeldb}, <$conf{labeldir}/*>) { next unless -r $file; parse_dimm_labels_file (\%labels, \%num_layers, \%labels_prod, \%num_layers_prod, $file); } return (\%labels, \%num_layers, \%labels_prod, \%num_layers_prod); } sub read_dimm_label { my ($num_layers, $mc, $top, $mid, $low) = @_; my $sysfs = "/sys/devices/system/edac/mc"; my $pos; $pos = "$mc:$top:$mid:$low" if ($num_layers == 3); $pos = "$mc:$top:$mid" if ($num_layers == 2); $pos = "$mc:$top" if ($num_layers == 1); if (!defined($dimm_node{$pos})) { my $label = "$pos missing"; $pos = ""; return ($label, $pos); } my $dimm = $dimm_node{$pos}; my $dimm_label_file = $dimm_label_file{$pos}; my $location = $dimm_location{$pos}; return ("label missing", "$pos missing") unless -f $dimm_label_file; if (!open (LABEL, "$dimm_label_file")) { warn "Failed to open $dimm_label_file: $!\n"; return ("Error"); } chomp (my $label =