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 <cstdlib> 17 #include <experimental/filesystem> 18 #include <memory> 19 #include <phosphor-logging/elog.hpp> 20 #include <phosphor-logging/elog-errors.hpp> 21 #include <xyz/openbmc_project/Control/Device/error.hpp> 22 #include <xyz/openbmc_project/Sensor/Device/error.hpp> 23 #include "sysfs.hpp" 24 #include "util.hpp" 25 #include <fstream> 26 27 using namespace phosphor::logging; 28 namespace fs = std::experimental::filesystem; 29 30 namespace sysfs { 31 32 static constexpr auto ofRoot = "/sys/firmware/devicetree/base"; 33 34 /** 35 * @brief Return the path to the phandle file matching value in io-channels. 36 * 37 * This function will take two passed in paths. 38 * One path is used to find the io-channels file. 39 * The other path is used to find the phandle file. 40 * The 4 byte phandle value is read from the phandle file(s). 41 * The 4 byte phandle value and 4 byte index value is read from io-channels. 42 * When a match is found, the path to the matching phandle file is returned. 43 * 44 * @param[in] iochanneldir - Path to file for getting phandle from io-channels 45 * @param[in] phandledir - Path to use for reading from phandle file 46 * 47 * @return Path to phandle file with value matching that in io-channels 48 */ 49 std::string findPhandleMatch(const std::string& iochanneldir, 50 const std::string& phandledir) 51 { 52 for (const auto& ofInst : fs::recursive_directory_iterator(phandledir)) 53 { 54 auto path = ofInst.path(); 55 if ("phandle" == ofInst.path().filename()) 56 { 57 auto ioChannelsPath = iochanneldir + "/io-channels"; 58 if (fs::exists(ioChannelsPath)) 59 { 60 auto fullOfPathPhandle = ofInst.path(); 61 std::ifstream ioChannelsFile(path); 62 std::ifstream pHandleFile(fullOfPathPhandle); 63 64 uint32_t ioChannelsValue; 65 uint32_t pHandleValue; 66 67 try 68 { 69 ioChannelsFile.read(reinterpret_cast<char*>(&ioChannelsValue), 70 sizeof(ioChannelsValue)); 71 pHandleFile.read(reinterpret_cast<char*>(&pHandleValue), 72 sizeof(pHandleValue)); 73 74 if (ioChannelsValue == pHandleValue) 75 { 76 return ofInst.path(); 77 } 78 } 79 catch (const std::exception& e) 80 { 81 log<level::INFO>(e.what()); 82 continue; 83 } 84 85 } 86 } 87 } 88 89 return std::string(); 90 } 91 92 /** 93 * @brief Return the path to use for a call out. 94 * 95 * If the path does not contain iio-hwmon, assume passed in path is the call 96 * out path. 97 * 98 * @param[in] ofPath - Open firmware path to search for matching phandle value 99 * 100 * @return Path to use for call out 101 */ 102 std::string findCalloutPath(const std::string& ofPath) 103 { 104 static constexpr auto iioHwmonStr = "iio-hwmon"; 105 106 if (ofPath.find(iioHwmonStr) != std::string::npos) 107 { 108 auto matchpath = findPhandleMatch(ofPath, ofRoot); 109 auto n = matchpath.rfind('/'); 110 if (n != std::string::npos) 111 { 112 return matchpath.substr(0, n); 113 } 114 } 115 116 return ofPath; 117 } 118 119 std::string findHwmon(const std::string& ofNode) 120 { 121 static constexpr auto hwmonRoot = "/sys/class/hwmon"; 122 123 fs::path fullOfPath{ofRoot}; 124 fullOfPath /= ofNode; 125 126 for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot)) 127 { 128 auto path = hwmonInst.path(); 129 path /= "of_node"; 130 if (fs::canonical(path) != fullOfPath) 131 { 132 // Try to find HWMON instance via phandle values. 133 // Used for IIO device drivers. 134 auto ofpath = fullOfPath.string(); 135 auto matchpath = findPhandleMatch(path, ofpath); 136 if (!std::string(matchpath).empty()) 137 { 138 return hwmonInst.path(); 139 } 140 else 141 { 142 continue; 143 } 144 } 145 146 return hwmonInst.path(); 147 } 148 149 return std::string(); 150 } 151 152 int readSysfsWithCallout(const std::string& root, 153 const std::string& instance, 154 const std::string& type, 155 const std::string& id, 156 const std::string& sensor) 157 { 158 namespace fs = std::experimental::filesystem; 159 160 int value = 0; 161 std::ifstream ifs; 162 fs::path instancePath{root}; 163 instancePath /= instance; 164 std::string fullPath = make_sysfs_path(instancePath, 165 type, id, sensor); 166 167 ifs.exceptions(std::ifstream::failbit 168 | std::ifstream::badbit 169 | std::ifstream::eofbit); 170 try 171 { 172 ifs.open(fullPath); 173 ifs >> value; 174 } 175 catch (const std::exception& e) 176 { 177 // Too many GCC bugs (53984, 66145) to do 178 // this the right way... 179 180 // errno should still reflect the error from the failing open 181 // or read system calls that got us here. 182 auto rc = errno; 183 184 // If the directory disappeared then this application should gracefully 185 // exit. There are race conditions between the unloading of a hwmon 186 // driver and the stopping of this service by systemd. To prevent 187 // this application from falsely failing in these scenarios, it will 188 // simply exit if the directory or file can not be found. It is up 189 // to the user(s) of this provided hwmon object to log the appropriate 190 // errors if the object disappears when it should not. 191 if (rc == ENOENT) 192 { 193 exit(0); 194 } 195 instancePath /= "device"; 196 auto callOutPath = findCalloutPath(fs::canonical(instancePath)); 197 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error; 198 199 // this throws a ReadFailure. 200 elog<ReadFailure>( 201 xyz::openbmc_project::Sensor::Device:: 202 ReadFailure::CALLOUT_ERRNO(rc), 203 xyz::openbmc_project::Sensor::Device:: 204 ReadFailure::CALLOUT_DEVICE_PATH( 205 fs::canonical(callOutPath).c_str())); 206 } 207 208 return value; 209 } 210 211 uint64_t writeSysfsWithCallout(const uint64_t& value, 212 const std::string& root, 213 const std::string& instance, 214 const std::string& type, 215 const std::string& id, 216 const std::string& sensor) 217 { 218 namespace fs = std::experimental::filesystem; 219 220 std::string valueStr = std::to_string(value); 221 std::ofstream ofs; 222 fs::path instancePath{root}; 223 instancePath /= instance; 224 std::string fullPath = make_sysfs_path(instancePath, 225 type, id, sensor); 226 227 ofs.exceptions(std::ofstream::failbit 228 | std::ofstream::badbit 229 | std::ofstream::eofbit); 230 try 231 { 232 ofs.open(fullPath); 233 ofs << valueStr; 234 } 235 catch (const std::exception& e) 236 { 237 // errno should still reflect the error from the failing open 238 // or write system calls that got us here. 239 auto rc = errno; 240 instancePath /= "device"; 241 auto callOutPath = findCalloutPath(fs::canonical(instancePath)); 242 using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error; 243 report<WriteFailure>( 244 xyz::openbmc_project::Control::Device:: 245 WriteFailure::CALLOUT_ERRNO(rc), 246 xyz::openbmc_project::Control::Device:: 247 WriteFailure::CALLOUT_DEVICE_PATH( 248 fs::canonical(callOutPath).c_str())); 249 250 exit(EXIT_FAILURE); 251 } 252 253 return value; 254 } 255 256 } 257 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 258