blob: 0b3ca66e4f0021c2dd7d96783b7cf042bacedabb [file] [log] [blame]
#include "config.h"
#include "sensor.hpp"
#include "env.hpp"
#include "gpio_handle.hpp"
#include "hwmon.hpp"
#include "sensorset.hpp"
#include "sysfs.hpp"
#include <fmt/format.h>
#include <phosphor-logging/elog-errors.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <xyz/openbmc_project/Sensor/Device/error.hpp>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <future>
#include <thread>
namespace sensor
{
using namespace phosphor::logging;
using namespace sdbusplus::xyz::openbmc_project::Common::Error;
// todo: this can be simplified once we move to the double interface
Sensor::Sensor(const SensorSet::key_type& sensor,
const hwmonio::HwmonIOInterface* ioAccess,
const std::string& devPath) :
_sensor(sensor),
_ioAccess(ioAccess), _devPath(devPath), _scale(0), _hasFaultFile(false)
{
auto chip = env::getEnv("GPIOCHIP", sensor);
auto access = env::getEnv("GPIO", sensor);
if (!access.empty() && !chip.empty())
{
_handle = gpio::BuildGpioHandle(chip, access);
if (!_handle)
{
log<level::ERR>("Unable to set up gpio locking");
elog<InternalFailure>();
}
}
auto gain = env::getEnv("GAIN", sensor);
if (!gain.empty())
{
_sensorAdjusts.gain = std::stod(gain);
}
auto offset = env::getEnv("OFFSET", sensor);
if (!offset.empty())
{
_sensorAdjusts.offset = std::stoi(offset);
}
auto senRmRCs = env::getEnv("REMOVERCS", sensor);
// Add sensor removal return codes defined per sensor
addRemoveRCs(senRmRCs);
}
void Sensor::addRemoveRCs(const std::string& rcList)
{
if (rcList.empty())
{
return;
}
// Convert to a char* for strtok
std::vector<char> rmRCs(rcList.c_str(), rcList.c_str() + rcList.size() + 1);
auto rmRC = std::strtok(&rmRCs[0], ", ");
while (rmRC != nullptr)
{
try
{
_sensorAdjusts.rmRCs.insert(std::stoi(rmRC));
}
catch (const std::logic_error& le)
{
// Unable to convert to int, continue to next token
std::string name = _sensor.first + "_" + _sensor.second;
log<level::INFO>("Unable to convert sensor removal return code",
entry("SENSOR=%s", name.c_str()),
entry("RC=%s", rmRC),
entry("EXCEPTION=%s", le.what()));
}
rmRC = std::strtok(nullptr, ", ");
}
}
SensorValueType Sensor::adjustValue(SensorValueType value)
{
// Because read doesn't have an out pointer to store errors.
// let's assume negative values are errors if they have this
// set.
#if NEGATIVE_ERRNO_ON_FAIL
if (value < 0)
{
return value;
}
#endif
// Adjust based on gain and offset
value = static_cast<decltype(value)>(static_cast<double>(value) *
_sensorAdjusts.gain +
_sensorAdjusts.offset);
if constexpr (std::is_same<SensorValueType, double>::value)
{
value *= std::pow(10, _scale);
}
return value;
}
std::shared_ptr<ValueObject> Sensor::addValue(const RetryIO& retryIO,
ObjectInfo& info,
TimedoutMap& timedoutMap)
{
// Get the initial value for the value interface.
auto& bus = *std::get<sdbusplus::bus_t*>(info);
auto& obj = std::get<InterfaceMap>(info);
auto& objPath = std::get<std::string>(info);
SensorValueType val = 0;
auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>(
obj[InterfaceType::STATUS]);
// As long as addStatus is called before addValue, statusIface
// should never be nullptr
assert(statusIface);
// Only read the input value if the status is functional
if (statusIface->functional())
{
#if UPDATE_FUNCTIONAL_ON_FAIL
try
#endif
{
// RAII object for GPIO unlock / lock
auto locker = gpioUnlock(getGpio());
// For sensors with attribute ASYNC_READ_TIMEOUT,
// spawn a thread with timeout
auto asyncReadTimeout = env::getEnv("ASYNC_READ_TIMEOUT", _sensor);
if (!asyncReadTimeout.empty())
{
std::chrono::milliseconds asyncTimeout{
std::stoi(asyncReadTimeout)};
val = asyncRead(_sensor, _ioAccess, asyncTimeout, timedoutMap,
_sensor.first, _sensor.second,
hwmon::entry::cinput, std::get<size_t>(retryIO),
std::get<std::chrono::milliseconds>(retryIO));
}
else
{
// Retry for up to a second if device is busy
// or has a transient error.
val = _ioAccess->read(
_sensor.first, _sensor.second, hwmon::entry::cinput,
std::get<size_t>(retryIO),
std::get<std::chrono::milliseconds>(retryIO));
}
}
#if UPDATE_FUNCTIONAL_ON_FAIL
catch (const std::system_error& e)
{
// Catch the exception here and update the functional property.
// By catching the exception, it will not propagate it up the stack
// and thus the code will skip the "Remove RCs" check in
// MainLoop::getObject and will not exit on failure.
statusIface->functional(false);
}
#endif
}
auto iface = std::make_shared<ValueObject>(bus, objPath.c_str(),
ValueObject::action::defer_emit);
hwmon::Attributes attrs;
if (hwmon::getAttributes(_sensor.first, attrs))
{
iface->unit(hwmon::getUnit(attrs));
_scale = hwmon::getScale(attrs);
}
val = adjustValue(val);
iface->value(val);
auto maxValue = env::getEnv("MAXVALUE", _sensor);
if (!maxValue.empty())
{
iface->maxValue(std::stoll(maxValue));
}
auto minValue = env::getEnv("MINVALUE", _sensor);
if (!minValue.empty())
{
iface->minValue(std::stoll(minValue));
}
obj[InterfaceType::VALUE] = iface;
return iface;
}
std::shared_ptr<StatusObject> Sensor::addStatus(ObjectInfo& info)
{
namespace fs = std::filesystem;
std::shared_ptr<StatusObject> iface = nullptr;
auto& objPath = std::get<std::string>(info);
auto& obj = std::get<InterfaceMap>(info);
// Check if fault sysfs file exists
std::string faultName = _sensor.first;
std::string faultID = _sensor.second;
std::string entry = hwmon::entry::fault;
bool functional = true;
auto sysfsFullPath = sysfs::make_sysfs_path(_ioAccess->path(), faultName,
faultID, entry);
if (fs::exists(sysfsFullPath))
{
_hasFaultFile = true;
try
{
uint32_t fault = _ioAccess->read(faultName, faultID, entry,
hwmonio::retries, hwmonio::delay);
if (fault != 0)
{
functional = false;
}
}
catch (const std::system_error& e)
{
using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::
Error;
using metadata = xyz::openbmc_project::Sensor::Device::ReadFailure;
report<ReadFailure>(
metadata::CALLOUT_ERRNO(e.code().value()),
metadata::CALLOUT_DEVICE_PATH(_devPath.c_str()));
log<level::INFO>(fmt::format("Failing sysfs file: {} errno {}",
sysfsFullPath, e.code().value())
.c_str());
}
}
auto& bus = *std::get<sdbusplus::bus_t*>(info);
iface = std::make_shared<StatusObject>(
bus, objPath.c_str(), StatusObject::action::emit_no_signals);
// Set functional property
iface->functional(functional);
obj[InterfaceType::STATUS] = iface;
return iface;
}
std::shared_ptr<AccuracyObject> Sensor::addAccuracy(ObjectInfo& info,
double accuracy)
{
auto& objPath = std::get<std::string>(info);
auto& obj = std::get<InterfaceMap>(info);
auto& bus = *std::get<sdbusplus::bus_t*>(info);
auto iface = std::make_shared<AccuracyObject>(
bus, objPath.c_str(), AccuracyObject::action::emit_no_signals);
iface->accuracy(accuracy);
obj[InterfaceType::ACCURACY] = iface;
return iface;
}
std::shared_ptr<PriorityObject> Sensor::addPriority(ObjectInfo& info,
size_t priority)
{
auto& objPath = std::get<std::string>(info);
auto& obj = std::get<InterfaceMap>(info);
auto& bus = *std::get<sdbusplus::bus_t*>(info);
auto iface = std::make_shared<PriorityObject>(
bus, objPath.c_str(), PriorityObject::action::emit_no_signals);
iface->priority(priority);
obj[InterfaceType::PRIORITY] = iface;
return iface;
}
void gpioLock(const gpioplus::HandleInterface*&& handle)
{
handle->setValues({0});
}
std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle)
{
if (handle == nullptr)
{
return std::nullopt;
}
handle->setValues({1});
// Default pause needed to guarantee sensors are ready
std::this_thread::sleep_for(std::chrono::milliseconds(500));
return GpioLocker(std::move(handle));
}
SensorValueType asyncRead(const SensorSet::key_type& sensorSetKey,
const hwmonio::HwmonIOInterface* ioAccess,
std::chrono::milliseconds asyncTimeout,
TimedoutMap& timedoutMap, const std::string& type,
const std::string& id, const std::string& sensor,
const size_t retries,
const std::chrono::milliseconds delay)
{
// Default async read timeout
bool valueIsValid = false;
std::future<int64_t> asyncThread;
auto asyncIter = timedoutMap.find(sensorSetKey);
if (asyncIter == timedoutMap.end())
{
// If sensor not found in timedoutMap, spawn an async thread
asyncThread = std::async(std::launch::async,
&hwmonio::HwmonIOInterface::read, ioAccess,
type, id, sensor, retries, delay);
valueIsValid = true;
}
else
{
// If we already have the async thread in the timedoutMap, it means this
// sensor has already timed out in the previous reads. No need to wait
// on subsequent reads - proceed to check the future_status to see when
// the async thread finishes
asyncTimeout = std::chrono::seconds(0);
asyncThread = std::move(asyncIter->second);
}
// TODO: This is still not a true asynchronous read as it still blocks the
// main thread for asyncTimeout amount of time. To make this completely
// asynchronous, schedule a read and register a callback to update the
// sensor value
std::future_status status = asyncThread.wait_for(asyncTimeout);
switch (status)
{
case std::future_status::ready:
// Read has finished
if (valueIsValid)
{
return asyncThread.get();
// Good sensor reads should skip the code below
}
// Async read thread has completed but had previously timed out (was
// found in the timedoutMap). Erase from timedoutMap and throw to
// allow retry in the next read cycle. Not returning the read value
// as the sensor reading may be bad / corrupted if it took so long.
timedoutMap.erase(sensorSetKey);
throw AsyncSensorReadTimeOut();
default:
// Read timed out so add the thread to the timedoutMap (if the entry
// already exists, operator[] updates it).
//
// Keeping the timed out futures in a map is required to prevent
// their destructor from being called when returning from this
// stack. The destructor will otherwise block until the read
// completes due to the limitation of std::async.
timedoutMap[sensorSetKey] = std::move(asyncThread);
throw AsyncSensorReadTimeOut();
}
}
} // namespace sensor