1 /** 2 * Copyright © 2016 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include <algorithm> 17 #include <cerrno> 18 #include <cstdlib> 19 #include <experimental/filesystem> 20 #include <fstream> 21 #include <memory> 22 #include <phosphor-logging/log.hpp> 23 #include <thread> 24 #include "config.h" 25 #include "sysfs.hpp" 26 27 using namespace std::string_literals; 28 namespace fs = std::experimental::filesystem; 29 30 namespace sysfs { 31 32 static constexpr auto retryableErrors = { 33 /* 34 * Retry on bus or device errors or timeouts in case 35 * they are transient. 36 */ 37 EIO, 38 ETIMEDOUT, 39 40 /* 41 * Retry CRC errors. 42 */ 43 EBADMSG, 44 45 /* 46 * Some hwmon drivers do this when they aren't ready 47 * instead of blocking. Retry. 48 */ 49 EAGAIN, 50 /* 51 * We'll see this when for example i2c devices are 52 * unplugged but the driver is still bound. Retry 53 * rather than exit on the off chance the device is 54 * plugged back in and the driver doesn't do a 55 * remove/probe. If a remove does occur, we'll 56 * eventually get ENOENT. 57 */ 58 ENXIO, 59 60 /* 61 * Some devices return this when they are busy doing 62 * something else. Even if being busy isn't the cause, 63 * a retry still gives this app a shot at getting data 64 * as opposed to failing out on the first try. 65 */ 66 ENODATA, 67 }; 68 69 static const auto emptyString = ""s; 70 static constexpr auto ofRoot = "/sys/firmware/devicetree/base"; 71 72 std::string findPhandleMatch( 73 const std::string& iochanneldir, 74 const std::string& phandledir) 75 { 76 // TODO: At the moment this method only supports device trees 77 // with iio-hwmon nodes with a single sensor. Typically 78 // device trees are defined with all the iio sensors in a 79 // single iio-hwmon node so it would be nice to add support 80 // for lists of phandles (with variable sized entries) via 81 // libfdt or something like that, so that users are not 82 // forced into implementing unusual looking device trees 83 // with multiple iio-hwmon nodes - one for each sensor. 84 85 fs::path ioChannelsPath{iochanneldir}; 86 ioChannelsPath /= "io-channels"; 87 88 if (!fs::exists(ioChannelsPath)) 89 { 90 return emptyString; 91 } 92 93 uint32_t ioChannelsValue; 94 std::ifstream ioChannelsFile(ioChannelsPath); 95 96 ioChannelsFile.read( 97 reinterpret_cast<char*>(&ioChannelsValue), 98 sizeof(ioChannelsValue)); 99 100 for (const auto& ofInst : fs::recursive_directory_iterator(phandledir)) 101 { 102 auto path = ofInst.path(); 103 if ("phandle" != path.filename()) 104 { 105 continue; 106 } 107 std::ifstream pHandleFile(path); 108 uint32_t pHandleValue; 109 110 pHandleFile.read( 111 reinterpret_cast<char*>(&pHandleValue), 112 sizeof(pHandleValue)); 113 114 if (ioChannelsValue == pHandleValue) 115 { 116 return path; 117 } 118 } 119 120 return emptyString; 121 } 122 123 std::string findCalloutPath(const std::string& instancePath) 124 { 125 // Follow the hwmon instance (/sys/class/hwmon/hwmon<N>) 126 // /sys/devices symlink. 127 fs::path devPath{instancePath}; 128 devPath /= "device"; 129 130 try 131 { 132 devPath = fs::canonical(devPath); 133 } 134 catch (const std::system_error& e) 135 { 136 return emptyString; 137 } 138 139 // See if the device is backed by the iio-hwmon driver. 140 fs::path p{devPath}; 141 p /= "driver"; 142 p = fs::canonical(p); 143 144 if (p.filename() != "iio_hwmon") 145 { 146 // Not backed by iio-hwmon. The device pointed to 147 // is the callout device. 148 return devPath; 149 } 150 151 // Find the DT path to the iio-hwmon platform device. 152 fs::path ofDevPath{devPath}; 153 ofDevPath /= "of_node"; 154 155 try 156 { 157 ofDevPath = fs::canonical(ofDevPath); 158 } 159 catch (const std::system_error& e) 160 { 161 return emptyString; 162 } 163 164 // Search /sys/bus/iio/devices for the phandle in io-channels. 165 // If a match is found, use the corresponding /sys/devices 166 // iio device as the callout device. 167 static constexpr auto iioDevices = "/sys/bus/iio/devices"; 168 for (const auto& iioDev: fs::recursive_directory_iterator(iioDevices)) 169 { 170 p = iioDev.path(); 171 p /= "of_node"; 172 173 try 174 { 175 p = fs::canonical(p); 176 } 177 catch (const std::system_error& e) 178 { 179 continue; 180 } 181 182 auto match = findPhandleMatch(ofDevPath, p); 183 auto n = match.rfind('/'); 184 if (n != std::string::npos) 185 { 186 // This is the iio device referred to by io-channels. 187 // Remove iio:device<N>. 188 try 189 { 190 return fs::canonical(iioDev).parent_path(); 191 } 192 catch (const std::system_error& e) 193 { 194 return emptyString; 195 } 196 } 197 } 198 199 return emptyString; 200 } 201 202 std::string findHwmonFromOFPath(const std::string& ofNode) 203 { 204 static constexpr auto hwmonRoot = "/sys/class/hwmon"; 205 206 fs::path fullOfPath{ofRoot}; 207 fullOfPath /= ofNode; 208 209 for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot)) 210 { 211 auto path = hwmonInst.path(); 212 path /= "of_node"; 213 214 try 215 { 216 path = fs::canonical(path); 217 } 218 catch (const std::system_error& e) 219 { 220 // realpath may encounter ENOENT (Hwmon 221 // instances have a nasty habit of 222 // going away without warning). 223 continue; 224 } 225 226 if (path == fullOfPath) 227 { 228 return hwmonInst.path(); 229 } 230 231 // Try to find HWMON instance via phandle values. 232 // Used for IIO device drivers. 233 auto matchpath = findPhandleMatch(path, fullOfPath); 234 if (!matchpath.empty()) 235 { 236 return hwmonInst.path(); 237 } 238 } 239 240 return emptyString; 241 } 242 243 std::string findHwmonFromDevPath(const std::string& devPath) 244 { 245 fs::path p{"/sys"}; 246 p /= devPath; 247 p /= "hwmon"; 248 249 try 250 { 251 //This path is also used as a filesystem path to an environment 252 //file, and that has issues with ':'s in the path so they've 253 //been converted to '--'s. Convert them back now. 254 size_t pos = 0; 255 std::string path = p; 256 while ((pos = path.find("--")) != std::string::npos) 257 { 258 path.replace(pos, 2, ":"); 259 } 260 261 for (const auto& hwmonInst : fs::directory_iterator(path)) 262 { 263 if ((hwmonInst.path().filename().string().find("hwmon") != 264 std::string::npos)) 265 { 266 return hwmonInst.path(); 267 } 268 } 269 } 270 catch (const std::exception& e) 271 { 272 using namespace phosphor::logging; 273 log<level::ERR>( 274 "Unable to find hwmon directory from the dev path", 275 entry("PATH=%s", devPath.c_str())); 276 } 277 return emptyString; 278 } 279 280 namespace hwmonio 281 { 282 283 HwmonIO::HwmonIO(const std::string& path) : p(path) 284 { 285 286 } 287 288 int64_t HwmonIO::read( 289 const std::string& type, 290 const std::string& id, 291 const std::string& sensor, 292 size_t retries, 293 std::chrono::milliseconds delay, 294 bool isOCC) const 295 { 296 int64_t val; 297 std::ifstream ifs; 298 auto fullPath = sysfs::make_sysfs_path( 299 p, type, id, sensor); 300 301 ifs.exceptions( 302 std::ifstream::failbit | 303 std::ifstream::badbit | 304 std::ifstream::eofbit); 305 306 while (true) 307 { 308 try 309 { 310 errno = 0; 311 if (!ifs.is_open()) 312 ifs.open(fullPath); 313 ifs.clear(); 314 ifs.seekg(0); 315 ifs >> val; 316 } 317 catch (const std::exception& e) 318 { 319 auto rc = errno; 320 321 if (!rc) 322 { 323 throw; 324 } 325 326 if (rc == ENOENT || rc == ENODEV) 327 { 328 // If the directory or device disappeared then this application 329 // should gracefully exit. There are race conditions between the 330 // unloading of a hwmon driver and the stopping of this service 331 // by systemd. To prevent this application from falsely failing 332 // in these scenarios, it will simply exit if the directory or 333 // file can not be found. It is up to the user(s) of this 334 // provided hwmon object to log the appropriate errors if the 335 // object disappears when it should not. 336 exit(0); 337 } 338 339 if (isOCC) 340 { 341 if (rc == EAGAIN) 342 { 343 // For the OCCs, when an EAGAIN is return, just set the 344 // value to 0 (0x00 = sensor is unavailable) 345 val = 0; 346 break; 347 } 348 else if (rc == EREMOTEIO) 349 { 350 // For the OCCs, when an EREMOTEIO is return, set the 351 // value to 255*1000 352 // (0xFF = sensor is failed, 1000 = sensor factor) 353 val = 255000; 354 break; 355 } 356 } 357 358 if (0 == std::count( 359 retryableErrors.begin(), 360 retryableErrors.end(), 361 rc) || 362 !retries) 363 { 364 // Not a retryable error or out of retries. 365 #ifdef NEGATIVE_ERRNO_ON_FAIL 366 return -rc; 367 #endif 368 369 // Work around GCC bugs 53984 and 66145 for callers by 370 // explicitly raising system_error here. 371 throw std::system_error(rc, std::generic_category()); 372 } 373 374 --retries; 375 std::this_thread::sleep_for(delay); 376 continue; 377 } 378 break; 379 } 380 381 return val; 382 } 383 384 void HwmonIO::write( 385 uint32_t val, 386 const std::string& type, 387 const std::string& id, 388 const std::string& sensor, 389 size_t retries, 390 std::chrono::milliseconds delay) const 391 392 { 393 std::ofstream ofs; 394 auto fullPath = sysfs::make_sysfs_path( 395 p, type, id, sensor); 396 397 ofs.exceptions( 398 std::ofstream::failbit | 399 std::ofstream::badbit | 400 std::ofstream::eofbit); 401 402 // See comments in the read method for an explanation of the odd exception 403 // handling behavior here. 404 405 while (true) 406 { 407 try 408 { 409 errno = 0; 410 if (!ofs.is_open()) 411 ofs.open(fullPath); 412 ofs.clear(); 413 ofs.seekp(0); 414 ofs << val; 415 ofs.flush(); 416 } 417 catch (const std::exception& e) 418 { 419 auto rc = errno; 420 421 if (!rc) 422 { 423 throw; 424 } 425 426 if (rc == ENOENT) 427 { 428 exit(0); 429 } 430 431 if (0 == std::count( 432 retryableErrors.begin(), 433 retryableErrors.end(), 434 rc) || 435 !retries) 436 { 437 // Not a retryable error or out of retries. 438 439 // Work around GCC bugs 53984 and 66145 for callers by 440 // explicitly raising system_error here. 441 throw std::system_error(rc, std::generic_category()); 442 } 443 444 --retries; 445 std::this_thread::sleep_for(delay); 446 continue; 447 } 448 break; 449 } 450 } 451 452 std::string HwmonIO::path() const 453 { 454 return p; 455 } 456 457 } // namespace hwmonio 458 } 459 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 460