1#!/usr/bin/env perl 2 3#Creates a configuration file for each hwmon sensor in the MRW 4#for use by the phosphor-hwmon daemon. These configuration files 5#contain labels and thresholds for the hwmon features for that sensor. 6#The files are created in subdirectories based on their device 7#tree paths. 8 9use strict; 10use warnings; 11 12use mrw::Targets; 13use mrw::Util; 14use Getopt::Long; 15use File::Path qw(make_path); 16 17use constant { 18 I2C_TYPE => "i2c" 19}; 20 21my $serverwizFile; 22my $g_outputDir; 23my @hwmon; 24 25GetOptions("x=s" => \$serverwizFile, 26 "d=s" => \$g_outputDir) or printUsage(); 27 28if (not defined $serverwizFile) { 29 printUsage(); 30} 31 32my $g_targetObj = Targets->new; 33$g_targetObj->loadXML($serverwizFile); 34 35my $bmc = Util::getBMCTarget($g_targetObj); 36 37getI2CSensors($bmc, \@hwmon); 38 39makeConfFiles($bmc, \@hwmon); 40 41exit 0; 42 43 44#Returns an array of hashes that represent hwmon enabled I2C sensors. 45sub getI2CSensors 46{ 47 my ($bmc, $hwmon) = @_; 48 my $connections = $g_targetObj->findConnections($bmc, "I2C"); 49 50 return if ($connections eq ""); 51 52 for my $i2c (@{$connections->{CONN}}) { 53 54 my $chip = $i2c->{DEST_PARENT}; 55 my @hwmonUnits = Util::getChildUnitsWithTargetType($g_targetObj, 56 "unit-hwmon-feature", 57 $chip); 58 59 #If the MRW doesn't specify a label for a particular hwmon 60 #feature, then we don't want to use it. 61 removeUnusedHwmons(\@hwmonUnits); 62 63 #If chip didn't have hwmon units, it isn't hwmon enabled. 64 next unless (scalar @hwmonUnits > 0); 65 66 my %entry; 67 $entry{type} = I2C_TYPE; 68 $entry{name} = lc $g_targetObj->getInstanceName($chip); 69 getHwmonAttributes(\@hwmonUnits, \%entry); 70 getI2CAttributes($i2c, \%entry); 71 72 push @$hwmon, { %entry }; 73 } 74} 75 76 77#Removes entries from the list of hwmon units passed in that have 78#an empty HWMON_NAME or DESCRIPTIVE_NAME attribute. 79sub removeUnusedHwmons 80{ 81 my ($units) = @_; 82 my $i = 0; 83 84 while ($i <= $#$units) { 85 86 my $hwmon = $g_targetObj->getAttributeField($$units[$i], 87 "HWMON_FEATURE", 88 "HWMON_NAME"); 89 my $name = $g_targetObj->getAttributeField($$units[$i], 90 "HWMON_FEATURE", 91 "DESCRIPTIVE_NAME"); 92 if (($hwmon eq "") || ($name eq "")) { 93 splice(@$units, $i, 1); 94 } 95 else { 96 $i++; 97 } 98 } 99} 100 101 102#Reads the hwmon related attributes from the HWMON_FEATURE 103#complex attribute and adds them to the hash. 104sub getHwmonAttributes 105{ 106 my ($units, $entry) = @_; 107 my %hwmonFeatures; 108 109 for my $unit (@$units) { 110 111 #The hwmon name, like 'in1', 'temp1', 'fan1', etc 112 my $hwmon = $g_targetObj->getAttributeField($unit, 113 "HWMON_FEATURE", 114 "HWMON_NAME"); 115 116 #The useful name for this feature, like 'ambient' 117 my $name = $g_targetObj->getAttributeField($unit, 118 "HWMON_FEATURE", 119 "DESCRIPTIVE_NAME"); 120 $hwmonFeatures{$hwmon}{label} = $name; 121 122 #Thresholds are optional, ignore if NA 123 my $warnHigh = $g_targetObj->getAttributeField($unit, 124 "HWMON_FEATURE", 125 "WARN_HIGH"); 126 if (($warnHigh ne "") && ($warnHigh ne "NA")) { 127 $hwmonFeatures{$hwmon}{warnhigh} = $warnHigh; 128 } 129 130 my $warnLow = $g_targetObj->getAttributeField($unit, 131 "HWMON_FEATURE", 132 "WARN_LOW"); 133 if (($warnLow ne "") && ($warnLow ne "NA")) { 134 $hwmonFeatures{$hwmon}{warnlow} = $warnLow; 135 } 136 137 my $critHigh = $g_targetObj->getAttributeField($unit, 138 "HWMON_FEATURE", 139 "CRIT_HIGH"); 140 if (($critHigh ne "") && ($critHigh ne "NA")) { 141 $hwmonFeatures{$hwmon}{crithigh} = $critHigh; 142 } 143 144 my $critLow = $g_targetObj->getAttributeField($unit, 145 "HWMON_FEATURE", 146 "CRIT_LOW"); 147 if (($critLow ne "") && ($critHigh ne "NA")) { 148 $hwmonFeatures{$hwmon}{critlow} = $critLow; 149 } 150 } 151 152 $entry->{hwmon} = { %hwmonFeatures }; 153} 154 155 156#Reads the I2C attributes for the chip and adds them to the hash. 157#This includes the i2C address, and register base address and 158#offset for the I2C bus the chip is on. 159sub getI2CAttributes 160{ 161 my ($i2c, $entry) = @_; 162 163 #The address comes from the destination unit, and needs 164 #to be the 7 bit value in hex without the 0x. 165 my $addr = $g_targetObj->getAttribute($i2c->{DEST}, "I2C_ADDRESS"); 166 $addr = hex($addr) >> 1; 167 $entry->{addr} = sprintf("%x", $addr); 168 169 #The reg base address and offset may be optional depending on 170 #the BMC chip type. We'll check later if it's required but missing. 171 if (!$g_targetObj->isBadAttribute($i2c->{SOURCE}, "REG_BASE_ADDRESS")) { 172 my $addr = $g_targetObj->getAttribute($i2c->{SOURCE}, 173 "REG_BASE_ADDRESS"); 174 $entry->{regBaseAddress} = sprintf("%x", hex($addr)); 175 } 176 177 if (!$g_targetObj->isBadAttribute($i2c->{SOURCE}, "REG_OFFSET")) { 178 my $offset = $g_targetObj->getAttribute($i2c->{SOURCE}, 179 "REG_OFFSET"); 180 $entry->{regOffset} = sprintf("%x", hex($offset)); 181 } 182} 183 184 185#Creates .conf files for each chip. 186sub makeConfFiles 187{ 188 my ($bmc, $hwmon) = @_; 189 190 for my $entry (@$hwmon) { 191 printConfFile($bmc, $entry); 192 } 193} 194 195 196#Writes out a configuration file for a hwmon sensor, containing: 197# LABEL_<feature> = <descriptive label> (e.g. LABEL_temp1 = ambient) 198# WARNHI_<feature> = <value> (e.g. WARNHI_temp1 = 99) 199# WARNLO_<feature> = <value> (e.g. WARNLO_temp1 = 0) 200# CRITHI_<feature> = <value> (e.g. CRITHI_temp1 = 100) 201# CRITHI_<feature> = <value> (e.g. CRITLO_temp1 = -1) 202# 203# The file is created in a subdirectory based on the chip's device 204# tree path. 205sub printConfFile 206{ 207 my ($bmc, $entry) = @_; 208 my $path = getConfFilePath($bmc, $entry); 209 my $name = $path . "/" . getConfFileName($entry); 210 211 make_path($path); 212 213 open(my $f, ">$name") or die "Could not open $name\n"; 214 215 for my $feature (sort keys %{$entry->{hwmon}}) { 216 print $f "LABEL_$feature = \"$entry->{hwmon}{$feature}{label}\"\n"; 217 218 #Thresholds are optional 219 if (exists $entry->{hwmon}{$feature}{warnhigh}) { 220 print $f "WARNHI_$feature = \"$entry->{hwmon}{$feature}{warnhigh}\"\n"; 221 } 222 if (exists $entry->{hwmon}{$feature}{warnlow}) { 223 print $f "WARNLO_$feature = \"$entry->{hwmon}{$feature}{warnlow}\"\n"; 224 } 225 if (exists $entry->{hwmon}{$feature}{crithigh}) { 226 print $f "CRITHI_$feature = \"$entry->{hwmon}{$feature}{crithigh}\"\n"; 227 } 228 if (exists $entry->{hwmon}{$feature}{critlow}) { 229 print $f "CRITLO_$feature = \"$entry->{hwmon}{$feature}{critlow}\"\n"; 230 } 231 } 232 233 close $f; 234} 235 236 237#Returns the chip's configuration file path. 238sub getConfFilePath 239{ 240 my ($bmc, $entry) = @_; 241 242 my $mfgr = $g_targetObj->getAttribute($bmc, "MANUFACTURER"); 243 244 #Unfortunately, because the conf file path is based on the 245 #device tree path which is tied to the internal chip structure, 246 #this has to be model specific. Until proven wrong, I'm going 247 #to make an assumption that all ASPEED chips have the same path 248 #as so far all of the models I've seen do. 249 if ($mfgr eq "ASPEED") { 250 return getAspeedConfFilePath($entry); 251 } 252 else { 253 die "Unsupported BMC manufacturer $mfgr\n"; 254 } 255} 256 257 258#Returns the relative path of the configuration file to create. 259#This path is based on the path of the chip in the device tree. 260#An example path is ahb/apb/i2c@1e78a000/i2c-bus@400/ 261sub getAspeedConfFilePath 262{ 263 my ($entry) = @_; 264 my $path; 265 266 if ($entry->{type} eq I2C_TYPE) { 267 268 #ASPEED requires the reg base address & offset fields 269 if ((not exists $entry->{regBaseAddress}) || 270 (not exists $entry->{regOffset})) { 271 die "Missing regBaseAddress or regOffset attributes " . 272 "in the I2C master unit XML\n"; 273 } 274 275 $path = "$g_outputDir/ahb/apb/i2c\@$entry->{regBaseAddress}/i2c-bus@" . 276 "$entry->{regOffset}"; 277 } 278 else { 279 #TODO: FSI support for the OCC when known 280 die "HWMON bus type $entry->{type} not implemented yet\n"; 281 } 282 283 return $path; 284} 285 286 287#Returns the name to use for the conf file: 288# <name>@<addr>.conf (e.g. rtc@68.conf) 289sub getConfFileName 290{ 291 my ($entry) = @_; 292 return "$entry->{name}\@$entry->{addr}.conf"; 293} 294 295 296sub printUsage 297{ 298 print "$0 -x [XML filename] -d [output base directory]\n"; 299 exit(1); 300} 301