|  | #include "config.h" | 
|  |  | 
|  | #include "sensor.hpp" | 
|  |  | 
|  | #include "env.hpp" | 
|  | #include "gpio_handle.hpp" | 
|  | #include "hwmon.hpp" | 
|  | #include "sensorset.hpp" | 
|  | #include "sysfs.hpp" | 
|  |  | 
|  | #include <cmath> | 
|  | #include <cstring> | 
|  | #include <experimental/filesystem> | 
|  | #include <phosphor-logging/elog-errors.hpp> | 
|  | #include <thread> | 
|  | #include <xyz/openbmc_project/Common/error.hpp> | 
|  | #include <xyz/openbmc_project/Sensor/Device/error.hpp> | 
|  |  | 
|  | namespace sensor | 
|  | { | 
|  |  | 
|  | using namespace phosphor::logging; | 
|  | using namespace sdbusplus::xyz::openbmc_project::Common::Error; | 
|  |  | 
|  | // todo: this can be deleted once we move to double | 
|  | // helper class to set the scale on the value iface only when it's available | 
|  | template <typename T> | 
|  | void setScale(T& iface, int64_t value, double) | 
|  | { | 
|  | } | 
|  | template <typename T> | 
|  | void setScale(T& iface, int64_t value, int64_t) | 
|  | { | 
|  | iface->scale(value); | 
|  | } | 
|  |  | 
|  | // 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) | 
|  | { | 
|  | 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. | 
|  | #ifdef 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) | 
|  | { | 
|  | static constexpr bool deferSignals = true; | 
|  |  | 
|  | // Get the initial value for the value interface. | 
|  | auto& bus = *std::get<sdbusplus::bus::bus*>(info); | 
|  | auto& obj = std::get<Object>(info); | 
|  | auto& objPath = std::get<std::string>(info); | 
|  |  | 
|  | SensorValueType val = 0; | 
|  | std::shared_ptr<StatusObject> statusIface = nullptr; | 
|  | auto it = obj.find(InterfaceType::STATUS); | 
|  | if (it != obj.end()) | 
|  | { | 
|  | statusIface = std::any_cast<std::shared_ptr<StatusObject>>(it->second); | 
|  | } | 
|  |  | 
|  | // If there's no fault file or the sensor has a fault file and | 
|  | // its status is functional, read the input value. | 
|  | if (!statusIface || (statusIface && statusIface->functional())) | 
|  | { | 
|  | unlockGpio(); | 
|  |  | 
|  | // 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)); | 
|  |  | 
|  | lockGpio(); | 
|  | val = adjustValue(val); | 
|  | } | 
|  |  | 
|  | auto iface = | 
|  | std::make_shared<ValueObject>(bus, objPath.c_str(), deferSignals); | 
|  | iface->value(val); | 
|  |  | 
|  | hwmon::Attributes attrs; | 
|  | if (hwmon::getAttributes(_sensor.first, attrs)) | 
|  | { | 
|  | iface->unit(hwmon::getUnit(attrs)); | 
|  |  | 
|  | setScale(iface, hwmon::getScale(attrs), val); | 
|  |  | 
|  | _scale = hwmon::getScale(attrs); | 
|  | } | 
|  |  | 
|  | 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::experimental::filesystem; | 
|  |  | 
|  | std::shared_ptr<StatusObject> iface = nullptr; | 
|  | auto& objPath = std::get<std::string>(info); | 
|  | auto& obj = std::get<Object>(info); | 
|  |  | 
|  | // Check if fault sysfs file exists | 
|  | std::string faultName = _sensor.first; | 
|  | std::string faultID = _sensor.second; | 
|  | std::string entry = hwmon::entry::fault; | 
|  |  | 
|  | auto sysfsFullPath = | 
|  | sysfs::make_sysfs_path(_ioAccess->path(), faultName, faultID, entry); | 
|  | if (fs::exists(sysfsFullPath)) | 
|  | { | 
|  | bool functional = 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>( | 
|  | "Logging failing sysfs file", | 
|  | phosphor::logging::entry("FILE=%s", sysfsFullPath.c_str())); | 
|  | } | 
|  |  | 
|  | static constexpr bool deferSignals = true; | 
|  | auto& bus = *std::get<sdbusplus::bus::bus*>(info); | 
|  |  | 
|  | iface = | 
|  | std::make_shared<StatusObject>(bus, objPath.c_str(), deferSignals); | 
|  | // Set functional property | 
|  | iface->functional(functional); | 
|  |  | 
|  | obj[InterfaceType::STATUS] = iface; | 
|  | } | 
|  |  | 
|  | return iface; | 
|  | } | 
|  |  | 
|  | void Sensor::unlockGpio() | 
|  | { | 
|  | if (_handle) | 
|  | { | 
|  | _handle->setValues({1}); | 
|  | std::this_thread::sleep_for(_pause); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Sensor::lockGpio() | 
|  | { | 
|  | if (_handle) | 
|  | { | 
|  | _handle->setValues({0}); | 
|  | } | 
|  | } | 
|  |  | 
|  | } // namespace sensor |