blob: 9a564df459247e10f3148b8f5b9481ef0294df6b [file] [log] [blame]
#!/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);
}