blob: b784997fa024fea0fb64e017dd08534835fa695d [file] [log] [blame]
#!/usr/bin/env perl
#Creates a configuration file for each hwmon sensor in the MRW
#for use by the phosphor-hwmon daemon. These configuration files
#contain labels and thresholds for the hwmon features for that sensor.
#The files are created in subdirectories based on their device
#tree paths.
use strict;
use warnings;
use mrw::Targets;
use mrw::Util;
use Getopt::Long;
use File::Path qw(make_path);
use constant {
I2C_TYPE => "i2c"
};
my $serverwizFile;
my $g_outputDir;
my @hwmon;
GetOptions("x=s" => \$serverwizFile,
"d=s" => \$g_outputDir) or printUsage();
if (not defined $serverwizFile) {
printUsage();
}
my $g_targetObj = Targets->new;
$g_targetObj->loadXML($serverwizFile);
my $bmc = Util::getBMCTarget($g_targetObj);
getI2CSensors($bmc, \@hwmon);
makeConfFiles($bmc, \@hwmon);
exit 0;
#Returns an array of hashes that represent hwmon enabled I2C sensors.
sub getI2CSensors
{
my ($bmc, $hwmon) = @_;
my $connections = $g_targetObj->findConnections($bmc, "I2C");
return if ($connections eq "");
for my $i2c (@{$connections->{CONN}}) {
my $chip = $i2c->{DEST_PARENT};
my @hwmonUnits = Util::getChildUnitsWithTargetType($g_targetObj,
"unit-hwmon-feature",
$chip);
#If the MRW doesn't specify a label for a particular hwmon
#feature, then we don't want to use it.
removeUnusedHwmons(\@hwmonUnits);
#If chip didn't have hwmon units, it isn't hwmon enabled.
next unless (scalar @hwmonUnits > 0);
my %entry;
$entry{type} = I2C_TYPE;
$entry{name} = lc $g_targetObj->getInstanceName($chip);
getHwmonAttributes(\@hwmonUnits, \%entry);
getI2CAttributes($i2c, \%entry);
push @$hwmon, { %entry };
}
}
#Removes entries from the list of hwmon units passed in that have
#an empty HWMON_NAME or DESCRIPTIVE_NAME attribute.
sub removeUnusedHwmons
{
my ($units) = @_;
my $i = 0;
while ($i <= $#$units) {
my $hwmon = $g_targetObj->getAttributeField($$units[$i],
"HWMON_FEATURE",
"HWMON_NAME");
my $name = $g_targetObj->getAttributeField($$units[$i],
"HWMON_FEATURE",
"DESCRIPTIVE_NAME");
if (($hwmon eq "") || ($name eq "")) {
splice(@$units, $i, 1);
}
else {
$i++;
}
}
}
#Reads the hwmon related attributes from the HWMON_FEATURE
#complex attribute and adds them to the hash.
sub getHwmonAttributes
{
my ($units, $entry) = @_;
my %hwmonFeatures;
for my $unit (@$units) {
#The hwmon name, like 'in1', 'temp1', 'fan1', etc
my $hwmon = $g_targetObj->getAttributeField($unit,
"HWMON_FEATURE",
"HWMON_NAME");
#The useful name for this feature, like 'ambient'
my $name = $g_targetObj->getAttributeField($unit,
"HWMON_FEATURE",
"DESCRIPTIVE_NAME");
$hwmonFeatures{$hwmon}{label} = $name;
#Thresholds are optional, ignore if NA
my $warnHigh = $g_targetObj->getAttributeField($unit,
"HWMON_FEATURE",
"WARN_HIGH");
if (($warnHigh ne "") && ($warnHigh ne "NA")) {
$hwmonFeatures{$hwmon}{warnhigh} = $warnHigh;
}
my $warnLow = $g_targetObj->getAttributeField($unit,
"HWMON_FEATURE",
"WARN_LOW");
if (($warnLow ne "") && ($warnLow ne "NA")) {
$hwmonFeatures{$hwmon}{warnlow} = $warnLow;
}
my $critHigh = $g_targetObj->getAttributeField($unit,
"HWMON_FEATURE",
"CRIT_HIGH");
if (($critHigh ne "") && ($critHigh ne "NA")) {
$hwmonFeatures{$hwmon}{crithigh} = $critHigh;
}
my $critLow = $g_targetObj->getAttributeField($unit,
"HWMON_FEATURE",
"CRIT_LOW");
if (($critLow ne "") && ($critHigh ne "NA")) {
$hwmonFeatures{$hwmon}{critlow} = $critLow;
}
}
$entry->{hwmon} = { %hwmonFeatures };
}
#Reads the I2C attributes for the chip and adds them to the hash.
#This includes the i2C address, and register base address and
#offset for the I2C bus the chip is on.
sub getI2CAttributes
{
my ($i2c, $entry) = @_;
#The address comes from the destination unit, and needs
#to be the 7 bit value in hex without the 0x.
my $addr = $g_targetObj->getAttribute($i2c->{DEST}, "I2C_ADDRESS");
$addr = hex($addr) >> 1;
$entry->{addr} = sprintf("%x", $addr);
#The reg base address and offset may be optional depending on
#the BMC chip type. We'll check later if it's required but missing.
if (!$g_targetObj->isBadAttribute($i2c->{SOURCE}, "REG_BASE_ADDRESS")) {
my $addr = $g_targetObj->getAttribute($i2c->{SOURCE},
"REG_BASE_ADDRESS");
$entry->{regBaseAddress} = sprintf("%x", hex($addr));
}
if (!$g_targetObj->isBadAttribute($i2c->{SOURCE}, "REG_OFFSET")) {
my $offset = $g_targetObj->getAttribute($i2c->{SOURCE},
"REG_OFFSET");
$entry->{regOffset} = sprintf("%x", hex($offset));
}
}
#Creates .conf files for each chip.
sub makeConfFiles
{
my ($bmc, $hwmon) = @_;
for my $entry (@$hwmon) {
printConfFile($bmc, $entry);
}
}
#Writes out a configuration file for a hwmon sensor, containing:
# LABEL_<feature> = <descriptive label> (e.g. LABEL_temp1 = ambient)
# WARNHI_<feature> = <value> (e.g. WARNHI_temp1 = 99)
# WARNLO_<feature> = <value> (e.g. WARNLO_temp1 = 0)
# CRITHI_<feature> = <value> (e.g. CRITHI_temp1 = 100)
# CRITHI_<feature> = <value> (e.g. CRITLO_temp1 = -1)
#
# The file is created in a subdirectory based on the chip's device
# tree path.
sub printConfFile
{
my ($bmc, $entry) = @_;
my $path = getConfFilePath($bmc, $entry);
my $name = $path . "/" . getConfFileName($entry);
make_path($path);
open(my $f, ">$name") or die "Could not open $name\n";
for my $feature (sort keys %{$entry->{hwmon}}) {
print $f "LABEL_$feature = \"$entry->{hwmon}{$feature}{label}\"\n";
#Thresholds are optional
if (exists $entry->{hwmon}{$feature}{warnhigh}) {
print $f "WARNHI_$feature = \"$entry->{hwmon}{$feature}{warnhigh}\"\n";
}
if (exists $entry->{hwmon}{$feature}{warnlow}) {
print $f "WARNLO_$feature = \"$entry->{hwmon}{$feature}{warnlow}\"\n";
}
if (exists $entry->{hwmon}{$feature}{crithigh}) {
print $f "CRITHI_$feature = \"$entry->{hwmon}{$feature}{crithigh}\"\n";
}
if (exists $entry->{hwmon}{$feature}{critlow}) {
print $f "CRITLO_$feature = \"$entry->{hwmon}{$feature}{critlow}\"\n";
}
}
close $f;
}
#Returns the chip's configuration file path.
sub getConfFilePath
{
my ($bmc, $entry) = @_;
my $mfgr = $g_targetObj->getAttribute($bmc, "MANUFACTURER");
#Unfortunately, because the conf file path is based on the
#device tree path which is tied to the internal chip structure,
#this has to be model specific. Until proven wrong, I'm going
#to make an assumption that all ASPEED chips have the same path
#as so far all of the models I've seen do.
if ($mfgr eq "ASPEED") {
return getAspeedConfFilePath($entry);
}
else {
die "Unsupported BMC manufacturer $mfgr\n";
}
}
#Returns the relative path of the configuration file to create.
#This path is based on the path of the chip in the device tree.
#An example path is ahb/apb/i2c@1e78a000/i2c-bus@400/
sub getAspeedConfFilePath
{
my ($entry) = @_;
my $path;
if ($entry->{type} eq I2C_TYPE) {
#ASPEED requires the reg base address & offset fields
if ((not exists $entry->{regBaseAddress}) ||
(not exists $entry->{regOffset})) {
die "Missing regBaseAddress or regOffset attributes " .
"in the I2C master unit XML\n";
}
$path = "$g_outputDir/ahb/apb/i2c\@$entry->{regBaseAddress}/i2c-bus@" .
"$entry->{regOffset}";
}
else {
#TODO: FSI support for the OCC when known
die "HWMON bus type $entry->{type} not implemented yet\n";
}
return $path;
}
#Returns the name to use for the conf file:
# <name>@<addr>.conf (e.g. rtc@68.conf)
sub getConfFileName
{
my ($entry) = @_;
return "$entry->{name}\@$entry->{addr}.conf";
}
sub printUsage
{
print "$0 -x [XML filename] -d [output base directory]\n";
exit(1);
}