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);
+}