/** * Copyright © 2022 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "fan.hpp" #include "logging.hpp" #include "sdbusplus.hpp" #include "system.hpp" #include "types.hpp" #include "utility.hpp" #include #include namespace phosphor { namespace fan { namespace monitor { using namespace phosphor::logging; using namespace sdbusplus::bus::match; Fan::Fan(Mode mode, sdbusplus::bus_t& bus, const sdeventplus::Event& event, std::unique_ptr& trust, const FanDefinition& def, System& system) : _bus(bus), _name(def.name), _deviation(def.deviation), _upperDeviation(def.upperDeviation), _numSensorFailsForNonFunc(def.numSensorFailsForNonfunc), _trustManager(trust), #ifdef MONITOR_USE_JSON _monitorDelay(def.monitorStartDelay), _monitorTimer(event, std::bind(std::mem_fn(&Fan::startMonitor), this)), #endif _system(system), _presenceMatch(bus, rules::propertiesChanged(util::INVENTORY_PATH + _name, util::INV_ITEM_IFACE), std::bind(std::mem_fn(&Fan::presenceChanged), this, std::placeholders::_1)), _presenceIfaceAddedMatch( bus, rules::interfacesAdded() + rules::argNpath(0, util::INVENTORY_PATH + _name), std::bind(std::mem_fn(&Fan::presenceIfaceAdded), this, std::placeholders::_1)), _fanMissingErrorDelay(def.fanMissingErrDelay), _setFuncOnPresent(def.funcOnPresent) { // Setup tach sensors for monitoring for (const auto& s : def.sensorList) { _sensors.emplace_back(std::make_shared( mode, bus, *this, s.name, s.hasTarget, def.funcDelay, s.targetInterface, s.targetPath, s.factor, s.offset, def.method, s.threshold, s.ignoreAboveMax, def.timeout, def.nonfuncRotorErrDelay, def.countInterval, event)); _trustManager->registerSensor(_sensors.back()); } bool functionalState = (_numSensorFailsForNonFunc == 0) || (countNonFunctionalSensors() < _numSensorFailsForNonFunc); if (updateInventory(functionalState) && !functionalState) { // the inventory update threw an exception, possibly because D-Bus // wasn't ready. Try to update sensors back to functional to avoid a // false-alarm. They will be updated again from subscribing to the // properties-changed event for (auto& sensor : _sensors) sensor->setFunctional(true); } #ifndef MONITOR_USE_JSON // Check current tach state when entering monitor mode if (mode != Mode::init) { _monitorReady = true; // The TachSensors will now have already read the input // and target values, so check them. tachChanged(); } #else if (_system.isPowerOn()) { _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay)); } #endif if (_fanMissingErrorDelay) { _fanMissingErrorTimer = std::make_unique< sdeventplus::utility::Timer>( event, std::bind(&System::fanMissingErrorTimerExpired, &system, std::ref(*this))); } try { _present = util::SDBusPlus::getProperty( util::INVENTORY_PATH + _name, util::INV_ITEM_IFACE, "Present"); if (!_present) { getLogger().log( std::format("On startup, fan {} is missing", _name)); if (_system.isPowerOn() && _fanMissingErrorTimer) { _fanMissingErrorTimer->restartOnce( std::chrono::seconds{*_fanMissingErrorDelay}); } } } catch (const util::DBusServiceError& e) { // This could happen on the first BMC boot if the presence // detect app hasn't started yet and there isn't an inventory // cache yet. } } void Fan::presenceIfaceAdded(sdbusplus::message_t& msg) { sdbusplus::message::object_path path; std::map>> interfaces; msg.read(path, interfaces); auto properties = interfaces.find(util::INV_ITEM_IFACE); if (properties == interfaces.end()) { return; } auto property = properties->second.find("Present"); if (property == properties->second.end()) { return; } _present = std::get(property->second); if (!_present) { getLogger().log(std::format( "New fan {} interface added and fan is not present", _name)); if (_system.isPowerOn() && _fanMissingErrorTimer) { _fanMissingErrorTimer->restartOnce( std::chrono::seconds{*_fanMissingErrorDelay}); } } _system.fanStatusChange(*this); } void Fan::startMonitor() { _monitorReady = true; std::for_each(_sensors.begin(), _sensors.end(), [this](auto& sensor) { try { // Force a getProperty call to check if the tach sensor is // on D-Bus. If it isn't, now set it to nonfunctional. // This isn't done earlier so that code watching for // nonfunctional tach sensors doesn't take actions before // those sensors show up on D-Bus. sensor->updateTachAndTarget(); tachChanged(*sensor); } catch (const util::DBusServiceError& e) { // The tach property still isn't on D-Bus. Ensure // sensor is nonfunctional, but skip creating an // error for it since it isn't a fan problem. getLogger().log(std::format( "Monitoring starting but {} sensor value not on D-Bus", sensor->name())); sensor->setFunctional(false, true); if (_numSensorFailsForNonFunc) { if (_functional && (countNonFunctionalSensors() >= _numSensorFailsForNonFunc)) { updateInventory(false); } } // At this point, don't start any power off actions due // to missing sensors. Let something else handle that // policy. _system.fanStatusChange(*this, true); } }); } void Fan::tachChanged() { if (_monitorReady) { for (auto& s : _sensors) { tachChanged(*s); } } } void Fan::tachChanged(TachSensor& sensor) { if (!_system.isPowerOn() || !_monitorReady) { return; } if (_trustManager->active()) { if (!_trustManager->checkTrust(sensor)) { return; } } // If the error checking method is 'count', if a tach change leads // to an out of range sensor the count timer will take over in calling // process() until the sensor is healthy again. if (!sensor.countTimerRunning()) { process(sensor); } } void Fan::countTimerExpired(TachSensor& sensor) { if (_trustManager->active() && !_trustManager->checkTrust(sensor)) { return; } process(sensor); } void Fan::process(TachSensor& sensor) { // If this sensor is out of range at this moment, start // its timer, at the end of which the inventory // for the fan may get updated to not functional. // If this sensor is OK, put everything back into a good state. if (outOfRange(sensor)) { if (sensor.functional()) { switch (sensor.getMethod()) { case MethodMode::timebased: // Start nonfunctional timer if not already running sensor.startTimer(TimerMode::nonfunc); break; case MethodMode::count: if (!sensor.countTimerRunning()) { sensor.startCountTimer(); } sensor.setCounter(true); if (sensor.getCounter() >= sensor.getThreshold()) { updateState(sensor); } break; } } } else { switch (sensor.getMethod()) { case MethodMode::timebased: if (sensor.functional()) { if (sensor.timerRunning()) { sensor.stopTimer(); } } else { // Start functional timer if not already running sensor.startTimer(TimerMode::func); } break; case MethodMode::count: sensor.setCounter(false); if (sensor.getCounter() == 0) { if (!sensor.functional()) { updateState(sensor); } sensor.stopCountTimer(); } break; } } } uint64_t Fan::findTargetSpeed() { uint64_t target = 0; // The sensor doesn't support a target, // so get it from another sensor. auto s = std::find_if(_sensors.begin(), _sensors.end(), [](const auto& s) { return s->hasTarget(); }); if (s != _sensors.end()) { target = (*s)->getTarget(); } return target; } size_t Fan::countNonFunctionalSensors() const { return std::count_if(_sensors.begin(), _sensors.end(), [](const auto& s) { return !s->functional(); }); } bool Fan::outOfRange(const TachSensor& sensor) { if (!sensor.hasOwner()) { return true; } auto actual = static_cast(sensor.getInput()); auto range = sensor.getRange(_deviation, _upperDeviation); return ((actual < range.first) || (range.second && actual > range.second.value())); } void Fan::updateState(TachSensor& sensor) { if (!_system.isPowerOn()) { return; } auto range = sensor.getRange(_deviation, _upperDeviation); std::string rangeMax = "NoMax"; if (range.second) { rangeMax = std::to_string(range.second.value()); } // Skip starting the error timer if the sensor // isn't on D-Bus as this isn't a fan hardware problem. sensor.setFunctional(!sensor.functional(), !sensor.hasOwner()); getLogger().log(std::format( "Setting tach sensor {} functional state to {}. " "[target = {}, actual = {}, allowed range = ({} - {}) " "owned = {}]", sensor.name(), sensor.functional(), sensor.getTarget(), sensor.getInput(), range.first, rangeMax, sensor.hasOwner())); // A zero value for _numSensorFailsForNonFunc means we aren't dealing // with fan FRU functional status, only sensor functional status. if (_numSensorFailsForNonFunc) { auto numNonFuncSensors = countNonFunctionalSensors(); // If the fan was nonfunctional and enough sensors are now OK, // the fan can be set to functional as long as `set_func_on_present` was // not set if (!_setFuncOnPresent && !_functional && !(numNonFuncSensors >= _numSensorFailsForNonFunc)) { getLogger().log(std::format("Setting fan {} to functional, number " "of nonfunctional sensors = {}", _name, numNonFuncSensors)); updateInventory(true); } // If the fan is currently functional, but too many // contained sensors are now nonfunctional, update // the fan to nonfunctional. if (_functional && (numNonFuncSensors >= _numSensorFailsForNonFunc)) { getLogger().log(std::format("Setting fan {} to nonfunctional, " "number of nonfunctional sensors = {}", _name, numNonFuncSensors)); updateInventory(false); } } // Skip the power off rule checks if the sensor isn't // on D-Bus so a running system isn't shutdown. _system.fanStatusChange(*this, !sensor.hasOwner()); } bool Fan::updateInventory(bool functional) { bool dbusError = false; try { auto objectMap = util::getObjMap(_name, util::OPERATIONAL_STATUS_INTF, util::FUNCTIONAL_PROPERTY, functional); auto response = util::SDBusPlus::callMethod( _bus, util::INVENTORY_SVC, util::INVENTORY_PATH, util::INVENTORY_INTF, "Notify", objectMap); if (response.is_method_error()) { log("Error in Notify call to update inventory"); dbusError = true; } } catch (const util::DBusError& e) { dbusError = true; getLogger().log( std::format("D-Bus Exception reading/updating inventory : {}", e.what()), Logger::error); } // This will always track the current state of the inventory. _functional = functional; return dbusError; } void Fan::presenceChanged(sdbusplus::message_t& msg) { std::string interface; std::map> properties; msg.read(interface, properties); auto presentProp = properties.find("Present"); if (presentProp != properties.end()) { _present = std::get(presentProp->second); getLogger().log( std::format("Fan {} presence state change to {}", _name, _present)); if (_present && _setFuncOnPresent) { updateInventory(true); std::for_each(_sensors.begin(), _sensors.end(), [](auto& sensor) { sensor->setFunctional(true); sensor->resetMethod(); }); } _system.fanStatusChange(*this); if (_fanMissingErrorDelay) { if (!_present && _system.isPowerOn()) { _fanMissingErrorTimer->restartOnce( std::chrono::seconds{*_fanMissingErrorDelay}); } else if (_present && _fanMissingErrorTimer->isEnabled()) { _fanMissingErrorTimer->setEnabled(false); } } } } void Fan::sensorErrorTimerExpired(const TachSensor& sensor) { if (_present && _system.isPowerOn()) { _system.sensorErrorTimerExpired(*this, sensor); } } void Fan::powerStateChanged([[maybe_unused]] bool powerStateOn) { #ifdef MONITOR_USE_JSON if (powerStateOn) { _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay)); _numSensorsOnDBusAtPowerOn = 0; std::for_each(_sensors.begin(), _sensors.end(), [this](auto& sensor) { try { // Force a getProperty call. If sensor is on D-Bus, // then make sure it's functional. sensor->updateTachAndTarget(); _numSensorsOnDBusAtPowerOn++; if (_present) { // If not functional, set it back to functional. if (!sensor->functional()) { sensor->setFunctional(true); _system.fanStatusChange(*this, true); } // Set the counters back to zero if (sensor->getMethod() == MethodMode::count) { sensor->resetMethod(); } } } catch (const util::DBusError& e) { // Properties still aren't on D-Bus. Let startMonitor() // deal with it, or maybe System::powerStateChanged() if // there aren't any sensors at all on D-Bus. getLogger().log(std::format( "At power on, tach sensor {} value not on D-Bus", sensor->name())); } }); if (_present) { // If configured to change functional state on the fan itself, // Set it back to true now if necessary. if (_numSensorFailsForNonFunc) { if (!_functional && (countNonFunctionalSensors() < _numSensorFailsForNonFunc)) { updateInventory(true); } } } else { getLogger().log( std::format("At power on, fan {} is missing", _name)); if (_fanMissingErrorTimer) { _fanMissingErrorTimer->restartOnce( std::chrono::seconds{*_fanMissingErrorDelay}); } } } else { _monitorReady = false; if (_monitorTimer.isEnabled()) { _monitorTimer.setEnabled(false); } if (_fanMissingErrorTimer && _fanMissingErrorTimer->isEnabled()) { _fanMissingErrorTimer->setEnabled(false); } std::for_each(_sensors.begin(), _sensors.end(), [](auto& sensor) { if (sensor->timerRunning()) { sensor->stopTimer(); } sensor->stopCountTimer(); }); } #endif } } // namespace monitor } // namespace fan } // namespace phosphor