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