blob: 9ba335d9f6f1d784a856452c619383e5685520b6 [file] [log] [blame]
#!/usr/bin/env perl
#Generates an OpenBMC device tree syntax file from the machine
#readable workbook. It relies on the fact that the dts include
#file for the BMC chip itself contains the BMC chip specific
#data, so this can just generate the system specific data.
#It also makes use of a YAML configuration file to contain
#settings that are outside the scope of the MRW.
#
#This doesn't attempt to support every possible type of device
#tree node from the start. Support may need to be added as newer
#systems come along that make use of different features.
use strict;
use warnings;
use XML::Simple;
use mrw::Targets;
use mrw::Util;
use Getopt::Long;
use YAML::Tiny qw(LoadFile);
use Scalar::Util qw(looks_like_number);
use constant {
VERSION => "/dts-v1/;",
ZERO_LENGTH_PROPERTY => "zero_length_property",
PRE_ROOT_INCLUDES => "pre-root-node",
ROOT_INCLUDES => "root-node",
POST_ROOT_INCLUDES => "post-root-node",
HOST_SPI_FLASH_MEM_REGION_NODE_LABEL => "flash_memory"
};
my $serverwizFile;
my $configFile;
my $outputFile;
my $debug;
GetOptions("x=s" => \$serverwizFile,
"y=s" => \$configFile,
"o=s" => \$outputFile,
"d" => \$debug)
or printUsage();
if ((not defined $serverwizFile) || (not defined $outputFile) ||
(not defined $configFile)) {
printUsage();
}
my $g_pnorNodeName = undef;
my %g_configuration = %{ LoadFile($configFile) };
my $g_targetObj = Targets->new;
$g_targetObj->loadXML($serverwizFile);
my ($g_bmc, $g_bmcModel, $g_bmcMfgr, $g_systemName);
setGlobalAttributes();
my $g_i2cBusAdjust = 0;
getI2CBusAdjust();
open (my $f, ">$outputFile") or die "Could not open $outputFile\n";
printVersion($f);
printIncludes($f, PRE_ROOT_INCLUDES);
printRootNodeStart($f);
printPropertyList($f, 1, "model", getSystemBMCModel());
printPropertyList($f, 1, "compatible", getBMCCompatibles());
printNode($f, 1, "aliases", getAliases());
printNode($f, 1, "chosen", getChosen());
printNode($f, 1, "memory", getBmcMemory());
printNode($f, 1, "reserved-memory", getReservedMemory());
printNode($f, 1, "leds", getLEDNode());
printNode($f, 1, "fsi-master", getFSINode());
printIncludes($f, ROOT_INCLUDES);
printRootNodeEnd($f, 0);
printNodes($f, 0, getBMCFlashNodes());
printNodes($f, 0, getOtherFlashNodes());
printNode($f, 0, "lpc_ctrl", getLPCNode());
printNode($f, 0, "mbox", getMBoxNode());
printNodes($f, 0, getUARTNodes());
printNodes($f, 0, getMacNodes());
printNodes($f, 0, getI2CNodes());
printNodes($f, 0, getVuartNodes());
printIncludes($f, POST_ROOT_INCLUDES);
close $f;
exit 0;
#Finds the values for these globals:
# $g_bmc, $g_bmcModel, $g_bmcMfgr, $g_systemName
sub setGlobalAttributes
{
$g_bmc = Util::getBMCTarget($g_targetObj);
if ($g_targetObj->isBadAttribute($g_bmc, "MODEL")) {
die "The MODEL attribute on $g_bmc is missing or empty.\n";
}
$g_bmcModel = $g_targetObj->getAttribute($g_bmc, "MODEL");
if ($g_targetObj->isBadAttribute($g_bmc, "MANUFACTURER")) {
die "The MANUFACTURER attribute on $g_bmc is missing or empty.\n";
}
$g_bmcMfgr = $g_targetObj->getAttribute($g_bmc, "MANUFACTURER");
$g_systemName = $g_targetObj->getSystemName();
if (length($g_systemName) == 0) {
die "The SYSTEM_NAME attribute is not set on the system target.\n";
}
}
#Returns a hash that represents the 'aliases' node.
#Will look like:
# aliases {
# name1 = &val1;
# name2 = &val2;
# ...
# }
sub getAliases
{
my %aliases;
#Get the info from the config file
if ((not exists $g_configuration{aliases}) ||
(keys %{$g_configuration{aliases}} == 0)) {
print "WARNING: Missing or empty 'aliases' section in config file.\n";
return %aliases;
}
%aliases = %{ $g_configuration{aliases} };
#add a & reference if one is missing
foreach my $a (keys %aliases) {
if (($aliases{$a} !~ /^&/) && ($aliases{$a} !~ /^\(ref\)/)) {
$aliases{$a} = "(ref)$aliases{$a}";
}
}
return %aliases;
}
#Return a hash that represents the 'chosen' node
#Will look like:
# chosen {
# stdout-path = ...
# bootargs = ...
# }
sub getChosen
{
my %chosen;
my @allowed = qw(bootargs stdin-path stdout-path);
#Get the info from the config file
if (not exists $g_configuration{chosen}) {
die "ERROR: Missing 'chosen' section in config file.\n";
}
%chosen = %{ $g_configuration{chosen} };
foreach my $key (keys %chosen) {
#Check for allowed entries. Empty is OK.
if (!grep(/^$key$/, @allowed)) {
die "Invalid entry $key in 'chosen' section in config file\n";
}
#stdout-path and stdin-path can use aliases, which will look like
#(alias)uart5 in the yaml. Change to (ref)uart5 so it will be
#converted to a '&' later.
$chosen{$key} =~ s/\(alias\)/\(ref\)/g;
}
return %chosen;
}
#Return a hash that represents the 'memory' node.
#Will look like:
# memory {
# reg = < base size >
# }
sub getBmcMemory
{
my %memory;
#Get the info from the config file
if (not exists $g_configuration{memory}) {
die "ERROR: Missing 'memory' section in config file.\n";
}
if ((not exists $g_configuration{memory}{base}) ||
($g_configuration{memory}{base} !~ /0x/)) {
die "ERROR: The base entry in the memory section in the config " .
"file is either missing or invalid.\n";
}
if ((not exists $g_configuration{memory}{size}) ||
($g_configuration{memory}{size} !~ /0x/)) {
die "ERROR: The size entry in the memory section in the config " .
"file is either missing or invalid.\n";
}
#Future: could do more validation on the actual values
addRegProp(\%memory,
$g_configuration{memory}{base},
$g_configuration{memory}{size});
return %memory;
}
#Returns a hash that represents the 'reserved-memory' node.
#This currently only supports the memory region for the LPC
#host spi flash mailbox. Will look like:
# reserved-memory {
# #address-cells = <1>;
# #size-cells = <1>;
# ranges;
#
# flash_memory: region@94000000 {
# no-map;
# reg = <0x94000000 0x04000000>;
# };
# };
sub getReservedMemory
{
my %memory;
if (not exists $g_configuration{"lpc-host-spi-flash-mailbox"}) {
return %memory;
}
$memory{"#address-cells"} = "<1>";
$memory{"#size-cells"} = "<1>";
$memory{ranges} = ZERO_LENGTH_PROPERTY;
#Get the sub node that contains the address range
my ($name, $node) = getHostSpiFlashMboxRegion();
$memory{$name} = { %$node };
return %memory;
}
#Returns a hash that represents a child node of the
#reserved-memory node which contains the address range
#that the host spi flash is mapped to.
sub getHostSpiFlashMboxRegion
{
my %node;
$node{"no-map"} = ZERO_LENGTH_PROPERTY;
#This node needs a label the LPC node can refer to.
$node{NODE_LABEL} = HOST_SPI_FLASH_MEM_REGION_NODE_LABEL;
#Get the memory region's base address and size from the config file
if (not exists $g_configuration{"lpc-host-spi-flash-mailbox"}
{"bmc-address-range"}{base}) {
die "Could not find lpc-host-spi-flash-mailbox base " .
"address in config file\n";
}
my $base = $g_configuration{"lpc-host-spi-flash-mailbox"}
{"bmc-address-range"}{base};
#Allow 1 hex value, up to 4B
if ($base !~ /^0x[0-9a-fA-F]{1,8}$/) {
die "lpc-host-spi-flash-mailbox base address $base is invalid\n";
}
if (not exists $g_configuration{"lpc-host-spi-flash-mailbox"}
{"bmc-address-range"}{size}) {
die "Could not find lpc-host-spi-flash-mailbox address size " .
"in config file\n";
}
my $size = $g_configuration{"lpc-host-spi-flash-mailbox"}
{"bmc-address-range"}{size};
if ($size !~ /^0x[0-9a-fA-F]{1,8}$/) {
die "lpc-host-spi-flash-mailbox address range size " .
"$size is invalid\n";
}
addRegProp(\%node, $base, $size);
my $name = makeNodeName("region", $node{reg});
return ($name, \%node);
}
#Returns an array of hashes representing the device tree nodes for
#the BMC flash. These nodes are BMC model specific because different
#models can have different device drivers.
sub getBMCFlashNodes
{
my @nodes;
if ($g_bmcModel eq "AST2500") {
my %node = getAST2500BMCSPIFlashNode();
push @nodes, { %node };
}
else {
die "ERROR: No BMC SPI flash support yet for BMC model $g_bmcModel\n";
}
return @nodes;
}
#Returns a hash that represents the BMC SPI flash(es) by finding the SPI
#connections that come from the unit tagged as BMC_CODE. The code also
#looks in the config file for any additional properties to add. Supports
#the hardware where the same SPI master unit can be wired to more than 1
#flash (a chip select line is used to switch between them.) This is
#specific to the ASPEED AST2500 hardware and device driver.
#Will look like:
# fmc {
# status = "okay"
# flash@0 {
# ...
# };
# flash@1 {
# ...
# };
sub getAST2500BMCSPIFlashNode
{
my %bmcFlash;
my $chipSelect = 0;
my $lastUnit = "";
my $connections = $g_targetObj->findConnections($g_bmc, "SPI", "FLASH");
if ($connections eq "") {
die "ERROR: No BMC SPI flashes found connected to the BMC\n";
}
statusOK(\%{$bmcFlash{fmc}});
foreach my $spi (@{$connections->{CONN}}) {
#Looking for spi-masters with a function of 'BMC_CODE'.
#It's possible there are multiple flash chips here.
if (!$g_targetObj->isBadAttribute($spi->{SOURCE}, "SPI_FUNCTION")) {
my $function = $g_targetObj->getAttribute($spi->{SOURCE},
"SPI_FUNCTION");
if ($function eq "BMC_CODE") {
my $flashName = "flash@".$chipSelect;
$bmcFlash{fmc}{$flashName}{COMMENT} = connectionComment($spi);
statusOK(\%{$bmcFlash{fmc}{$flashName}});
#Add in anything specified in the config file for this chip.
addBMCFlashConfigProperties(\%{$bmcFlash{fmc}{$flashName}},
$chipSelect);
#The code currently only supports the config where a chip
#select line is used to select between possibly multiple
#flash chips attached to the same SPI pins/unit. So we
#need to make sure if there are multiple chips found, that
#they are off of the same master unit.
if ($lastUnit eq "") {
$lastUnit = $spi->{SOURCE};
}
else {
if ($lastUnit ne $spi->{SOURCE}) {
die "ERROR: Currently only 1 spi-master unit is " .
"supported for BMC flash connections."
}
}
#Since we don't need anything chip select specific from the
#XML, we can just assign our own chip selects.
$chipSelect++;
}
}
}
if ($chipSelect == 0) {
die "ERROR: Didn't find any BMC flash chips connected";
}
return %bmcFlash;
}
#Looks in the bmc-flash-config section in the config file for the
#chip select passed in to add any additional properties to the BMC
#flash node.
# $node = hash reference to the flash node
# $cs = the flash chip select value
sub addBMCFlashConfigProperties
{
my ($node, $cs) = @_;
my $section = "chip-select-$cs";
if (exists $g_configuration{"bmc-flash-config"}{$section}) {
foreach my $key (sort keys $g_configuration{"bmc-flash-config"}{$section}) {
$node->{$key} = $g_configuration{"bmc-flash-config"}{$section}{$key};
}
}
}
#Returns an array of hashes representing the other flashes used by the
#BMC besides the ones that hold the BMC code. This is BMC model specific
#as different models can have different interfaces.
#Typically, these are SPI flashes.
sub getOtherFlashNodes
{
my @nodes;
if ($g_bmcModel eq "AST2500") {
@nodes = getAST2500SpiFlashNodes();
}
else {
die "ERROR: No SPI flash support yet for BMC model $g_bmcModel\n";
}
return @nodes;
}
#Returns an array of hashes representing the SPI flashes in an
#AST2500. These are for the SPI1 and SPI2 interfaces in the chip.
#Each SPI master interface can support multiple flash chips. If
#no hardware is connected to the interface, the node won't be present.
sub getAST2500SpiFlashNodes
{
my @nodes;
#The AST2500 has 2 SPI master units, 1 and 2.
my @units = (1, 2);
foreach my $unit (@units) {
my ($node, $foundPNOR) = getAST2500SpiMasterNode($unit);
if (keys %$node) {
my %spiNode;
my $nodeName = "spi$unit";
$spiNode{$nodeName} = { %$node };
push @nodes, { %spiNode };
#Save off the PNOR SPI node name for use by LPC node
if ($foundPNOR) {
$g_pnorNodeName = $nodeName;
}
}
}
return @nodes;
}
#Returns a hash that represents the device tree node for the SPI1
#or SPI2 master interface on the AST2500. Each master can support
#multiple chips by use of a chip select.
#Will look like:
# spi1 {
# status = "okay";
# flash@0 {
# ...
# };
# };
#
# $spiNum = The SPI master unit number to use
sub getAST2500SpiMasterNode
{
my $spiNum = shift;
my %spiMaster;
my $chipSelect = 0;
my $foundPNOR = 0;
my $connections = $g_targetObj->findConnections($g_bmc, "SPI", "FLASH");
if ($connections eq "") {
return %spiMaster;
}
#Looking for spi-masters with a chip-unit of $spiNum
#It's possible there are multiple flash chips off the master
foreach my $spi (@{$connections->{CONN}}) {
my $unitNum = $g_targetObj->getAttribute($spi->{SOURCE},
"CHIP_UNIT");
if ($unitNum == $spiNum) {
statusOK(\%spiMaster);
#Add in any pinctrl properties. These would come from the parent
#of $spi{SOURCE}, which would be a unit-pingroup-bmc if the
#pins for this connection are multi-function.
addPinCtrlProps($g_targetObj->getTargetParent($spi->{SOURCE}),
\%spiMaster);
my $flashName = "flash@".$chipSelect;
$spiMaster{$flashName}{COMMENT} = connectionComment($spi);
statusOK(\%{$spiMaster{$flashName}});
#AST2500 PNORs need a label
my $function = $g_targetObj->getAttribute($spi->{SOURCE},
"SPI_FUNCTION");
if ($function eq "PNOR") {
$spiMaster{$flashName}{label} = "pnor";
$foundPNOR = 1;
}
$chipSelect++;
}
}
return (\%spiMaster, $foundPNOR);
}
#Returns a hash that represents the mbox node.
#This node is used by the LPC mailbox device driver.
#Only present if the LPC mailbox is enabled in the config file.
#Node looks like:
# &mbox {
# status = "okay";
# }
sub getMBoxNode
{
my %node;
if (exists $g_configuration{"lpc-host-spi-flash-mailbox"}) {
statusOK(\%node);
}
return %node;
}
#Returns a hash that represents the LPC node.
#Only present if the LPC mailbox is enabled in the config file.
#Node looks like:
# &lpc_ctrl {
# flash = <&spi1>;
# memory-region = <&flash_memory>;
# status = "okay";
#};
sub getLPCNode
{
my %node;
if (exists $g_configuration{"lpc-host-spi-flash-mailbox"}) {
statusOK(\%node);
#Point to the reserved-memory region label
$node{"memory-region"} = "<(ref)" .
HOST_SPI_FLASH_MEM_REGION_NODE_LABEL . ">";
if (not defined $g_pnorNodeName) {
die "The PNOR SPI flash node cannot be found but is required " .
"if the LPC mailbox is enabled.\n";
}
$node{flash} = "<(ref)$g_pnorNodeName>";
}
return %node;
}
#Returns a hash that represents the leds node by finding all of the
#GPIO connections to LEDs.
#Node will look like:
# leds {
# <ledname> {
# gpios = &gpio ASPEED_GPIO(x, y) GPIO_ACTIVE_xxx>
# };
# <another ledname> {
# ...
# }
sub getLEDNode
{
my %leds;
$leds{compatible} = "gpio-leds";
my $connections = $g_targetObj->findConnections($g_bmc, "GPIO", "LED");
if ($connections eq "") {
print "WARNING: No LEDs found connected to the BMC\n";
return %leds;
}
foreach my $gpio (@{$connections->{CONN}}) {
my %ledNode;
$ledNode{COMMENT} = connectionComment($gpio);
#The node name will be the simplified LED name
my $name = $gpio->{DEST_PARENT};
$name =~ s/(-\d+$)//; #remove trailing position
$name =~ s/.*\///; #remove the front of the path
#For now only supports ASPEED.
if (uc($g_bmcMfgr) ne "ASPEED") {
die "ERROR: Unsupported BMC manufacturer $g_bmcMfgr\n";
}
my $num = $g_targetObj->getAttribute($gpio->{SOURCE}, "PIN_NUM");
my $macro = getAspeedGpioMacro($num);
#If it's active high or low
my $state = $g_targetObj->getAttribute($gpio->{DEST_PARENT}, "ON_STATE");
my $activeString = getGpioActiveString($state);
$ledNode{gpios} = "<&gpio $macro $activeString>";
$leds{$name} = { %ledNode };
}
return %leds;
}
#Returns a either GPIO_ACTIVE_HIGH or GPIO_ACTIVE_LOW
# $val = either a 1 or a 0 for active high or low
sub getGpioActiveString
{
my $val = shift;
if ($val == 0) {
return "GPIO_ACTIVE_LOW";
}
return "GPIO_ACTIVE_HIGH";
}
#Turns a GPIO number into something like ASPEED_GPIO(A, 0) for the
#ASPEED GPIO numbering scheme A[0-7] -> Z[0-7] and then starts at
#AA[0-7] after that.
# $num = the GPIO number
sub getAspeedGpioMacro
{
my $num = shift;
my $char;
my $offset = $num % 8;
my $block = int($num / 8);
#If past Z, wraps to AA, AB, etc
if ((ord('A') + $block) > ord('Z')) {
#how far past Z?
$char = $block - (ord('Z') - ord('A'));
#Don't let it wrap twice
if ($char > (ord('Z') - ord('A') + 1)) {
die "ERROR: Invalid PIN_NUM value $num found for GPIO\n";
}
#start back at 'A' again, and convert to a character
$char = chr($char + ord('A') - 1);
#Add in a bonus 'A', to get something like AB
$char = "A".$char;
}
else {
$char = ord('A') + $block;
$char = chr($char);
}
return "ASPEED_GPIO($char, $offset)";
}
#Returns a hash that represents the OpenFSI device tree node.
#This node defines the GPIOs used by FSI.
#Node will look like:
# fsi-master {
# status = "okay";
# compatible = "ibm,fsi-master-gpio", "ibm,fsi-master";
# clock-gpios = <&gpio ASPEED_GPIO(AA, 0) GPIO_ACTIVE_HIGH>;
# data-gpios = <&gpio ASPEED_GPIO(E, 0) GPIO_ACTIVE_HIGH>;
# enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>;
# mux-gpios = <&gpio ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>;
# trans-gpios = <&gpio ASPEED_GPIO(R, 2) GPIO_ACTIVE_HIGH>;
# };
sub getFSINode
{
my %node;
my $enabled = 0;
#For now only supports ASPEED because of the GPIO syntax.
if (uc($g_bmcMfgr) ne "ASPEED") {
die "ERROR: Unsupported BMC manufacturer $g_bmcMfgr in getFSINode\n";
}
#Check that OpenFSI is enabled in the config file.
if (exists $g_configuration{"enable-openfsi"}) {
if ($g_configuration{"enable-openfsi"} eq "true") {
$enabled = 1;
}
elsif (($g_configuration{"enable-openfsi"} ne "true") &&
($g_configuration{"enable-openfsi"} ne "false")) {
die "Invalid enable-openfsi config file value: " .
$g_configuration{"enable-openfsi"} . "\n";
}
}
return %node unless ($enabled == 1);
#In the MRW there is an fsi_bit_bang logical part that connects to the
#BMC's 5 FSI related GPIOs. This part then has an FSI master unit that
#would connect to the P9's FSI slave (though we don't check that).
#Find the specific GPIOs by the fsi_bit_bang slave
#unit that they're connected to. Some are optional.
my %fsiGpios = ("fsi_bit_bang.fsi_clk" =>
{
name => "clock-gpios",
gpio => undef,
required => 1
},
"fsi_bit_bang.fsi_dat" =>
{
name => "data-gpios",
gpio => undef,
required => 1
},
"fsi_bit_bang.fsi_mux" =>
{
name => "mux-gpios",
gpio => undef
},
"fsi_bit_bang.fsi_enable" =>
{
name => "enable-gpios",
gpio => undef
},
"fsi_bit_bang.fsi_trans" =>
{
name => "trans-gpios",
gpio => undef
}
);
my $connections = $g_targetObj->findConnections($g_bmc, "GPIO");
if ($connections eq "") {
die "No GPIO connections found in getFSINode\n";
}
for my $gpio (@{$connections->{CONN}}) {
#Check if the destination's slave unit is in our list.
my $slaveUnit = $g_targetObj->getInstanceName($gpio->{DEST});
if (exists $fsiGpios{$slaveUnit}) {
my $num = $g_targetObj->getAttribute($gpio->{SOURCE}, "PIN_NUM");
$fsiGpios{$slaveUnit}{gpio} = getAspeedGpioMacro($num);
}
}
statusOK(\%node);
push @{$node{compatible}}, "ibm,fsi-master", "ibm,fsi-master-gpio";
while (my ($key, $hash) = each(%fsiGpios)) {
if (not defined $hash->{gpio}) {
if (exists $hash->{required}) {
die "Missing connection for FSI GPIO $key\n";
}
}
else {
$node{$hash->{name}} = "<&gpio $hash->{gpio} GPIO_ACTIVE_HIGH>";
}
}
return %node;
}
#Returns a list of hashes that represent the UART nodes on the BMC by
#finding the UART connections.
#Nodes will look like:
# &uartX {
# status = "okay"
# }
sub getUARTNodes
{
my @nodes;
#Using U750 for legacy MRW reasons
my $connections = $g_targetObj->findConnections($g_bmc, "U750");
if ($connections eq "") {
print "WARNING: No UART buses found connected to the BMC\n";
return @nodes;
}
foreach my $uart (@{$connections->{CONN}}) {
my %node;
my $num = $g_targetObj->getAttribute($uart->{SOURCE}, "CHIP_UNIT");
my $name = "uart$num";
statusOK(\%{$node{$name}});
$node{$name}{COMMENT} = connectionComment($uart);
#Add in any pinctrl properties. These would come from the parent
#of $uart{SOURCE}, which would be a unit-pingroup-bmc if the
#pins for this connection are multi-function.
addPinCtrlProps($g_targetObj->getTargetParent($uart->{SOURCE}),
\%{$node{$name}});
push @nodes, { %node };
}
return @nodes;
}
#Returns a list of hashes that represent the MAC (ethernet) nodes on the BMC
#by finding the connections of type ETHERNET.
#Nodes will look like:
# &macX {
# ...
# }
sub getMacNodes
{
my @nodes;
my $connections = $g_targetObj->findConnections($g_bmc, "ETHERNET");
if ($connections eq "") {
print "WARNING: No ethernet buses found connected to the BMC\n";
return @nodes;
}
foreach my $eth (@{$connections->{CONN}}) {
my %node;
my $num = $g_targetObj->getAttribute($eth->{SOURCE}, "CHIP_UNIT");
my $ncsi = $g_targetObj->getAttribute($eth->{SOURCE}, "NCSI_MODE");
my $hwChecksum = $g_targetObj->getAttribute($eth->{SOURCE},
"USE_HW_CHECKSUM");
my $name = "mac$num";
statusOK(\%{$node{$name}});
if ($ncsi == 1) {
$node{$name}{"use-ncsi"} = ZERO_LENGTH_PROPERTY;
}
if ($hwChecksum == 0) {
$node{$name}{"no-hw-checksum"} = ZERO_LENGTH_PROPERTY;
}
$node{$name}{COMMENT} = connectionComment($eth);
#Add in any pinctrl properties. These would come from the parent
#of $eth{SOURCE}, which would be a unit-pingroup-bmc if the
#pins for this connection are multi-function.
addPinCtrlProps($g_targetObj->getTargetParent($eth->{SOURCE}),
\%{$node{$name}});
push @nodes, { %node };
}
return @nodes;
}
#Returns a list of hashes that represent the virtual UART nodes
#Node will look like:
# &vuart {
# status = "okay"
# }
sub getVuartNodes
{
my @nodes;
my %node;
#For now, enable 1 node all the time.
#TBD if this needs to be fixed
statusOK(\%{$node{vuart}});
push @nodes, { %node };
return @nodes;
}
#Returns a list of hashes that represent the I2C device nodes.
#There is 1 parent node for each bus, which then have subnodes
#for each device on that bus. If a bus doesn't have any
#attached devices, it doesn't need to show up.
#The nodes will look like:
# &i2c0 {
# status = "okay"
# device1@addr { (addr = 7 bit I2C address)
# reg = <addr>
# compatible = ...
# ...
# }
# device2@addr {
# reg = <addr>
# ...
# }
# }
# &i2c1 {
# ...
# }
sub getI2CNodes
{
my @nodes;
my %busNodes;
my $connections = $g_targetObj->findConnections($g_bmc, "I2C");
if ($connections eq "") {
print "WARNING: No I2C buses found connected to the BMC\n";
return @nodes;
}
foreach my $i2c (@{$connections->{CONN}}) {
my %deviceNode, my $deviceName;
$deviceNode{COMMENT} = connectionComment($i2c);
$deviceName = lc $i2c->{DEST_PARENT};
$deviceName =~ s/-\d+$//; #remove trailing position
$deviceName =~ s/.*\///; #remove the front of the path
#Get the I2C address
my $i2cAddress = $g_targetObj->getAttribute($i2c->{DEST}, "I2C_ADDRESS");
$i2cAddress = hex($i2cAddress);
if ($i2cAddress == 0) {
die "ERROR: Missing I2C address on $i2c->{DEST}\n";
}
#Put it in the format we want to print it in
$i2cAddress = Util::adjustI2CAddress($i2cAddress);
addRegProp(\%deviceNode, $i2cAddress);
$deviceName = makeNodeName($deviceName, $deviceNode{reg});
#Get the I2C bus number
if ($g_targetObj->isBadAttribute($i2c->{SOURCE},
"I2C_PORT")) {
die "ERROR: I2C_PORT attribute in $i2c->{DEST_PARENT} " .
"is either missing or empty.\n";
}
my $busNum = $g_targetObj->getAttribute($i2c->{SOURCE}, "I2C_PORT");
if ($busNum =~ /0x/i) {
$busNum = hex($busNum);
}
#Convert the number to the Linux numbering scheme.
$busNum += $g_i2cBusAdjust;
#Get the compatible property
if ($g_targetObj->isBadAttribute($i2c->{DEST_PARENT},
"BMC_DT_COMPATIBLE")) {
die "ERROR: BMC_DT_COMPATIBLE attribute in $i2c->{DEST_PARENT} " .
"is either missing or empty.\n";
}
$deviceNode{compatible} = $g_targetObj->getAttribute(
$i2c->{DEST_PARENT},
"BMC_DT_COMPATIBLE");
#Get any other part specific properties, where the property
#names are actually defined in the XML.
my %props = getPartDefinedDTProperties($i2c->{DEST_PARENT});
foreach my $prop (sort keys %props) {
$deviceNode{$prop} = $props{$prop};
}
#busNodeName is the hash twice so when we loop
#below it doesn't get lost
my $busNodeName = "i2c$busNum";
statusOK(\%{$busNodes{$busNodeName}{$busNodeName}});
$busNodes{$busNodeName}{$busNodeName}{$deviceName} = { %deviceNode };
#Add in any pinctrl properties. These would come from the parent
#of $i2c{SOURCE}, which would be a unit-pingroup-bmc if the
#pins for this connection are multi-function.
addPinCtrlProps($g_targetObj->getTargetParent($i2c->{SOURCE}),
\%{$busNodes{$busNodeName}{$busNodeName}});
}
#Each bus gets its own hash entry in the array
for my $b (sort keys %busNodes) {
push @nodes, { %{$busNodes{$b}} };
}
return @nodes;
}
#Returns a hash of property names and values that should be stored in
#the device tree node for this device. The names of the properties and
#the attributes to find their values in are stored in the
#BMC_DT_ATTR_NAMES attribute in the chip.
# $chip = the chip target
sub getPartDefinedDTProperties
{
my $chip = shift;
my %props;
if ($g_targetObj->isBadAttribute($chip, "BMC_DT_ATTR_NAMES")) {
return %props;
}
my $attr = $g_targetObj->getAttribute($chip, "BMC_DT_ATTR_NAMES");
$attr =~ s/\s//g;
my @names = split(',', $attr);
#There can be up to 4 entries in this attribute
for (my $i = 0; $i < scalar @names; $i += 2) {
#$names[$i] holds the name of the attribute.
#$names[$i+1] holds the name of the property to store its value in.
if (($names[$i] ne "NA") && ($names[$i] ne "")) {
my $val = $g_targetObj->getAttribute($chip, $names[$i]);
#if the value is empty, assume it's for a standalone property,
#which gets turned into: some-property;
if ($val eq "") {
$props{$names[$i+1]} = ZERO_LENGTH_PROPERTY;
}
else {
$props{$names[$i+1]} = "<$val>";
}
}
}
return %props;
}
#Sets the global $g_i2cBusAdjust from the configuration file.
sub getI2CBusAdjust
{
if (exists $g_configuration{"i2c-bus-adjust"}) {
$g_i2cBusAdjust = $g_configuration{"i2c-bus-adjust"};
if (!looks_like_number($g_i2cBusAdjust)) {
die "ERROR: Invalid i2c-bus-adjust value $g_i2cBusAdjust " .
"found in config file.\n";
}
}
else {
$g_i2cBusAdjust = 0;
print "WARNING: No I2C Bus number adjustment done " .
"for this system.\n";
}
}
#Adds two pinctrl properties to the device node hash passed in,
#if specified in the MRW. Pin Control refers to a mechanism for
#Linux to know which function of a multi-function pin to configure.
#For example, a pin could either be configured to be a GPIO, or
#an I2C clock line. The pin function depends on board wiring,
#so is known by the MRW.
# $target = the target to get the BMC_DT_PINCTRL_FUNCTS attribute from
# $node = a hash reference to the device tree node to add the properties to
sub addPinCtrlProps
{
my ($target, $node) = @_;
if (!$g_targetObj->isBadAttribute($target, "BMC_DT_PINCTRL_FUNCS")) {
my $attr = $g_targetObj->getAttribute($target,
"BMC_DT_PINCTRL_FUNCS");
my $pinCtrl0Prop = makePinCtrl0PropValue($attr);
if ($pinCtrl0Prop ne "") {
$node->{"pinctrl-names"} = "default";
$node->{"pinctrl-0"} = $pinCtrl0Prop;
}
}
}
#Constructs the pinctrl-0 property value based on the
#BMC_DT_PINCTRL_FUNCS attribute passed in.
# $attr = BMC_DT_PINCTRL_FUNCS attribute value, which is an array
sub makePinCtrl0PropValue
{
my $attr = shift;
my @entries;
my $value = "";
$attr =~ s/\s//g;
my @funcs = split(',', $attr);
foreach my $func (@funcs) {
if (($func ne "NA") && ($func ne "")) {
push @entries, $func;
}
}
#<&pinctrl_funcA_default &pinctrl_funcB_default ...>
if (scalar @entries) {
$value = "<";
foreach my $entry (@entries) {
$value .= "&pinctrl_".$entry."_default ";
}
$value =~ s/\s$//; #Remove the trailing space
$value .= ">";
}
return $value;
}
#Returns a list of compatible fields for the BMC itself.
sub getBMCCompatibles
{
my @compats;
#1st entry: <system mfgr>,<system name>-bmc
#2nd entry: <bmc mfgr>,<bmc model>
foreach my $target (sort keys %{ $g_targetObj->getAllTargets() }) {
if ($g_targetObj->getType($target) eq "SYS") {
my $mfgr = $g_targetObj->getAttribute($target, "MANUFACTURER");
push @compats, lc "$mfgr,$g_systemName-bmc";
last;
}
}
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";
}
#Create the comment that will show up in the device tree
#for a connection. In the output, will look like:
# // sourceUnit ->
# // destChip
#
# $conn = The connection hash reference
sub connectionComment
{
my $conn = shift;
my $comment = "$conn->{SOURCE} ->\n$conn->{DEST_PARENT}";
return $comment;
}
#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
# - 'ZERO_LENGTH_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 - will print a property list, like: "a", "b"
sub printNode
{
my ($f, $level, $name, %vals) = @_;
my $include = "";
#No reason to print an empty node
if (!keys %vals) {
return;
}
if ($level == 0) {
$name = "&".$name;
}
print $f "\n";
if (exists $vals{COMMENT}) {
my @lines = split('\n', $vals{COMMENT});
foreach my $l (@lines) {
print $f indent($level) . "// $l\n";
}
}
#The node can have a label, which looks like:
#label : name {
my $label = "";
if (exists $vals{NODE_LABEL}) {
$label = $vals{NODE_LABEL} . ": ";
}
print $f indent($level) . $label . "$name {\n";
#First print properties, then includes, then subnodes
#Print Properties
foreach my $v (sort keys %vals) {
next if ($v eq "COMMENT");
next if ($v eq "DTSI_INCLUDE");
next if ($v eq "NODE_LABEL");
next if (ref($vals{$v}) eq "HASH");
if (ref($vals{$v}) eq "ARRAY") {
printPropertyList($f, $level+1, $v, @{$vals{$v}});
}
elsif ($vals{$v} ne ZERO_LENGTH_PROPERTY) {
printProperty($f, $level+1, $v, $vals{$v});
}
else {
printZeroLengthProperty($f, $level+1, $v);
}
}
#Print Includes
foreach my $v (sort keys %vals) {
if ($v eq "DTSI_INCLUDE") {
#print 1 include per line
my @incs = split(',', $vals{$v});
foreach my $i (@incs) {
print $f qq(#include "$i"\n);
}
}
}
#Print Nodes
foreach my $v (sort keys %vals) {
if (ref($vals{$v}) eq "HASH") {
printNode($f, $level+1, $v, %{$vals{$v}});
}
}
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 qq("$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) = @_;
my $quoteChar = qq(");
$val = convertReference($val);
#properties with < > or single word aliases don't need quotes
if (($val =~ /<.*>/) || ($val =~ /^&\w+$/)) {
$quoteChar = "";
}
print $f indent($level) . "$name = $quoteChar$val$quoteChar;\n";
}
#Prints a zero length property e.g. some-property;
# $f = file handle
# $level = indent level (0,1,etc)
# $name = name of property
sub printZeroLengthProperty
{
my ($f, $level, $name) = @_;
print $f indent($level) . "$name;\n";
}
#Replace '(ref)' with '&'.
#Needed because Serverwiz doesn't properly escape '&'s in the XML,
#so the '(ref)' string is used to represent the reference
#specifier instead of '&'.
sub convertReference
{
my $val = shift;
$val =~ s/\(ref\)/&/g;
return $val
}
#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.
#The files to include come from the configuration file.
# $f = file handle
# $type = include type
sub printIncludes
{
my ($f, $type) = @_;
my @includes = getIncludes($type);
foreach my $i (@includes) {
#if a .dtsi, gets " ", otherwise < >
if ($i =~ /\.dtsi$/) {
$i = qq("$i");
}
else {
$i = "<$i>";
}
print $f "#include $i\n";
}
}
#Returns an array of include files found in the config file
#for the type specified.
# $type = the include type, which is the section name in the
# YAML configuration file.
sub getIncludes
{
my $type = shift;
my @includes;
#The config file may have a section but no includes
#listed in it, which is OK.
if ((exists $g_configuration{includes}{$type}) &&
(ref($g_configuration{includes}{$type}) eq "ARRAY")) {
@includes = @{$g_configuration{includes}{$type}};
}
return @includes;
}
#Appends the first value of the 'reg' property
#passed in to the name passed in to create the
#full name for the node
# $name = node name that will be appended to
# $reg = the reg property values
sub makeNodeName
{
my ($name, $reg) = @_;
$reg =~ s/<//g;
$reg =~ s/>//g;
my @vals = split(' ', $reg);
if (scalar @vals > 0) {
$vals[0] =~ s/0x//;
$name .= "@" . lc $vals[0];
}
return $name;
}
#Prints the root node starting bracket.
# $f = file handle
sub printRootNodeStart
{
my $f = shift;
print $f qq(/ {\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).qq(};\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);
}
#Adds a {status} = "okay" element to the hash passed in.
# $node = reference to the hash to add element to
sub statusOK
{
my $node = shift;
$node->{status} = "okay";
}
#Adds the {reg} element to the hash passed in using the values
#passed in. Resulting value looks like: "<val1 val2 etc>"
# $node = reference to the hash to add element to
# @values = the values for the property. May be passed in one at
# a time and not as an array.
sub addRegProp
{
my $node = shift;
my @values = @_;
$node->{reg} = "<";
for (my $i = 0; $i < scalar @values; $i++) {
$node->{reg} .= $values[$i];
if ($i < (scalar @values) - 1) {
$node->{reg} .= " ";
}
}
$node->{reg} .= ">";
}
sub printUsage
{
print "gen_devtree.pl -x [XML filename] -y [yaml config file] " .
"-o [output filename]\n";
exit(1);
}