First stage of generating BMC device tree from the MRW.

This new script will generate a dts device tree syntax
file from the machine readable workbook XML.

So far just a partial device tree is generated.  A future
commit will finish the rest.

Change-Id: I09f76ebe1554146b83dab6dd6a633b562535b541
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/gen_devtree.pl b/gen_devtree.pl
new file mode 100755
index 0000000..9a564df
--- /dev/null
+++ b/gen_devtree.pl
@@ -0,0 +1,422 @@
+#!/usr/bin/env perl
+
+#Generates a BMC device tree syntax file from the machine
+#readable workbook.
+
+use strict;
+use XML::Simple;
+use mrw::Targets;
+use Getopt::Long;
+
+use constant VERSION => "/dts-v1/;";
+use constant STANDALONE_PROPERTY => "standalone_property";
+use constant DTSI_INCLUDE => "DTSI_INCLUDE";
+
+my $serverwizFile;
+my $outputFile;
+my $debug;
+
+GetOptions("x=s" => \$serverwizFile,
+           "o=s" => \$outputFile,
+           "d" => \$debug)
+or printUsage();
+
+if ((not defined $serverwizFile) || (not defined $outputFile)) {
+    printUsage();
+}
+
+my $g_targetObj = Targets->new;
+$g_targetObj->loadXML($serverwizFile);
+
+my $g_bmc = getBMCTarget();
+if (length($g_bmc) == 0) {
+    die "Unable to find a BMC in this system\n";
+}
+
+my $g_bmcModel = $g_targetObj->getAttribute($g_bmc, "MODEL");
+my $g_bmcMfgr = $g_targetObj->getAttribute($g_bmc, "MANUFACTURER");
+my $g_systemName = $g_targetObj->getSystemName();
+
+open (my $f, ">$outputFile") or die "Could not open $outputFile\n";
+
+printVersion($f);
+printIncludes($f, 0);
+printRootNodeStart($f);
+
+printPropertyList($f, 1, "model", getSystemBMCModel());
+
+printPropertyList($f, 1, "compatible", getBMCCompatibles());
+printNode($f, 1, "chosen", getChosen());
+printNode($f, 1, "memory", getMemory($g_bmc));
+
+#TODO: LEDs, UART, I2C, aliases, pinctlr
+printRootNodeEnd($f, 0);
+
+printNodes($f, 0, getMacNodes());
+
+printNodes($f, 0, getVuartNodes());
+
+close $f;
+exit 0;
+
+
+
+#Return a hash that represents the 'chosen' node
+sub getChosen()
+{
+    my $bmcStdOut = $g_targetObj->getAttributeField($g_bmc, "BMC_DT_CHOSEN",
+                                                    "stdout-path");
+    my $args = $g_targetObj->getAttributeField($g_bmc, "BMC_DT_CHOSEN",
+                                              "bootargs");
+    my %chosen;
+    $chosen{"stdout-path"} = $bmcStdOut;
+    $chosen{"bootargs"} = $args;
+    return %chosen;
+}
+
+
+#Returns a list of hashes that represent the MAC (ethernet) nodes on the BMC
+sub getMacNodes()
+{
+    my @nodes;
+    my $children = $g_targetObj->getTargetChildren($g_bmc);
+
+    #The next version of this will look for ethernet connections in the
+    #MRW instead of just the units...
+    foreach my $c (@$children) {
+
+        if ($g_targetObj->getTargetType($c) eq "unit-ethernet-master") {
+
+            if ($g_targetObj->getAttribute($c, "UNIT_ENABLED") == 1) {
+                my %node;
+                my $num = $g_targetObj->getAttribute($c, "CHIP_UNIT");
+                my $ncsi = $g_targetObj->getAttribute($c, "NCSI_MODE");
+                my $hwChecksum = $g_targetObj->getAttribute($c,
+                                                          "USE_HW_CHECKSUM");
+
+                my $name = "mac$num";
+                $node{$name}{status} = "okay";
+                if ($ncsi == 1) {
+                    $node{$name}{"use-ncsi"} = STANDALONE_PROPERTY;
+                }
+                if ($hwChecksum == 0) {
+                    $node{$name}{"no-hw-checksum"} = STANDALONE_PROPERTY;
+                }
+
+                push @nodes, { %node };
+            }
+        }
+    }
+    return @nodes;
+}
+
+
+#Returns a last of hashes that represent the virtual UART nodes
+sub getVuartNodes()
+{
+    my @nodes;
+    my %node;
+
+    #For now, enable 1 node all the time.
+    #TODO if this needs to be fixed.
+    $node{vuart}{status} = "okay";
+
+    push @nodes, { %node };
+
+    return @nodes;
+}
+
+
+#Returns a hash{'reg'} = "<.....>"  based on the
+#BMC_DT_MEMORY attribute.  This is used to display
+#memory ranges.
+sub getMemory()
+{
+    my $target = shift;
+    my $memory = $g_targetObj->getAttribute($target, "BMC_DT_MEMORY");
+    my @mem = split(',', $memory);
+    my %property;
+    my $val = "<";
+
+    #Encoded as 4 <base address>,<size> pairs of memory ranges
+    #Unused ranges are all 0s.
+    #For now, assumes 32 bit numbers, revisit later for 64 bit support
+    #Convert it into:  <num1 num2 num3 num4 etc>
+
+    for (my $i = 0;$i < scalar @mem;$i += 2) {
+
+        #pair is valid if size is nonzero
+        if (hex($mem[$i+1]) != 0) {
+            $val .= "$mem[$i] $mem[$i+1] ";
+        }
+    }
+
+    $val =~ s/\s$//;
+    $val .= ">";
+    $property{reg} = $val;
+
+    return %property;
+}
+
+
+#Returns a list of compatible fields for the BMC itself.
+sub getBMCCompatibles()
+{
+    my @compats;
+
+    #The first one is from the MRW, the next one is more generic
+    #and just <mfgr>-<model>.
+
+    if (!$g_targetObj->isBadAttribute($g_bmc, "BMC_DT_COMPATIBLE", "NA")) {
+        my $attr = $g_targetObj->getAttribute($g_bmc, "BMC_DT_COMPATIBLE");
+        push @compats, $attr;
+    }
+
+    push @compats, lc($g_bmcMfgr).",".lc($g_bmcModel);
+
+    return @compats;
+}
+
+
+#Returns a string for the system's BMC model property
+sub getSystemBMCModel()
+{
+    #<System> BMC
+    my $sys = lc $g_systemName;
+    $sys = uc(substr($sys, 0, 1)) . substr($sys, 1);
+
+    return $sys . " BMC";
+}
+
+
+#Prints a list of nodes at the same indent level
+#  $f = file handle
+#  $level = indent level (0,1,etc)
+#  @nodes = array of node hashes to print, where the
+#  key for the hash is the name of the node
+sub printNodes()
+{
+    my ($f, $level, @nodes) = @_;
+
+    foreach my $n (@nodes) {
+        my %node = %$n;
+
+        foreach my $name (sort keys %node) {
+            my %n = %{ $node{$name} };
+            printNode($f, $level, $name, %n);
+        }
+    }
+}
+
+
+#Print a single node and its children
+#  $f = file handle
+#  $level = indent level (0,1,etc)
+#  $name = the name of the node - shows up as:
+#     name { ...
+#  %vals = The contents of the node, with the following options:
+#     if the key is:
+#     - 'DTSI_INCLUDE', then value gets turned into a #include
+#     - 'COMMENT', then value gets turned into a // comment (coming soon)
+#     - 'STANDALONE_PROPERTY' then value gets turned into:  value;
+#
+#     If the value is:
+#     - a hash - then that hash gets turned into a child node
+#       where the key is the name of the child node
+#     - an array of hashes indicates an array of nodes (coming soon)
+sub printNode()
+{
+    my ($f, $level, $name, %vals) = @_;
+    my $include = "";
+
+    if ($level == 0) {
+        $name = "&".$name;
+    }
+
+    print $f "\n".indent($level) . "$name {\n";
+
+    foreach my $v (sort keys %vals) {
+
+        #A header file include, print it later
+        if ($v eq DTSI_INCLUDE) {
+            $include = $vals{$v};
+        }
+        #A nested node
+        elsif (ref($vals{$v}) eq "HASH") {
+            printNode($f, $level+1, $v, %{$vals{$v}});
+        }
+        elsif ($vals{$v} ne STANDALONE_PROPERTY) {
+            printProperty($f, $level+1, $v, $vals{$v});
+        }
+        else {
+            printStandaloneProperty($f, $level+1, $v);
+        }
+    }
+
+    #Now print the includes, if any.
+    if ($include ne "") {
+        my @incs = split(',', $include);
+        foreach my $i (@incs) {
+            print $f "#include \"$i\";\n";
+        }
+    }
+
+    print $f indent($level) . "};\n";
+}
+
+
+#Prints a comma separated list of properties.
+#e.g.  a = "b, c, d";
+#  $f = file handle
+#  $level = indent level (0,1,etc)
+#  $name = name of property
+#  @vals = list of property values
+sub printPropertyList()
+{
+    my ($f, $level, $name, @vals) = @_;
+
+    print $f indent($level) . "$name = ";
+
+    for (my $i = 0;$i < scalar @vals; $i++) {
+        print $f "\"$vals[$i]\"";
+        if ($i < (scalar(@vals) - 1)) {
+            print $f ", ";
+        }
+    }
+    print $f ";\n"
+}
+
+
+#Prints a single property.  e.g. a = "b";
+#  $f = file handle
+#  $level = indent level (0,1,etc)
+#  $name = name of property
+#  @vals = property values
+sub printProperty()
+{
+    my ($f, $level, $name, $val) = @_;
+    print $f indent($level) . "$name = \"" . convertAlias($val) . "\";\n";
+}
+
+
+#Prints a standalone property e.g. some-property;
+#  $f = file handle
+#  $level = indent level (0,1,etc)
+#  $name = name of property
+sub printStandaloneProperty()
+{
+    my ($f, $level, $name) = @_;
+    print $f indent($level) . "$name;\n";
+}
+
+
+#Replace '(alias)' with '&'.
+#Needed because Serverwiz doesn't properly escape '&'s in the XML,
+#so the '(alias)' string is used to represent the alias
+#specifier instead of '&'.
+sub convertAlias() {
+    my $val = shift;
+    $val =~ s/\(alias\)/&/g;
+    return $val
+}
+
+
+#Returns the target for the BMC chip.
+#Not worrying about multiple BMC systems for now.
+sub getBMCTarget()
+{
+    foreach my $target (sort keys %{ $g_targetObj->getAllTargets() })
+    {
+        if ($g_targetObj->getType($target) eq "BMC") {
+           return $target;
+        }
+    }
+    return "";
+}
+
+
+#Prints the device tree version line.
+#  $f = file handle
+sub printVersion()
+{
+    my $f = shift;
+    print $f VERSION."\n"
+}
+
+
+#Prints the #include line for pulling in an include file.
+#  $f = file handle
+#  $level = indent level (0,1,etc)
+sub printIncludes()
+{
+    my ($f, $level) = @_;
+    my @includes = getIncludes($g_bmc);
+
+    foreach my $i (@includes) {
+        #if a .dtsi, gets " ", otherwise < >
+        if ($i =~ /\.dtsi$/) {
+            $i = "\"" . $i . "\"";
+        }
+        else {
+            $i = "<" . $i . ">";
+        }
+        print $f indent($level) . "#include $i;\n";
+    }
+}
+
+
+#Returns an array of includes from the BMC_DT_INCLUDES attribute
+#on the target passed in.
+#  $target = the target to get the includes from
+sub getIncludes()
+{
+    my $target = shift;
+    my @includes;
+
+
+    if (!$g_targetObj->isBadAttribute($target, "BMC_DT_INCLUDES")) {
+        my $attr = $g_targetObj->getAttribute($target, "BMC_DT_INCLUDES");
+        my @incs = split(',', $attr);
+
+        foreach my $i (@incs) {
+            if ($i ne "NA") {
+                push @includes, $i
+            }
+        }
+    }
+
+    return @includes;
+}
+
+
+#Prints the root node starting bracket.
+#  $f = file handle
+sub printRootNodeStart() {
+    my $f = shift;
+    print $f "\\ \{\n";
+}
+
+
+#Prints the root node ending bracket.
+#  $f = file handle
+#  $level = indent level (0,1,etc)
+sub printRootNodeEnd() {
+    my ($f, $level) = @_;
+    print $f indent($level)."\};\n";
+}
+
+
+#Returns a string that can be used to indent based on the
+#level passed in.  Each level is an additional 4 spaces.
+#  $level = indent level (0,1,etc)
+sub indent() {
+    my $level = shift;
+    return ' ' x ($level * 4);
+}
+
+
+sub printUsage
+{
+    print "gen_devtree.pl -x [XML filename] -o [output filename]\n";
+    exit(1);
+}