#!/usr/bin/perl -w #****************************************************************************** # $Id$ #****************************************************************************** # 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 file is part of edac-utils. # # 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 $prefix = "/usr"; my $sysconfdir = "/etc"; my $dmidecode = find_prog ("dmidecode"); my $modprobe = find_prog ("modprobe") or exit (1); my %conf = (); my %bus = (); my %dimms = (); my @layers; my @max_pos; # Size of each printed dimm size - FIXME: calculate it dynamically my $item_size = 12; my $prog = basename $0; $conf{labeldb} = "$sysconfdir/edac/labels.db"; $conf{labeldir} = "$sysconfdir/edac/labels.d"; $conf{mbconfig} = "$sysconfdir/edac/mainboard"; my $status = 0; my $usage = < sub { $$mref = $_[1]||"report" }, "help" => sub {usage (0)}, "quiet" => \$conf{opt}{quiet}, "print-labels" => \$conf{opt}{print_labels}, "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}); 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 get_mainboard_info { my ($vendor, $model); 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; } 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 parse_dimm_labels_file { my ($lh, $file) = (@_); my $line = -1; my $vendor = ""; my @models = (); 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 = (); 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); 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\.\d,*)+/) { log_error ("$file: $line: Invalid syntax, ignoring: \"$_\"\n"); next; } for my $target (split (/[, ]+/, $info)) { my ($mc, $row, $chan) = ($target =~ /(\d+)\.(\d+)\.(\d+)/); map { $lh->{$vendor}{lc $_}{$mc}{$row}{$chan} = $label } @models; } } } close (LABELS) or die "Error from label db \"$file\" : $!\n"; return $lh; } sub parse_dimm_labels { my %labels = (); # # 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, $file); } return \%labels; } sub read_dimm_label { my ($mc, $row, $chan) = @_; my $sysfs = "/sys/devices/system/edac/mc"; my $file = "$sysfs/mc$mc/csrow$row/ch${chan}_dimm_label"; return ("Missing") unless -f $file; if (!open (LABEL, "$file")) { warn "Failed to open $file: $!\n"; return ("Error"); } chomp (my $label =