xref: /openbmc/phosphor-mrw-tools/hwmon.pl (revision 9a1fa83d)
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