1 #include "config.h" 2 3 #include "sensor.hpp" 4 5 #include "env.hpp" 6 #include "gpio_handle.hpp" 7 #include "hwmon.hpp" 8 #include "sensorset.hpp" 9 #include "sysfs.hpp" 10 11 #include <fmt/format.h> 12 13 #include <cassert> 14 #include <chrono> 15 #include <cmath> 16 #include <cstring> 17 #include <filesystem> 18 #include <phosphor-logging/elog-errors.hpp> 19 #include <thread> 20 #include <xyz/openbmc_project/Common/error.hpp> 21 #include <xyz/openbmc_project/Sensor/Device/error.hpp> 22 23 namespace sensor 24 { 25 26 using namespace phosphor::logging; 27 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 28 29 // todo: this can be deleted once we move to double 30 // helper class to set the scale on the value iface only when it's available 31 template <typename T> 32 void setScale(T& iface, int64_t value, double) 33 { 34 } 35 template <typename T> 36 void setScale(T& iface, int64_t value, int64_t) 37 { 38 iface->scale(value); 39 } 40 41 // todo: this can be simplified once we move to the double interface 42 Sensor::Sensor(const SensorSet::key_type& sensor, 43 const hwmonio::HwmonIOInterface* ioAccess, 44 const std::string& devPath) : 45 _sensor(sensor), 46 _ioAccess(ioAccess), _devPath(devPath), _scale(0), _hasFaultFile(false) 47 { 48 auto chip = env::getEnv("GPIOCHIP", sensor); 49 auto access = env::getEnv("GPIO", sensor); 50 if (!access.empty() && !chip.empty()) 51 { 52 _handle = gpio::BuildGpioHandle(chip, access); 53 54 if (!_handle) 55 { 56 log<level::ERR>("Unable to set up gpio locking"); 57 elog<InternalFailure>(); 58 } 59 } 60 61 auto gain = env::getEnv("GAIN", sensor); 62 if (!gain.empty()) 63 { 64 _sensorAdjusts.gain = std::stod(gain); 65 } 66 67 auto offset = env::getEnv("OFFSET", sensor); 68 if (!offset.empty()) 69 { 70 _sensorAdjusts.offset = std::stoi(offset); 71 } 72 auto senRmRCs = env::getEnv("REMOVERCS", sensor); 73 // Add sensor removal return codes defined per sensor 74 addRemoveRCs(senRmRCs); 75 } 76 77 void Sensor::addRemoveRCs(const std::string& rcList) 78 { 79 if (rcList.empty()) 80 { 81 return; 82 } 83 84 // Convert to a char* for strtok 85 std::vector<char> rmRCs(rcList.c_str(), rcList.c_str() + rcList.size() + 1); 86 auto rmRC = std::strtok(&rmRCs[0], ", "); 87 while (rmRC != nullptr) 88 { 89 try 90 { 91 _sensorAdjusts.rmRCs.insert(std::stoi(rmRC)); 92 } 93 catch (const std::logic_error& le) 94 { 95 // Unable to convert to int, continue to next token 96 std::string name = _sensor.first + "_" + _sensor.second; 97 log<level::INFO>("Unable to convert sensor removal return code", 98 entry("SENSOR=%s", name.c_str()), 99 entry("RC=%s", rmRC), 100 entry("EXCEPTION=%s", le.what())); 101 } 102 rmRC = std::strtok(nullptr, ", "); 103 } 104 } 105 106 SensorValueType Sensor::adjustValue(SensorValueType value) 107 { 108 // Because read doesn't have an out pointer to store errors. 109 // let's assume negative values are errors if they have this 110 // set. 111 #ifdef NEGATIVE_ERRNO_ON_FAIL 112 if (value < 0) 113 { 114 return value; 115 } 116 #endif 117 118 // Adjust based on gain and offset 119 value = static_cast<decltype(value)>(static_cast<double>(value) * 120 _sensorAdjusts.gain + 121 _sensorAdjusts.offset); 122 123 if constexpr (std::is_same<SensorValueType, double>::value) 124 { 125 value *= std::pow(10, _scale); 126 } 127 128 return value; 129 } 130 131 std::shared_ptr<ValueObject> Sensor::addValue(const RetryIO& retryIO, 132 ObjectInfo& info) 133 { 134 static constexpr bool deferSignals = true; 135 136 // Get the initial value for the value interface. 137 auto& bus = *std::get<sdbusplus::bus::bus*>(info); 138 auto& obj = std::get<InterfaceMap>(info); 139 auto& objPath = std::get<std::string>(info); 140 141 SensorValueType val = 0; 142 143 auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>( 144 obj[InterfaceType::STATUS]); 145 // As long as addStatus is called before addValue, statusIface 146 // should never be nullptr 147 assert(statusIface); 148 149 // Only read the input value if the status is functional 150 if (statusIface->functional()) 151 { 152 #ifdef UPDATE_FUNCTIONAL_ON_FAIL 153 try 154 #endif 155 { 156 // RAII object for GPIO unlock / lock 157 auto locker = gpioUnlock(getGpio()); 158 159 // Retry for up to a second if device is busy 160 // or has a transient error. 161 val = 162 _ioAccess->read(_sensor.first, _sensor.second, 163 hwmon::entry::cinput, std::get<size_t>(retryIO), 164 std::get<std::chrono::milliseconds>(retryIO)); 165 166 val = adjustValue(val); 167 } 168 #ifdef UPDATE_FUNCTIONAL_ON_FAIL 169 catch (const std::system_error& e) 170 { 171 // Catch the exception here and update the functional property. 172 // By catching the exception, it will not propagate it up the stack 173 // and thus the code will skip the "Remove RCs" check in 174 // MainLoop::getObject and will not exit on failure. 175 statusIface->functional(false); 176 } 177 #endif 178 } 179 180 auto iface = 181 std::make_shared<ValueObject>(bus, objPath.c_str(), deferSignals); 182 iface->value(val); 183 184 hwmon::Attributes attrs; 185 if (hwmon::getAttributes(_sensor.first, attrs)) 186 { 187 iface->unit(hwmon::getUnit(attrs)); 188 189 setScale(iface, hwmon::getScale(attrs), val); 190 191 _scale = hwmon::getScale(attrs); 192 } 193 194 auto maxValue = env::getEnv("MAXVALUE", _sensor); 195 if (!maxValue.empty()) 196 { 197 iface->maxValue(std::stoll(maxValue)); 198 } 199 auto minValue = env::getEnv("MINVALUE", _sensor); 200 if (!minValue.empty()) 201 { 202 iface->minValue(std::stoll(minValue)); 203 } 204 205 obj[InterfaceType::VALUE] = iface; 206 return iface; 207 } 208 209 std::shared_ptr<StatusObject> Sensor::addStatus(ObjectInfo& info) 210 { 211 namespace fs = std::filesystem; 212 213 std::shared_ptr<StatusObject> iface = nullptr; 214 auto& objPath = std::get<std::string>(info); 215 auto& obj = std::get<InterfaceMap>(info); 216 217 // Check if fault sysfs file exists 218 std::string faultName = _sensor.first; 219 std::string faultID = _sensor.second; 220 std::string entry = hwmon::entry::fault; 221 222 bool functional = true; 223 auto sysfsFullPath = 224 sysfs::make_sysfs_path(_ioAccess->path(), faultName, faultID, entry); 225 if (fs::exists(sysfsFullPath)) 226 { 227 _hasFaultFile = true; 228 try 229 { 230 uint32_t fault = _ioAccess->read(faultName, faultID, entry, 231 hwmonio::retries, hwmonio::delay); 232 if (fault != 0) 233 { 234 functional = false; 235 } 236 } 237 catch (const std::system_error& e) 238 { 239 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device:: 240 Error; 241 using metadata = xyz::openbmc_project::Sensor::Device::ReadFailure; 242 243 report<ReadFailure>( 244 metadata::CALLOUT_ERRNO(e.code().value()), 245 metadata::CALLOUT_DEVICE_PATH(_devPath.c_str())); 246 247 log<level::INFO>( 248 fmt::format("Failing sysfs file: {}", sysfsFullPath).c_str()); 249 } 250 } 251 252 static constexpr bool deferSignals = true; 253 auto& bus = *std::get<sdbusplus::bus::bus*>(info); 254 255 iface = std::make_shared<StatusObject>(bus, objPath.c_str(), deferSignals); 256 // Set functional property 257 iface->functional(functional); 258 259 obj[InterfaceType::STATUS] = iface; 260 261 return iface; 262 } 263 264 void gpioLock(const gpioplus::HandleInterface*&& handle) 265 { 266 handle->setValues({0}); 267 } 268 269 std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle) 270 { 271 if (handle == nullptr) 272 { 273 return std::nullopt; 274 } 275 276 handle->setValues({1}); 277 // Default pause needed to guarantee sensors are ready 278 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 279 return GpioLocker(std::move(handle)); 280 } 281 282 } // namespace sensor 283