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