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 <future> 19 #include <phosphor-logging/elog-errors.hpp> 20 #include <thread> 21 #include <xyz/openbmc_project/Common/error.hpp> 22 #include <xyz/openbmc_project/Sensor/Device/error.hpp> 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), 35 _ioAccess(ioAccess), _devPath(devPath), _scale(0), _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)>(static_cast<double>(value) * 109 _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(const RetryIO& retryIO, 121 ObjectInfo& info, 122 TimedoutMap& timedoutMap) 123 { 124 static constexpr bool deferSignals = true; 125 126 // Get the initial value for the value interface. 127 auto& bus = *std::get<sdbusplus::bus::bus*>(info); 128 auto& obj = std::get<InterfaceMap>(info); 129 auto& objPath = std::get<std::string>(info); 130 131 SensorValueType val = 0; 132 133 auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>( 134 obj[InterfaceType::STATUS]); 135 // As long as addStatus is called before addValue, statusIface 136 // should never be nullptr 137 assert(statusIface); 138 139 // Only read the input value if the status is functional 140 if (statusIface->functional()) 141 { 142 #if UPDATE_FUNCTIONAL_ON_FAIL 143 try 144 #endif 145 { 146 // RAII object for GPIO unlock / lock 147 auto locker = gpioUnlock(getGpio()); 148 149 // For sensors with attribute ASYNC_READ_TIMEOUT, 150 // spawn a thread with timeout 151 auto asyncReadTimeout = env::getEnv("ASYNC_READ_TIMEOUT", _sensor); 152 if (!asyncReadTimeout.empty()) 153 { 154 std::chrono::milliseconds asyncTimeout{ 155 std::stoi(asyncReadTimeout)}; 156 val = asyncRead(_sensor, _ioAccess, asyncTimeout, timedoutMap, 157 _sensor.first, _sensor.second, 158 hwmon::entry::cinput, std::get<size_t>(retryIO), 159 std::get<std::chrono::milliseconds>(retryIO)); 160 } 161 else 162 { 163 // Retry for up to a second if device is busy 164 // or has a transient error. 165 val = _ioAccess->read( 166 _sensor.first, _sensor.second, hwmon::entry::cinput, 167 std::get<size_t>(retryIO), 168 std::get<std::chrono::milliseconds>(retryIO)); 169 } 170 } 171 #if UPDATE_FUNCTIONAL_ON_FAIL 172 catch (const std::system_error& e) 173 { 174 // Catch the exception here and update the functional property. 175 // By catching the exception, it will not propagate it up the stack 176 // and thus the code will skip the "Remove RCs" check in 177 // MainLoop::getObject and will not exit on failure. 178 statusIface->functional(false); 179 } 180 #endif 181 } 182 183 auto iface = 184 std::make_shared<ValueObject>(bus, objPath.c_str(), deferSignals); 185 186 hwmon::Attributes attrs; 187 if (hwmon::getAttributes(_sensor.first, attrs)) 188 { 189 iface->unit(hwmon::getUnit(attrs)); 190 191 _scale = hwmon::getScale(attrs); 192 } 193 194 val = adjustValue(val); 195 iface->value(val); 196 197 auto maxValue = env::getEnv("MAXVALUE", _sensor); 198 if (!maxValue.empty()) 199 { 200 iface->maxValue(std::stoll(maxValue)); 201 } 202 auto minValue = env::getEnv("MINVALUE", _sensor); 203 if (!minValue.empty()) 204 { 205 iface->minValue(std::stoll(minValue)); 206 } 207 208 obj[InterfaceType::VALUE] = iface; 209 return iface; 210 } 211 212 std::shared_ptr<StatusObject> Sensor::addStatus(ObjectInfo& info) 213 { 214 namespace fs = std::filesystem; 215 216 std::shared_ptr<StatusObject> iface = nullptr; 217 auto& objPath = std::get<std::string>(info); 218 auto& obj = std::get<InterfaceMap>(info); 219 220 // Check if fault sysfs file exists 221 std::string faultName = _sensor.first; 222 std::string faultID = _sensor.second; 223 std::string entry = hwmon::entry::fault; 224 225 bool functional = true; 226 auto sysfsFullPath = 227 sysfs::make_sysfs_path(_ioAccess->path(), faultName, faultID, entry); 228 if (fs::exists(sysfsFullPath)) 229 { 230 _hasFaultFile = true; 231 try 232 { 233 uint32_t fault = _ioAccess->read(faultName, faultID, entry, 234 hwmonio::retries, hwmonio::delay); 235 if (fault != 0) 236 { 237 functional = false; 238 } 239 } 240 catch (const std::system_error& e) 241 { 242 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device:: 243 Error; 244 using metadata = xyz::openbmc_project::Sensor::Device::ReadFailure; 245 246 report<ReadFailure>( 247 metadata::CALLOUT_ERRNO(e.code().value()), 248 metadata::CALLOUT_DEVICE_PATH(_devPath.c_str())); 249 250 log<level::INFO>(fmt::format("Failing sysfs file: {} errno {}", 251 sysfsFullPath, e.code().value()) 252 .c_str()); 253 } 254 } 255 256 static constexpr bool deferSignals = true; 257 auto& bus = *std::get<sdbusplus::bus::bus*>(info); 258 259 iface = std::make_shared<StatusObject>(bus, objPath.c_str(), deferSignals); 260 // Set functional property 261 iface->functional(functional); 262 263 obj[InterfaceType::STATUS] = iface; 264 265 return iface; 266 } 267 268 void gpioLock(const gpioplus::HandleInterface*&& handle) 269 { 270 handle->setValues({0}); 271 } 272 273 std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle) 274 { 275 if (handle == nullptr) 276 { 277 return std::nullopt; 278 } 279 280 handle->setValues({1}); 281 // Default pause needed to guarantee sensors are ready 282 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 283 return GpioLocker(std::move(handle)); 284 } 285 286 SensorValueType asyncRead(const SensorSet::key_type& sensorSetKey, 287 const hwmonio::HwmonIOInterface* ioAccess, 288 std::chrono::milliseconds asyncTimeout, 289 TimedoutMap& timedoutMap, const std::string& type, 290 const std::string& id, const std::string& sensor, 291 const size_t retries, 292 const std::chrono::milliseconds delay) 293 { 294 // Default async read timeout 295 bool valueIsValid = false; 296 std::future<int64_t> asyncThread; 297 298 auto asyncIter = timedoutMap.find(sensorSetKey); 299 if (asyncIter == timedoutMap.end()) 300 { 301 // If sensor not found in timedoutMap, spawn an async thread 302 asyncThread = 303 std::async(std::launch::async, &hwmonio::HwmonIOInterface::read, 304 ioAccess, type, id, sensor, retries, delay); 305 valueIsValid = true; 306 } 307 else 308 { 309 // If we already have the async thread in the timedoutMap, it means this 310 // sensor has already timed out in the previous reads. No need to wait 311 // on subsequent reads - proceed to check the future_status to see when 312 // the async thread finishes 313 asyncTimeout = std::chrono::seconds(0); 314 asyncThread = std::move(asyncIter->second); 315 } 316 317 // TODO: This is still not a true asynchronous read as it still blocks the 318 // main thread for asyncTimeout amount of time. To make this completely 319 // asynchronous, schedule a read and register a callback to update the 320 // sensor value 321 std::future_status status = asyncThread.wait_for(asyncTimeout); 322 switch (status) 323 { 324 case std::future_status::ready: 325 // Read has finished 326 if (valueIsValid) 327 { 328 return asyncThread.get(); 329 // Good sensor reads should skip the code below 330 } 331 // Async read thread has completed but had previously timed out (was 332 // found in the timedoutMap). Erase from timedoutMap and throw to 333 // allow retry in the next read cycle. Not returning the read value 334 // as the sensor reading may be bad / corrupted if it took so long. 335 timedoutMap.erase(sensorSetKey); 336 throw AsyncSensorReadTimeOut(); 337 default: 338 // Read timed out so add the thread to the timedoutMap (if the entry 339 // already exists, operator[] updates it). 340 // 341 // Keeping the timed out futures in a map is required to prevent 342 // their destructor from being called when returning from this 343 // stack. The destructor will otherwise block until the read 344 // completes due to the limitation of std::async. 345 timedoutMap[sensorSetKey] = std::move(asyncThread); 346 throw AsyncSensorReadTimeOut(); 347 } 348 } 349 350 } // namespace sensor 351