#include "config.h" #include "sensor.hpp" #include "env.hpp" #include "gpio_handle.hpp" #include "hwmon.hpp" #include "sensorset.hpp" #include "sysfs.hpp" #include #include #include #include #include #include #include #include #include #include #include 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("Unable to set up gpio locking"); elog(); } } 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 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("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( static_cast(value) * _sensorAdjusts.gain + _sensorAdjusts.offset); if constexpr (std::is_same::value) { value *= std::pow(10, _scale); } return value; } std::shared_ptr Sensor::addValue( const RetryIO& retryIO, ObjectInfo& info, TimedoutMap& timedoutMap) { // Get the initial value for the value interface. auto& bus = *std::get(info); auto& obj = std::get(info); auto& objPath = std::get(info); SensorValueType val = 0; auto& statusIface = std::any_cast&>( 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(retryIO), std::get(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(retryIO), std::get(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(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 Sensor::addStatus(ObjectInfo& info) { namespace fs = std::filesystem; std::shared_ptr iface = nullptr; auto& objPath = std::get(info); auto& obj = std::get(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( metadata::CALLOUT_ERRNO(e.code().value()), metadata::CALLOUT_DEVICE_PATH(_devPath.c_str())); log(std::format("Failing sysfs file: {} errno {}", sysfsFullPath, e.code().value()) .c_str()); } } auto& bus = *std::get(info); iface = std::make_shared( bus, objPath.c_str(), StatusObject::action::emit_no_signals); // Set functional property iface->functional(functional); obj[InterfaceType::STATUS] = iface; return iface; } std::shared_ptr Sensor::addAccuracy(ObjectInfo& info, double accuracy) { auto& objPath = std::get(info); auto& obj = std::get(info); auto& bus = *std::get(info); auto iface = std::make_shared( bus, objPath.c_str(), AccuracyObject::action::emit_no_signals); iface->accuracy(accuracy); obj[InterfaceType::ACCURACY] = iface; return iface; } std::shared_ptr Sensor::addPriority(ObjectInfo& info, size_t priority) { auto& objPath = std::get(info); auto& obj = std::get(info); auto& bus = *std::get(info); auto iface = std::make_shared( 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 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 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