blob: 8a3030074b3c7d789fba040daf9b371331b40026 [file] [log] [blame]
Matt Spinlerabf8da32017-04-27 14:08:45 -05001/**
2 * Copyright © 2017 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Matt Spinlerabf8da32017-04-27 14:08:45 -050016#include "fan.hpp"
Matthew Barth177fe982020-05-26 11:05:19 -050017
Matt Spinlerae1f8ef2020-10-14 16:15:51 -050018#include "logging.hpp"
Matthew Barth177fe982020-05-26 11:05:19 -050019#include "sdbusplus.hpp"
Matt Spinlerb0412d02020-10-12 16:53:52 -050020#include "system.hpp"
Matt Spinlerabf8da32017-04-27 14:08:45 -050021#include "types.hpp"
Matt Spinlerb1e18512017-04-27 14:42:33 -050022#include "utility.hpp"
Matthew Barth177fe982020-05-26 11:05:19 -050023
Jay Meyera7aed012020-10-06 14:32:22 -050024#include <fmt/format.h>
25
Matthew Barth177fe982020-05-26 11:05:19 -050026#include <phosphor-logging/log.hpp>
27
28#include <algorithm>
Matt Spinlerabf8da32017-04-27 14:08:45 -050029
30namespace phosphor
31{
32namespace fan
33{
34namespace monitor
35{
36
37using namespace phosphor::logging;
Matt Spinlerb0412d02020-10-12 16:53:52 -050038using namespace sdbusplus::bus::match;
Matt Spinlerabf8da32017-04-27 14:08:45 -050039
Matthew Barth177fe982020-05-26 11:05:19 -050040Fan::Fan(Mode mode, sdbusplus::bus::bus& bus, const sdeventplus::Event& event,
Matt Spinlerb0412d02020-10-12 16:53:52 -050041 std::unique_ptr<trust::Manager>& trust, const FanDefinition& def,
42 System& system) :
Matt Spinlerabf8da32017-04-27 14:08:45 -050043 _bus(bus),
44 _name(std::get<fanNameField>(def)),
45 _deviation(std::get<fanDeviationField>(def)),
Matt Spinlerc39e8592017-09-28 13:13:08 -050046 _numSensorFailsForNonFunc(std::get<numSensorFailsForNonfuncField>(def)),
Matt Spinlerb0412d02020-10-12 16:53:52 -050047 _trustManager(trust),
48#ifdef MONITOR_USE_JSON
49 _monitorDelay(std::get<monitorStartDelayField>(def)),
50 _monitorTimer(event, std::bind(std::mem_fn(&Fan::startMonitor), this)),
51#endif
Matt Spinlerb63aa092020-10-14 09:45:11 -050052 _system(system),
53 _presenceMatch(bus,
54 rules::propertiesChanged(util::INVENTORY_PATH + _name,
55 util::INV_ITEM_IFACE),
56 std::bind(std::mem_fn(&Fan::presenceChanged), this,
Matt Spinler27f6b682020-10-27 08:43:37 -050057 std::placeholders::_1)),
Matt Spinler7d135642021-02-04 12:44:17 -060058 _presenceIfaceAddedMatch(
59 bus,
60 rules::interfacesAdded() +
61 rules::argNpath(0, util::INVENTORY_PATH + _name),
62 std::bind(std::mem_fn(&Fan::presenceIfaceAdded), this,
63 std::placeholders::_1)),
Matt Spinler27f6b682020-10-27 08:43:37 -050064 _fanMissingErrorDelay(std::get<fanMissingErrDelayField>(def))
Matt Spinlerabf8da32017-04-27 14:08:45 -050065{
Matt Spinlerae1f8ef2020-10-14 16:15:51 -050066 // Start from a known state of functional (even if
67 // _numSensorFailsForNonFunc is 0)
Jolie Ku4c3c24f2020-09-09 11:01:46 +080068 updateInventory(true);
69
Matthew Barth0a9fe162018-01-26 12:53:15 -060070 // Setup tach sensors for monitoring
71 auto& sensors = std::get<sensorListField>(def);
72 for (auto& s : sensors)
73 {
Matt Spinler4283c5d2021-03-01 15:56:00 -060074 _sensors.emplace_back(std::make_shared<TachSensor>(
75 mode, bus, *this, std::get<sensorNameField>(s),
76 std::get<hasTargetField>(s), std::get<funcDelay>(def),
77 std::get<targetInterfaceField>(s), std::get<factorField>(s),
78 std::get<offsetField>(s), std::get<methodField>(def),
79 std::get<thresholdField>(s), std::get<timeoutField>(def),
80 std::get<nonfuncRotorErrDelayField>(def), event));
Matthew Barth0a9fe162018-01-26 12:53:15 -060081
Matt Spinler4283c5d2021-03-01 15:56:00 -060082 _trustManager->registerSensor(_sensors.back());
Matthew Barth0a9fe162018-01-26 12:53:15 -060083 }
84
Matt Spinlerb0412d02020-10-12 16:53:52 -050085#ifndef MONITOR_USE_JSON
Matthew Barth0a9fe162018-01-26 12:53:15 -060086 // Check current tach state when entering monitor mode
Matthew Barth6ad28432017-08-22 11:18:19 -050087 if (mode != Mode::init)
88 {
Matt Spinlerb0412d02020-10-12 16:53:52 -050089 _monitorReady = true;
90
Matthew Barth177fe982020-05-26 11:05:19 -050091 // The TachSensors will now have already read the input
92 // and target values, so check them.
Matthew Barth6ad28432017-08-22 11:18:19 -050093 tachChanged();
94 }
Matt Spinlerb0412d02020-10-12 16:53:52 -050095#else
Matt Spinler7d135642021-02-04 12:44:17 -060096 if (_system.isPowerOn())
97 {
98 _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay));
99 }
Matt Spinlerb0412d02020-10-12 16:53:52 -0500100#endif
Matt Spinlerb63aa092020-10-14 09:45:11 -0500101
Matt Spinler27f6b682020-10-27 08:43:37 -0500102 if (_fanMissingErrorDelay)
103 {
104 _fanMissingErrorTimer = std::make_unique<
105 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
106 event, std::bind(&System::fanMissingErrorTimerExpired, &system,
107 std::ref(*this)));
Matt Spinler7d135642021-02-04 12:44:17 -0600108 }
Matt Spinler27f6b682020-10-27 08:43:37 -0500109
Matt Spinler7d135642021-02-04 12:44:17 -0600110 try
111 {
112 _present = util::SDBusPlus::getProperty<bool>(
113 util::INVENTORY_PATH + _name, util::INV_ITEM_IFACE, "Present");
114
115 if (!_present)
Matt Spinler27f6b682020-10-27 08:43:37 -0500116 {
Matt Spinlerac372972021-01-25 15:11:22 -0600117 getLogger().log(
118 fmt::format("On startup, fan {} is missing", _name));
Matt Spinler7d135642021-02-04 12:44:17 -0600119 if (_system.isPowerOn() && _fanMissingErrorTimer)
120 {
121 _fanMissingErrorTimer->restartOnce(
122 std::chrono::seconds{*_fanMissingErrorDelay});
123 }
124 }
125 }
126 catch (const util::DBusServiceError& e)
127 {
128 // This could happen on the first BMC boot if the presence
129 // detect app hasn't started yet and there isn't an inventory
130 // cache yet.
131 }
132}
133
134void Fan::presenceIfaceAdded(sdbusplus::message::message& msg)
135{
136 sdbusplus::message::object_path path;
137 std::map<std::string, std::map<std::string, std::variant<bool>>> interfaces;
138
139 msg.read(path, interfaces);
140
141 auto properties = interfaces.find(util::INV_ITEM_IFACE);
142 if (properties == interfaces.end())
143 {
144 return;
145 }
146
147 auto property = properties->second.find("Present");
148 if (property == properties->second.end())
149 {
150 return;
151 }
152
153 _present = std::get<bool>(property->second);
154
155 if (!_present)
156 {
157 getLogger().log(fmt::format(
158 "New fan {} interface added and fan is not present", _name));
159 if (_system.isPowerOn() && _fanMissingErrorTimer)
160 {
Matt Spinler27f6b682020-10-27 08:43:37 -0500161 _fanMissingErrorTimer->restartOnce(
162 std::chrono::seconds{*_fanMissingErrorDelay});
163 }
164 }
Matt Spinler7d135642021-02-04 12:44:17 -0600165
166 _system.fanStatusChange(*this);
Matt Spinlerb0412d02020-10-12 16:53:52 -0500167}
168
169void Fan::startMonitor()
170{
171 _monitorReady = true;
172
Matt Spinler4283c5d2021-03-01 15:56:00 -0600173 std::for_each(_sensors.begin(), _sensors.end(), [this](auto& sensor) {
174 if (_present)
175 {
176 try
177 {
178 // Force a getProperty call to check if the tach sensor is
179 // on D-Bus. If it isn't, now set it to nonfunctional.
180 // This isn't done earlier so that code watching for
181 // nonfunctional tach sensors doesn't take actions before
182 // those sensors show up on D-Bus.
183 sensor->updateTachAndTarget();
184 tachChanged(*sensor);
185 }
186 catch (const util::DBusServiceError& e)
187 {
188 // The tach property still isn't on D-Bus, ensure
189 // sensor is nonfunctional.
190 getLogger().log(fmt::format(
191 "Monitoring starting but {} sensor value not on D-Bus",
192 sensor->name()));
193
194 sensor->setFunctional(false);
195
196 if (_numSensorFailsForNonFunc)
197 {
198 if (_functional && (countNonFunctionalSensors() >=
199 _numSensorFailsForNonFunc))
200 {
201 updateInventory(false);
202 }
203 }
204
205 _system.fanStatusChange(*this);
206 }
207 }
208 });
Matt Spinlerabf8da32017-04-27 14:08:45 -0500209}
210
Matt Spinlerebaae612017-04-27 14:21:48 -0500211void Fan::tachChanged()
212{
Matt Spinlerb0412d02020-10-12 16:53:52 -0500213 if (_monitorReady)
Matt Spinlerebaae612017-04-27 14:21:48 -0500214 {
Matt Spinlerb0412d02020-10-12 16:53:52 -0500215 for (auto& s : _sensors)
216 {
217 tachChanged(*s);
218 }
Matt Spinlerebaae612017-04-27 14:21:48 -0500219 }
220}
221
Matt Spinlerebaae612017-04-27 14:21:48 -0500222void Fan::tachChanged(TachSensor& sensor)
223{
Matt Spinler7d135642021-02-04 12:44:17 -0600224 if (!_system.isPowerOn() || !_monitorReady)
225 {
226 return;
227 }
228
Matt Spinlerc39e8592017-09-28 13:13:08 -0500229 if (_trustManager->active())
230 {
231 if (!_trustManager->checkTrust(sensor))
232 {
233 return;
234 }
235 }
236
Matthew Barthfcb0dbc2021-02-10 14:23:39 -0600237 process(sensor);
238}
239
240void Fan::process(TachSensor& sensor)
241{
Matthew Barth177fe982020-05-26 11:05:19 -0500242 // If this sensor is out of range at this moment, start
243 // its timer, at the end of which the inventory
244 // for the fan may get updated to not functional.
Matt Spinlera4c8f1f2017-04-27 14:38:38 -0500245
Matthew Barth177fe982020-05-26 11:05:19 -0500246 // If this sensor is OK, put everything back into a good state.
Matt Spinlera4c8f1f2017-04-27 14:38:38 -0500247
248 if (outOfRange(sensor))
249 {
Matthew Barthe11cbc62018-02-20 12:11:07 -0600250 if (sensor.functional())
Matt Spinlera4c8f1f2017-04-27 14:38:38 -0500251 {
Jolie Ku69f2f482020-10-21 09:59:43 +0800252 switch (sensor.getMethod())
253 {
254 case MethodMode::timebased:
255 // Start nonfunctional timer if not already running
256 sensor.startTimer(TimerMode::nonfunc);
257 break;
258 case MethodMode::count:
259 sensor.setCounter(true);
260 if (sensor.getCounter() >= sensor.getThreshold())
261 {
262 updateState(sensor);
263 }
264 break;
265 }
Matt Spinlera4c8f1f2017-04-27 14:38:38 -0500266 }
267 }
268 else
269 {
Jolie Ku69f2f482020-10-21 09:59:43 +0800270 switch (sensor.getMethod())
Matt Spinlera4c8f1f2017-04-27 14:38:38 -0500271 {
Jolie Ku69f2f482020-10-21 09:59:43 +0800272 case MethodMode::timebased:
273 if (sensor.functional())
274 {
Matthew Barth11b5d8f2021-01-28 14:04:09 -0600275 if (sensor.timerRunning())
276 {
277 sensor.stopTimer();
278 }
Jolie Ku69f2f482020-10-21 09:59:43 +0800279 }
280 else
281 {
282 // Start functional timer if not already running
283 sensor.startTimer(TimerMode::func);
284 }
285 break;
286 case MethodMode::count:
287 sensor.setCounter(false);
288 if (!sensor.functional() && sensor.getCounter() == 0)
289 {
290 updateState(sensor);
291 }
292 break;
Matt Spinlera4c8f1f2017-04-27 14:38:38 -0500293 }
294 }
Matt Spinlerebaae612017-04-27 14:21:48 -0500295}
296
Matthew Barthf552ea52018-01-15 16:22:04 -0600297uint64_t Fan::findTargetSpeed()
Matt Spinlerabf8da32017-04-27 14:08:45 -0500298{
299 uint64_t target = 0;
Matthew Barth177fe982020-05-26 11:05:19 -0500300 // The sensor doesn't support a target,
301 // so get it from another sensor.
Matthew Barthf552ea52018-01-15 16:22:04 -0600302 auto s = std::find_if(_sensors.begin(), _sensors.end(),
Matthew Barth177fe982020-05-26 11:05:19 -0500303 [](const auto& s) { return s->hasTarget(); });
Matt Spinlerabf8da32017-04-27 14:08:45 -0500304
Matthew Barthf552ea52018-01-15 16:22:04 -0600305 if (s != _sensors.end())
Matt Spinlerabf8da32017-04-27 14:08:45 -0500306 {
Matthew Barthf552ea52018-01-15 16:22:04 -0600307 target = (*s)->getTarget();
Matt Spinlerabf8da32017-04-27 14:08:45 -0500308 }
309
310 return target;
311}
312
Matthew Barth7c23a042021-01-26 16:21:45 -0600313size_t Fan::countNonFunctionalSensors()
Matt Spinlerabf8da32017-04-27 14:08:45 -0500314{
Matthew Barth7c23a042021-01-26 16:21:45 -0600315 return std::count_if(_sensors.begin(), _sensors.end(),
316 [](const auto& s) { return !s->functional(); });
Matt Spinlerabf8da32017-04-27 14:08:45 -0500317}
318
Matt Spinlerabf8da32017-04-27 14:08:45 -0500319bool Fan::outOfRange(const TachSensor& sensor)
320{
321 auto actual = static_cast<uint64_t>(sensor.getInput());
Matthew Barth7c23a042021-01-26 16:21:45 -0600322 auto range = sensor.getRange(_deviation);
Matt Spinlerabf8da32017-04-27 14:08:45 -0500323
Matthew Barth7c23a042021-01-26 16:21:45 -0600324 if ((actual < range.first) || (actual > range.second))
Matt Spinlerabf8da32017-04-27 14:08:45 -0500325 {
326 return true;
327 }
328
329 return false;
330}
331
Jolie Ku69f2f482020-10-21 09:59:43 +0800332void Fan::updateState(TachSensor& sensor)
Matt Spinlera9406a72017-04-27 14:29:24 -0500333{
Matthew Barth7c23a042021-01-26 16:21:45 -0600334 auto range = sensor.getRange(_deviation);
Matt Spinler7d135642021-02-04 12:44:17 -0600335
336 if (!_system.isPowerOn())
337 {
338 return;
339 }
340
Matthew Barthe11cbc62018-02-20 12:11:07 -0600341 sensor.setFunctional(!sensor.functional());
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500342 getLogger().log(
343 fmt::format("Setting tach sensor {} functional state to {}. "
Matthew Barth7c23a042021-01-26 16:21:45 -0600344 "[target = {}, input = {}, allowed range = ({} - {})]",
345 sensor.name(), sensor.functional(), sensor.getTarget(),
346 sensor.getInput(), range.first, range.second));
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500347
348 // A zero value for _numSensorFailsForNonFunc means we aren't dealing
349 // with fan FRU functional status, only sensor functional status.
350 if (_numSensorFailsForNonFunc)
Matthew Barthe11cbc62018-02-20 12:11:07 -0600351 {
Matthew Barth7c23a042021-01-26 16:21:45 -0600352 auto numNonFuncSensors = countNonFunctionalSensors();
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500353 // If the fan was nonfunctional and enough sensors are now OK,
Matthew Barth7c23a042021-01-26 16:21:45 -0600354 // the fan can be set to functional
355 if (!_functional && !(numNonFuncSensors >= _numSensorFailsForNonFunc))
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500356 {
Matthew Barth7c23a042021-01-26 16:21:45 -0600357 getLogger().log(fmt::format("Setting fan {} to functional, number "
358 "of nonfunctional sensors = {}",
359 _name, numNonFuncSensors));
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500360 updateInventory(true);
361 }
Matt Spinlera9406a72017-04-27 14:29:24 -0500362
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500363 // If the fan is currently functional, but too many
364 // contained sensors are now nonfunctional, update
Matthew Barth7c23a042021-01-26 16:21:45 -0600365 // the fan to nonfunctional.
366 if (_functional && (numNonFuncSensors >= _numSensorFailsForNonFunc))
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500367 {
Matthew Barth7c23a042021-01-26 16:21:45 -0600368 getLogger().log(fmt::format("Setting fan {} to nonfunctional, "
369 "number of nonfunctional sensors = {}",
370 _name, numNonFuncSensors));
Matt Spinlerae1f8ef2020-10-14 16:15:51 -0500371 updateInventory(false);
372 }
Matt Spinlerb1e18512017-04-27 14:42:33 -0500373 }
Matt Spinlerb63aa092020-10-14 09:45:11 -0500374
375 _system.fanStatusChange(*this);
Matt Spinlera9406a72017-04-27 14:29:24 -0500376}
377
Matt Spinlerb1e18512017-04-27 14:42:33 -0500378void Fan::updateInventory(bool functional)
379{
Matthew Barth177fe982020-05-26 11:05:19 -0500380 auto objectMap =
381 util::getObjMap<bool>(_name, util::OPERATIONAL_STATUS_INTF,
382 util::FUNCTIONAL_PROPERTY, functional);
Matthew Barth51dd1852017-11-16 15:21:13 -0600383 auto response = util::SDBusPlus::lookupAndCallMethod(
Matthew Barth177fe982020-05-26 11:05:19 -0500384 _bus, util::INVENTORY_PATH, util::INVENTORY_INTF, "Notify", objectMap);
Matt Spinlerb1e18512017-04-27 14:42:33 -0500385 if (response.is_method_error())
386 {
387 log<level::ERR>("Error in Notify call to update inventory");
388 return;
389 }
390
Matthew Barth177fe982020-05-26 11:05:19 -0500391 // This will always track the current state of the inventory.
Matt Spinlerb1e18512017-04-27 14:42:33 -0500392 _functional = functional;
393}
394
Matt Spinlerb63aa092020-10-14 09:45:11 -0500395void Fan::presenceChanged(sdbusplus::message::message& msg)
396{
397 std::string interface;
398 std::map<std::string, std::variant<bool>> properties;
399
400 msg.read(interface, properties);
401
402 auto presentProp = properties.find("Present");
403 if (presentProp != properties.end())
404 {
405 _present = std::get<bool>(presentProp->second);
406
Matt Spinler27f6b682020-10-27 08:43:37 -0500407 getLogger().log(
Matt Spinlerac372972021-01-25 15:11:22 -0600408 fmt::format("Fan {} presence state change to {}", _name, _present));
Matt Spinler27f6b682020-10-27 08:43:37 -0500409
Matt Spinlerb63aa092020-10-14 09:45:11 -0500410 _system.fanStatusChange(*this);
Matt Spinler27f6b682020-10-27 08:43:37 -0500411
412 if (_fanMissingErrorDelay)
413 {
Matt Spinler7d135642021-02-04 12:44:17 -0600414 if (!_present && _system.isPowerOn())
Matt Spinler27f6b682020-10-27 08:43:37 -0500415 {
416 _fanMissingErrorTimer->restartOnce(
417 std::chrono::seconds{*_fanMissingErrorDelay});
418 }
Matt Spinler7d135642021-02-04 12:44:17 -0600419 else if (_present && _fanMissingErrorTimer->isEnabled())
Matt Spinler27f6b682020-10-27 08:43:37 -0500420 {
421 _fanMissingErrorTimer->setEnabled(false);
422 }
423 }
Matt Spinlerb63aa092020-10-14 09:45:11 -0500424 }
425}
Matt Spinlerf13b42e2020-10-26 15:29:49 -0500426
427void Fan::sensorErrorTimerExpired(const TachSensor& sensor)
428{
Matt Spinler7d135642021-02-04 12:44:17 -0600429 if (_present && _system.isPowerOn())
Matt Spinlerf13b42e2020-10-26 15:29:49 -0500430 {
431 _system.sensorErrorTimerExpired(*this, sensor);
432 }
433}
434
Matt Spinler7d135642021-02-04 12:44:17 -0600435void Fan::powerStateChanged(bool powerStateOn)
436{
437#ifdef MONITOR_USE_JSON
438 if (powerStateOn)
439 {
Matt Spinler7d135642021-02-04 12:44:17 -0600440 _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay));
441
Matt Spinler4283c5d2021-03-01 15:56:00 -0600442 if (_present)
443 {
444 std::for_each(
445 _sensors.begin(), _sensors.end(), [this](auto& sensor) {
446 try
447 {
448 // Force a getProperty call. If sensor is on D-Bus,
449 // then make sure it's functional.
450 sensor->updateTachAndTarget();
451
452 // If not functional, set it back to functional.
453 if (!sensor->functional())
454 {
455 sensor->setFunctional(true);
456 _system.fanStatusChange(*this, true);
457 }
458 }
459 catch (const util::DBusServiceError& e)
460 {
461 // Properties still aren't on D-Bus. Let startMonitor()
462 // deal with it.
463 getLogger().log(fmt::format(
464 "At power on, tach sensor {} value not on D-Bus",
465 sensor->name()));
466 }
467 });
468
469 // If configured to change functional state on the fan itself,
470 // Set it back to true now if necessary.
471 if (_numSensorFailsForNonFunc)
472 {
473 if (!_functional &&
474 (countNonFunctionalSensors() < _numSensorFailsForNonFunc))
475 {
476 updateInventory(true);
477 }
478 }
479 }
480 else
Matt Spinler7d135642021-02-04 12:44:17 -0600481 {
482 getLogger().log(
483 fmt::format("At power on, fan {} is missing", _name));
484
485 if (_fanMissingErrorTimer)
486 {
487 _fanMissingErrorTimer->restartOnce(
488 std::chrono::seconds{*_fanMissingErrorDelay});
489 }
490 }
491 }
492 else
493 {
494 _monitorReady = false;
495
496 if (_monitorTimer.isEnabled())
497 {
498 _monitorTimer.setEnabled(false);
499 }
500
501 if (_fanMissingErrorTimer && _fanMissingErrorTimer->isEnabled())
502 {
503 _fanMissingErrorTimer->setEnabled(false);
504 }
505
506 std::for_each(_sensors.begin(), _sensors.end(), [](auto& sensor) {
507 if (sensor->timerRunning())
508 {
509 sensor->stopTimer();
510 }
511 });
512 }
513#endif
514}
515
Matthew Barth177fe982020-05-26 11:05:19 -0500516} // namespace monitor
517} // namespace fan
518} // namespace phosphor