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 == EREMOTEIO) 342 { 343 // For the OCCs, when an EREMOTEIO is return, set the 344 // value to 255*1000 345 // (0xFF = sensor is failed, 1000 = sensor factor) 346 val = 255000; 347 break; 348 } 349 } 350 351 if (0 == std::count( 352 retryableErrors.begin(), 353 retryableErrors.end(), 354 rc) || 355 !retries) 356 { 357 // Not a retryable error or out of retries. 358 #ifdef NEGATIVE_ERRNO_ON_FAIL 359 return -rc; 360 #endif 361 362 // Work around GCC bugs 53984 and 66145 for callers by 363 // explicitly raising system_error here. 364 throw std::system_error(rc, std::generic_category()); 365 } 366 367 --retries; 368 std::this_thread::sleep_for(delay); 369 continue; 370 } 371 break; 372 } 373 374 return val; 375 } 376 377 void HwmonIO::write( 378 uint32_t val, 379 const std::string& type, 380 const std::string& id, 381 const std::string& sensor, 382 size_t retries, 383 std::chrono::milliseconds delay) const 384 385 { 386 std::ofstream ofs; 387 auto fullPath = sysfs::make_sysfs_path( 388 p, type, id, sensor); 389 390 ofs.exceptions( 391 std::ofstream::failbit | 392 std::ofstream::badbit | 393 std::ofstream::eofbit); 394 395 // See comments in the read method for an explanation of the odd exception 396 // handling behavior here. 397 398 while (true) 399 { 400 try 401 { 402 errno = 0; 403 if (!ofs.is_open()) 404 ofs.open(fullPath); 405 ofs.clear(); 406 ofs.seekp(0); 407 ofs << val; 408 ofs.flush(); 409 } 410 catch (const std::exception& e) 411 { 412 auto rc = errno; 413 414 if (!rc) 415 { 416 throw; 417 } 418 419 if (rc == ENOENT) 420 { 421 exit(0); 422 } 423 424 if (0 == std::count( 425 retryableErrors.begin(), 426 retryableErrors.end(), 427 rc) || 428 !retries) 429 { 430 // Not a retryable error or out of retries. 431 432 // Work around GCC bugs 53984 and 66145 for callers by 433 // explicitly raising system_error here. 434 throw std::system_error(rc, std::generic_category()); 435 } 436 437 --retries; 438 std::this_thread::sleep_for(delay); 439 continue; 440 } 441 break; 442 } 443 } 444 445 std::string HwmonIO::path() const 446 { 447 return p; 448 } 449 450 } // namespace hwmonio 451 } 452 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 453