]> www.infradead.org Git - users/mchehab/rasdaemon.git/commitdiff
Add edac-ctl perl script to contrib
authorMauro Carvalho Chehab <mchehab@redhat.com>
Fri, 19 Apr 2013 12:59:19 +0000 (09:59 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Fri, 19 Apr 2013 12:59:19 +0000 (09:59 -0300)
edac-ctl is part of edac-utils and it was written by
Mark Grondona <mgrondona@llnl.gov>. While it uses the old
EDAC API most of the time, it offers some interesting
features for EDAC. So, add it here.

Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
contrib/edac-ctl [new file with mode: 0755]

diff --git a/contrib/edac-ctl b/contrib/edac-ctl
new file mode 100755 (executable)
index 0000000..9811e70
--- /dev/null
@@ -0,0 +1,781 @@
+#!/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 <mgrondona@llnl.gov>
+#  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/local";
+my $sysconfdir  = "${prefix}/etc";
+my $dmidecode   = find_prog ("dmidecode");
+my $modprobe    = find_prog ("modprobe")  or exit (1);
+
+my %conf        = ();
+my %bus         = ();
+my %dimm_size   = ();
+my %csrow_size  = ();
+my %rank_size   = ();
+my %csrow_ranks = ();
+
+my @layers;
+my @max_pos;
+my @max_csrow;
+my $item_size;
+
+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       = <<EOF;
+Usage: $prog [OPTIONS...]
+ --quiet            Quiet operation.
+ --mainboard        Print mainboard vendor and model for this hardware.
+ --status           Print status of EDAC drivers.
+ --print-labels     Print Motherboard DIMM labels to stdout.
+ --guess-labels     Print DMI labels, when bank locator is available.
+ --register-labels  Load Motherboard DIMM labels into EDAC driver.
+ --delay=N          Delay N seconds before writing DIMM labels.
+ --labeldb=DB       Load label database from file DB.
+ --layout           Display the memory layout.
+ --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}) {
+
+    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}{status}) {
+    $status = print_status ();
+    exit ($status ? 0 : 1);
+}
+
+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;
+
+    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});
+
+    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 (<MODULES>) {
+       $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 (<DMI>) {
+        $line++;
+
+        /^(\s*)(board|base board|system) information/i || next LINE;
+        my $indent = $1;
+       my $type = $2;
+
+        while ( <DMI> ) {
+            /^(\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) = (<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 (<CFG>) {
+        $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 (<DMI>) {
+        /^(\s*)memory device$/i || next LINE;
+       my ($dimm_label, $dimm_addr);
+
+        while (<DMI>) {
+           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, $file) = (@_);
+    my $line = -1;
+    my $vendor = "";
+    my @models = ();
+
+    open (LABELS, "$file")
+        or die "Unable to open label database: $file: $!\n";
+
+    while (<LABELS>) {
+        $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 = <LABEL> || "");
+
+    close (LABEL);
+
+    return ($label);
+}
+
+sub print_dimm_labels
+{
+    my $fh = shift || *STDOUT;
+    my $lref = parse_dimm_labels ();
+    my $vendor = lc $conf{mainboard}{vendor};
+    my $model  = lc $conf{mainboard}{model};
+    my $format = "%-35s %-20s %-20s\n";
+
+    if (!exists $$lref{$vendor}{$model}) {
+        log_error ("No dimm labels for $conf{mainboard}{vendor} " .
+                   "model $conf{mainboard}{model}\n");
+        return;
+    }
+
+
+    printf $fh $format, "LOCATION", "CONFIGURED LABEL", "SYSFS CONTENTS";
+
+    for my $mc (sort keys %{$$lref{$vendor}{$model}}) {
+        for my $row (sort keys %{$$lref{$vendor}{$model}{$mc}}) {
+            for my $chan (sort keys %{$$lref{$vendor}{$model}{$mc}{$row}}) {
+
+                my $label = $$lref{$vendor}{$model}{$mc}{$row}{$chan};
+                my $rlabel = read_dimm_label ($mc, $row, $chan);
+                my $loc = "mc$mc/csrow$row/ch${chan}_dimm_label";
+
+                printf $fh $format, $loc, $label, $rlabel;
+            }
+        }
+    }
+    print $fh "\n";
+
+}
+
+sub register_dimm_labels
+{
+    my $lref = parse_dimm_labels ();
+    my $vendor = lc $conf{mainboard}{vendor};
+    my $model  = lc $conf{mainboard}{model};
+    my $sysfs  = "/sys/devices/system/edac/mc";
+
+    if (!exists $$lref{$vendor}{$model}) {
+        log_error ("No dimm labels for $conf{mainboard}{vendor} " .
+                                      "model $conf{mainboard}{model}\n");
+        return 0;
+    }
+
+    select (undef, undef, undef, $conf{opt}{delay});
+
+    for my $mc (sort keys %{$$lref{$vendor}{$model}}) {
+        for my $row (sort keys %{$$lref{$vendor}{$model}{$mc}}) {
+            for my $chan (sort keys %{$$lref{$vendor}{$model}{$mc}{$row}}) {
+
+                my $file = "$sysfs/mc$mc/csrow$row/ch${chan}_dimm_label";
+
+                # Ignore sysfs files that don't exist. Might just be
+                #  unpopulated bank.
+                next unless -f $file;
+
+                if (!open (DL, ">$file")) {
+                    warn ("Unable to open $file\n");
+                    next;
+                }
+
+                syswrite DL, $$lref{$vendor}{$model}{$mc}{$row}{$chan};
+
+                close (DL);
+
+            }
+        }
+    }
+    return 1;
+}
+
+sub parse_dimm_nodes
+{
+    my $file = $File::Find::name;
+
+    if (($file =~ /max_location$/)) {
+        open IN, $file;
+        my $location = <IN>;
+        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 =~ /_dimm_label$/) {
+        my $mc = $file;
+        $mc =~ s,.*/mc(\d+).*,$1,;
+
+        my $csrow = $file;
+        $csrow =~ s,.*/csrow(\d+).*,$1,;
+
+        my $channel = $file;
+        $channel =~ s,.*/ch(\d+).*,$1,;
+
+        if (!@max_csrow) {
+            $max_csrow[0] = 0;
+            $max_csrow[1] = 0;
+            $max_csrow[2] = 0;
+        }
+        if ($mc > $max_csrow[0]) {
+            $max_csrow[0] = $mc;
+        }
+
+        if ($csrow > $max_csrow[1]) {
+            $max_csrow[1] = $csrow;
+        }
+
+        if ($channel > $max_csrow[2]) {
+            $max_csrow[2] = $channel;
+        }
+
+        my $str_loc = join(':', $mc, $csrow, $channel);
+        $rank_size{$str_loc} = 1;
+
+        $str_loc = join(':', $mc, $csrow);
+        $csrow_ranks{$str_loc}++;
+
+        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 = <IN>;
+        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,.*dimm(\d+).*,$1,;
+
+        open IN, $file;
+        my $location = <IN>;
+        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]) {
+                $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 = <IN>;
+        close IN;
+
+        my $str_loc = join(':', $mc, @pos);
+        $dimm_size{$str_loc} = $size;
+
+        return;
+    }
+}
+
+sub dimm_display_layer($@);
+
+sub dimm_display_layer($@)
+{
+    my $layer = shift;
+    my @pos = @_;
+
+    $layer--;
+    if ($layer < 0) {
+        my $str_loc = join(':', @pos);
+        my $size = $dimm_size{$str_loc};
+        if (!$size) {
+            $size = 0;
+        }
+        my $s = sprintf "  %4i MB  |", $size;
+        $item_size = length($s);
+        return $s;
+    }
+
+    my $s;
+    for (my $i = 0; $i <= $max_pos[$layer]; $i++) {
+        $pos[$layer] = $i;
+        $s .= dimm_display_layer($layer, @pos);
+    }
+
+    return $s;
+}
+
+sub dimm_display_layer_header($$)
+{
+    my $n_items = 1;
+    my $scale;
+    my $layer = shift;
+    my $tot_items = shift;
+
+    my $s;
+    for (my $i = 0; $i <= $layer; $i++) {
+        $n_items *= $max_pos[$i] + 1;
+    }
+    $scale = $tot_items / $n_items;
+
+    my $d = 0;
+    for (my $i = 0; $i < $n_items; $i++) {
+        my $val = sprintf("%s%d", $layers[$layer], $d);
+        $val = substr($val, 0, $scale * $item_size - 2);
+        my $fillsize =  $scale * $item_size - 1 - length($val);
+        $s .= "|";
+        $s .= " " x ($fillsize / 2);
+        $s .= $val;
+        $s .= " " x ($fillsize - floor($fillsize / 2));
+
+        $d++;
+        if ($d > $max_pos[$layer]) {
+            $d = 0;
+        }
+    }
+    $s .= "|";
+    return $s;
+}
+
+sub dimm_display_mem()
+{
+    my @pos = @max_pos;
+    my $sep = "";
+    my $tot_items = 1;
+    my $first = 1;
+
+    for (my $i = 0; $i < scalar(@pos) - 1; $i++) {
+        $pos[$i] = 0;
+        $tot_items *= $max_pos[$i] + 1;
+    }
+
+    my $is_even = $max_pos[scalar(@max_pos) - 1] % 2;
+    for (my $d = $max_pos[scalar(@max_pos) - 1]; $d >= 0; $d--) {
+        my $len;
+
+        my $s = sprintf("%s%d: |", $layers[scalar(@max_pos) - 1], $d);
+        my $p1 = length($s) - 1;
+
+        $pos[scalar(@pos) - 1] = $d;
+        $s .= dimm_display_layer(scalar(@pos) - 1, @pos);
+        $len += length($s);
+
+        $sep = "-" x $p1;
+        $sep .= "+";
+        $sep .= "-" x ($len - $p1 - 2);
+        $sep .= "+";
+
+        if ($first) {
+            my $sep1 = " " x $p1;
+            $sep1 .= "+";
+            $sep1 .= "-" x ($len - $p1 - 2);
+            $sep1 .= "+";
+            printf "$sep1\n";
+            for (my $layer = 0; $layer < scalar(@pos) - 1; $layer++) {
+                my $s = sprintf("%s%d: |", $layers[scalar(@max_pos) - 1], 0);
+                my $p1 = length($s) - 1;
+                my $msg = " " x $p1;
+                $msg .= dimm_display_layer_header($layer, $tot_items);
+                printf "$msg\n";
+            }
+            printf "$sep\n" if (!$is_even);
+            $first = 0;
+        }
+
+        if ($is_even && (($max_pos[scalar(@max_pos) - 1] - $d) % 2 == 0)) {
+            printf "$sep\n";
+        }
+
+        printf "$s\n";
+    }
+    printf "$sep\n";
+}
+
+sub fill_csrow_size()
+{
+    foreach my $str_loc (keys %rank_size) {
+        my @temp = split(/:/, $str_loc);
+        my $csrow = join(':', $temp[0], $temp[1]);
+        if ($csrow_ranks{$csrow}) {
+            $rank_size{$str_loc} = $csrow_size{$csrow} / $csrow_ranks{$csrow};
+        }
+    }
+}
+
+sub display_memory_layout
+{
+    my $sysfs_dir = "/sys/devices/system/edac/mc";
+
+    find({wanted => \&parse_dimm_nodes, no_chdir => 1}, $sysfs_dir);
+
+    if (!scalar(%csrow_size)) {
+        log_error ("No memories found at via edac.\n");
+        exit -1;
+    } elsif (!scalar(%dimm_size)) {
+        fill_csrow_size;
+        $layers[0] = "mc";
+        $layers[1] = "csrow";
+        $layers[2] = "channel";
+        @max_pos = @max_csrow;
+        %dimm_size = %rank_size;
+    }
+    dimm_display_mem();
+}
+
+sub find_prog
+{
+    my ($file) = @_;
+    for my $dir ("/sbin", "/usr/sbin", split ':', $ENV{PATH}) {
+        return "$dir/$file" if -x "$dir/$file";
+    }
+    # log_error ("Failed to find $file in PATH\n");
+    return "";
+}
+
+sub log_msg   { print STDERR "$prog: ", @_ unless $conf{opt}{quiet}; }
+sub log_error { log_msg ("Error: @_"); }
+
+# vi: ts=4 sw=4 expandtab