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 <phosphor-logging/elog-errors.hpp> 12 #include <xyz/openbmc_project/Common/error.hpp> 13 #include <xyz/openbmc_project/Sensor/Device/error.hpp> 14 15 #include <cassert> 16 #include <chrono> 17 #include <cmath> 18 #include <cstring> 19 #include <filesystem> 20 #include <format> 21 #include <future> 22 #include <thread> 23 24 namespace sensor 25 { 26 27 using namespace phosphor::logging; 28 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 29 30 // todo: this can be simplified once we move to the double interface 31 Sensor::Sensor(const SensorSet::key_type& sensor, 32 const hwmonio::HwmonIOInterface* ioAccess, 33 const std::string& devPath) : 34 _sensor(sensor), _ioAccess(ioAccess), _devPath(devPath), _scale(0), 35 _hasFaultFile(false) 36 { 37 auto chip = env::getEnv("GPIOCHIP", sensor); 38 auto access = env::getEnv("GPIO", sensor); 39 if (!access.empty() && !chip.empty()) 40 { 41 _handle = gpio::BuildGpioHandle(chip, access); 42 43 if (!_handle) 44 { 45 log<level::ERR>("Unable to set up gpio locking"); 46 elog<InternalFailure>(); 47 } 48 } 49 50 auto gain = env::getEnv("GAIN", sensor); 51 if (!gain.empty()) 52 { 53 _sensorAdjusts.gain = std::stod(gain); 54 } 55 56 auto offset = env::getEnv("OFFSET", sensor); 57 if (!offset.empty()) 58 { 59 _sensorAdjusts.offset = std::stoi(offset); 60 } 61 auto senRmRCs = env::getEnv("REMOVERCS", sensor); 62 // Add sensor removal return codes defined per sensor 63 addRemoveRCs(senRmRCs); 64 } 65 66 void Sensor::addRemoveRCs(const std::string& rcList) 67 { 68 if (rcList.empty()) 69 { 70 return; 71 } 72 73 // Convert to a char* for strtok 74 std::vector<char> rmRCs(rcList.c_str(), rcList.c_str() + rcList.size() + 1); 75 auto rmRC = std::strtok(&rmRCs[0], ", "); 76 while (rmRC != nullptr) 77 { 78 try 79 { 80 _sensorAdjusts.rmRCs.insert(std::stoi(rmRC)); 81 } 82 catch (const std::logic_error& le) 83 { 84 // Unable to convert to int, continue to next token 85 std::string name = _sensor.first + "_" + _sensor.second; 86 log<level::INFO>("Unable to convert sensor removal return code", 87 entry("SENSOR=%s", name.c_str()), 88 entry("RC=%s", rmRC), 89 entry("EXCEPTION=%s", le.what())); 90 } 91 rmRC = std::strtok(nullptr, ", "); 92 } 93 } 94 95 SensorValueType Sensor::adjustValue(SensorValueType value) 96 { 97 // Because read doesn't have an out pointer to store errors. 98 // let's assume negative values are errors if they have this 99 // set. 100 #if NEGATIVE_ERRNO_ON_FAIL 101 if (value < 0) 102 { 103 return value; 104 } 105 #endif 106 107 // Adjust based on gain and offset 108 value = static_cast<decltype(value)>( 109 static_cast<double>(value) * _sensorAdjusts.gain + 110 _sensorAdjusts.offset); 111 112 if constexpr (std::is_same<SensorValueType, double>::value) 113 { 114 value *= std::pow(10, _scale); 115 } 116 117 return value; 118 } 119 120 std::shared_ptr<ValueObject> Sensor::addValue( 121 const RetryIO& retryIO, ObjectInfo& info, TimedoutMap& timedoutMap) 122 { 123 // Get the initial value for the value interface. 124 auto& bus = *std::get<sdbusplus::bus_t*>(info); 125 auto& obj = std::get<InterfaceMap>(info); 126 auto& objPath = std::get<std::string>(info); 127 128 SensorValueType val = 0; 129 130 auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>( 131 obj[InterfaceType::STATUS]); 132 // As long as addStatus is called before addValue, statusIface 133 // should never be nullptr 134 assert(statusIface); 135 136 // Only read the input value if the status is functional 137 if (statusIface->functional()) 138 { 139 #if UPDATE_FUNCTIONAL_ON_FAIL 140 try 141 #endif 142 { 143 // RAII object for GPIO unlock / lock 144 auto locker = gpioUnlock(getGpio()); 145 146 // For sensors with attribute ASYNC_READ_TIMEOUT, 147 // spawn a thread with timeout 148 auto asyncReadTimeout = env::getEnv("ASYNC_READ_TIMEOUT", _sensor); 149 if (!asyncReadTimeout.empty()) 150 { 151 std::chrono::milliseconds asyncTimeout{ 152 std::stoi(asyncReadTimeout)}; 153 val = asyncRead(_sensor, _ioAccess, asyncTimeout, timedoutMap, 154 _sensor.first, _sensor.second, 155 hwmon::entry::cinput, std::get<size_t>(retryIO), 156 std::get<std::chrono::milliseconds>(retryIO)); 157 } 158 else 159 { 160 // Retry for up to a second if device is busy 161 // or has a transient error. 162 val = _ioAccess->read( 163 _sensor.first, _sensor.second, hwmon::entry::cinput, 164 std::get<size_t>(retryIO), 165 std::get<std::chrono::milliseconds>(retryIO)); 166 } 167 } 168 #if 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 = std::make_shared<ValueObject>(bus, objPath.c_str(), 181 ValueObject::action::defer_emit); 182 183 hwmon::Attributes attrs; 184 if (hwmon::getAttributes(_sensor.first, attrs)) 185 { 186 iface->unit(hwmon::getUnit(attrs)); 187 188 _scale = hwmon::getScale(attrs); 189 } 190 191 val = adjustValue(val); 192 iface->value(val); 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>(std::format("Failing sysfs file: {} errno {}", 248 sysfsFullPath, e.code().value()) 249 .c_str()); 250 } 251 } 252 253 auto& bus = *std::get<sdbusplus::bus_t*>(info); 254 255 iface = std::make_shared<StatusObject>( 256 bus, objPath.c_str(), StatusObject::action::emit_no_signals); 257 // Set functional property 258 iface->functional(functional); 259 260 obj[InterfaceType::STATUS] = iface; 261 262 return iface; 263 } 264 265 std::shared_ptr<AccuracyObject> 266 Sensor::addAccuracy(ObjectInfo& info, double accuracy) 267 { 268 auto& objPath = std::get<std::string>(info); 269 auto& obj = std::get<InterfaceMap>(info); 270 271 auto& bus = *std::get<sdbusplus::bus_t*>(info); 272 auto iface = std::make_shared<AccuracyObject>( 273 bus, objPath.c_str(), AccuracyObject::action::emit_no_signals); 274 275 iface->accuracy(accuracy); 276 obj[InterfaceType::ACCURACY] = iface; 277 278 return iface; 279 } 280 281 std::shared_ptr<PriorityObject> 282 Sensor::addPriority(ObjectInfo& info, size_t priority) 283 { 284 auto& objPath = std::get<std::string>(info); 285 auto& obj = std::get<InterfaceMap>(info); 286 287 auto& bus = *std::get<sdbusplus::bus_t*>(info); 288 auto iface = std::make_shared<PriorityObject>( 289 bus, objPath.c_str(), PriorityObject::action::emit_no_signals); 290 291 iface->priority(priority); 292 obj[InterfaceType::PRIORITY] = iface; 293 294 return iface; 295 } 296 297 void gpioLock(const gpioplus::HandleInterface*&& handle) 298 { 299 handle->setValues({0}); 300 } 301 302 std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle) 303 { 304 if (handle == nullptr) 305 { 306 return std::nullopt; 307 } 308 309 handle->setValues({1}); 310 // Default pause needed to guarantee sensors are ready 311 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 312 return GpioLocker(std::move(handle)); 313 } 314 315 SensorValueType asyncRead( 316 const SensorSet::key_type& sensorSetKey, 317 const hwmonio::HwmonIOInterface* ioAccess, 318 std::chrono::milliseconds asyncTimeout, TimedoutMap& timedoutMap, 319 const std::string& type, const std::string& id, const std::string& sensor, 320 const size_t retries, const std::chrono::milliseconds delay) 321 { 322 // Default async read timeout 323 bool valueIsValid = false; 324 std::future<int64_t> asyncThread; 325 326 auto asyncIter = timedoutMap.find(sensorSetKey); 327 if (asyncIter == timedoutMap.end()) 328 { 329 // If sensor not found in timedoutMap, spawn an async thread 330 asyncThread = 331 std::async(std::launch::async, &hwmonio::HwmonIOInterface::read, 332 ioAccess, type, id, sensor, retries, delay); 333 valueIsValid = true; 334 } 335 else 336 { 337 // If we already have the async thread in the timedoutMap, it means this 338 // sensor has already timed out in the previous reads. No need to wait 339 // on subsequent reads - proceed to check the future_status to see when 340 // the async thread finishes 341 asyncTimeout = std::chrono::seconds(0); 342 asyncThread = std::move(asyncIter->second); 343 } 344 345 // TODO: This is still not a true asynchronous read as it still blocks the 346 // main thread for asyncTimeout amount of time. To make this completely 347 // asynchronous, schedule a read and register a callback to update the 348 // sensor value 349 std::future_status status = asyncThread.wait_for(asyncTimeout); 350 switch (status) 351 { 352 case std::future_status::ready: 353 // Read has finished 354 if (valueIsValid) 355 { 356 return asyncThread.get(); 357 // Good sensor reads should skip the code below 358 } 359 // Async read thread has completed but had previously timed out (was 360 // found in the timedoutMap). Erase from timedoutMap and throw to 361 // allow retry in the next read cycle. Not returning the read value 362 // as the sensor reading may be bad / corrupted if it took so long. 363 timedoutMap.erase(sensorSetKey); 364 throw AsyncSensorReadTimeOut(); 365 default: 366 // Read timed out so add the thread to the timedoutMap (if the entry 367 // already exists, operator[] updates it). 368 // 369 // Keeping the timed out futures in a map is required to prevent 370 // their destructor from being called when returning from this 371 // stack. The destructor will otherwise block until the read 372 // completes due to the limitation of std::async. 373 timedoutMap[sensorSetKey] = std::move(asyncThread); 374 throw AsyncSensorReadTimeOut(); 375 } 376 } 377 378 } // namespace sensor 379