blob: 3b12905e253ee5db9ec7e2540a3b9ac1105a8b7f [file] [log] [blame]
/**
* Copyright © 2017 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 "sdbusplus.hpp"
#include "types.hpp"
#include "utility.hpp"
#include <fmt/format.h>
#include <phosphor-logging/log.hpp>
#include <algorithm>
namespace phosphor
{
namespace fan
{
namespace monitor
{
using namespace phosphor::logging;
Fan::Fan(Mode mode, sdbusplus::bus::bus& bus, const sdeventplus::Event& event,
std::unique_ptr<trust::Manager>& trust, const FanDefinition& def) :
_bus(bus),
_name(std::get<fanNameField>(def)),
_deviation(std::get<fanDeviationField>(def)),
_numSensorFailsForNonFunc(std::get<numSensorFailsForNonfuncField>(def)),
_trustManager(trust)
{
// Start from a known state of functional
updateInventory(true);
// Setup tach sensors for monitoring
auto& sensors = std::get<sensorListField>(def);
for (auto& s : sensors)
{
try
{
_sensors.emplace_back(std::make_shared<TachSensor>(
mode, bus, *this, std::get<sensorNameField>(s),
std::get<hasTargetField>(s), std::get<funcDelay>(def),
std::get<targetInterfaceField>(s), std::get<factorField>(s),
std::get<offsetField>(s), std::get<timeoutField>(def), event));
_trustManager->registerSensor(_sensors.back());
}
catch (InvalidSensorError& e)
{
// Count the number of failed tach sensors
if (++_numFailedSensor >= _numSensorFailsForNonFunc)
{
// Mark associated fan as nonfunctional
updateInventory(false);
}
}
}
// Check current tach state when entering monitor mode
if (mode != Mode::init)
{
// The TachSensors will now have already read the input
// and target values, so check them.
tachChanged();
}
}
void Fan::tachChanged()
{
for (auto& s : _sensors)
{
tachChanged(*s);
}
}
void Fan::tachChanged(TachSensor& sensor)
{
if (_trustManager->active())
{
if (!_trustManager->checkTrust(sensor))
{
return;
}
}
// 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())
{
// Start nonfunctional timer if not already running
sensor.startTimer(TimerMode::nonfunc);
}
}
else
{
if (sensor.functional())
{
sensor.stopTimer();
}
else
{
// Start functional timer if not already running
sensor.startTimer(TimerMode::func);
}
}
}
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;
}
bool Fan::tooManySensorsNonfunctional()
{
size_t numFailed =
std::count_if(_sensors.begin(), _sensors.end(),
[](const auto& s) { return !s->functional(); });
return (numFailed >= _numSensorFailsForNonFunc);
}
bool Fan::outOfRange(const TachSensor& sensor)
{
auto actual = static_cast<uint64_t>(sensor.getInput());
auto target = sensor.getTarget();
auto factor = sensor.getFactor();
auto offset = sensor.getOffset();
uint64_t min = target * (100 - _deviation) / 100;
uint64_t max = target * (100 + _deviation) / 100;
// TODO: openbmc/openbmc#2937 enhance this function
// either by making it virtual, or by predefining different
// outOfRange ops and selecting by yaml config
min = min * factor + offset;
max = max * factor + offset;
if ((actual < min) || (actual > max))
{
return true;
}
return false;
}
void Fan::timerExpired(TachSensor& sensor)
{
sensor.setFunctional(!sensor.functional());
// If the fan was nonfunctional and enough sensors are now OK,
// the fan can go back to functional
if (!_functional && !tooManySensorsNonfunctional())
{
log<level::INFO>(
fmt::format("Setting fan {} back to functional", _name).c_str());
updateInventory(true);
}
// If the fan is currently functional, but too many
// contained sensors are now nonfunctional, update
// the whole fan nonfunctional.
if (_functional && tooManySensorsNonfunctional())
{
log<level::ERR>(fmt::format("Setting fan {} to nonfunctional "
"Sensor: {} "
"Actual speed: {} "
"Target speed: {}",
_name, sensor.name(), sensor.getInput(),
sensor.getTarget())
.c_str());
updateInventory(false);
}
}
void Fan::updateInventory(bool functional)
{
auto objectMap =
util::getObjMap<bool>(_name, util::OPERATIONAL_STATUS_INTF,
util::FUNCTIONAL_PROPERTY, functional);
auto response = util::SDBusPlus::lookupAndCallMethod(
_bus, util::INVENTORY_PATH, util::INVENTORY_INTF, "Notify", objectMap);
if (response.is_method_error())
{
log<level::ERR>("Error in Notify call to update inventory");
return;
}
// This will always track the current state of the inventory.
_functional = functional;
}
} // namespace monitor
} // namespace fan
} // namespace phosphor