1 /** 2 * Copyright © 2017 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 "pmbus.hpp" 17 18 #include <phosphor-logging/elog-errors.hpp> 19 #include <phosphor-logging/elog.hpp> 20 #include <xyz/openbmc_project/Common/Device/error.hpp> 21 #include <xyz/openbmc_project/Common/error.hpp> 22 23 #include <filesystem> 24 #include <fstream> 25 26 namespace phosphor 27 { 28 namespace pmbus 29 { 30 31 using namespace phosphor::logging; 32 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 33 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 34 namespace fs = std::filesystem; 35 36 /** 37 * @brief Helper to close a file handle 38 */ 39 struct FileCloser 40 { 41 void operator()(FILE* fp) const 42 { 43 fclose(fp); 44 } 45 }; 46 47 std::string PMBus::insertPageNum(const std::string& templateName, size_t page) 48 { 49 auto name = templateName; 50 51 // insert the page where the P was 52 auto pos = name.find('P'); 53 if (pos != std::string::npos) 54 { 55 name.replace(pos, 1, std::to_string(page)); 56 } 57 58 return name; 59 } 60 61 fs::path PMBus::getPath(Type type) 62 { 63 switch (type) 64 { 65 default: 66 /* fall through */ 67 case Type::Base: 68 return basePath; 69 break; 70 case Type::Hwmon: 71 return basePath / "hwmon" / hwmonDir; 72 break; 73 case Type::Debug: 74 return debugPath / "pmbus" / hwmonDir; 75 break; 76 case Type::DeviceDebug: 77 { 78 auto dir = driverName + "." + std::to_string(instance); 79 return debugPath / dir; 80 break; 81 } 82 case Type::HwmonDeviceDebug: 83 return debugPath / "pmbus" / hwmonDir / getDeviceName(); 84 break; 85 } 86 } 87 88 std::string PMBus::getDeviceName() 89 { 90 std::string name; 91 std::ifstream file; 92 auto path = basePath / "name"; 93 94 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 95 std::ifstream::eofbit); 96 try 97 { 98 file.open(path); 99 file >> name; 100 } 101 catch (std::exception& e) 102 { 103 log<level::ERR>("Unable to read PMBus device name", 104 entry("PATH=%s", path.c_str())); 105 } 106 107 return name; 108 } 109 110 bool PMBus::readBitInPage(const std::string& name, size_t page, Type type) 111 { 112 auto pagedBit = insertPageNum(name, page); 113 return readBit(pagedBit, type); 114 } 115 116 bool PMBus::readBit(const std::string& name, Type type) 117 { 118 unsigned long int value = 0; 119 std::ifstream file; 120 fs::path path = getPath(type); 121 122 path /= name; 123 124 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 125 std::ifstream::eofbit); 126 127 try 128 { 129 char* err = NULL; 130 std::string val{1, '\0'}; 131 132 file.open(path); 133 file.read(&val[0], 1); 134 135 value = strtoul(val.c_str(), &err, 10); 136 137 if (*err) 138 { 139 log<level::ERR>("Invalid character in sysfs file", 140 entry("FILE=%s", path.c_str()), 141 entry("CONTENTS=%s", val.c_str())); 142 143 // Catch below and handle as a read failure 144 elog<InternalFailure>(); 145 } 146 } 147 catch (std::exception& e) 148 { 149 auto rc = errno; 150 151 log<level::ERR>("Failed to read sysfs file", 152 entry("FILENAME=%s", path.c_str())); 153 154 using metadata = xyz::openbmc_project::Common::Device::ReadFailure; 155 156 elog<ReadFailure>( 157 metadata::CALLOUT_ERRNO(rc), 158 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 159 } 160 161 return value != 0; 162 } 163 164 bool PMBus::exists(const std::string& name, Type type) 165 { 166 auto path = getPath(type); 167 path /= name; 168 return fs::exists(path); 169 } 170 171 uint64_t PMBus::read(const std::string& name, Type type) 172 { 173 uint64_t data = 0; 174 std::ifstream file; 175 auto path = getPath(type); 176 path /= name; 177 178 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 179 std::ifstream::eofbit); 180 181 try 182 { 183 file.open(path); 184 file >> std::hex >> data; 185 } 186 catch (std::exception& e) 187 { 188 auto rc = errno; 189 log<level::ERR>("Failed to read sysfs file", 190 entry("FILENAME=%s", path.c_str())); 191 192 using metadata = xyz::openbmc_project::Common::Device::ReadFailure; 193 194 elog<ReadFailure>( 195 metadata::CALLOUT_ERRNO(rc), 196 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 197 } 198 199 return data; 200 } 201 202 std::string PMBus::readString(const std::string& name, Type type) 203 { 204 std::string data; 205 std::ifstream file; 206 auto path = getPath(type); 207 path /= name; 208 209 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 210 std::ifstream::eofbit); 211 212 try 213 { 214 file.open(path); 215 file >> data; 216 } 217 catch (std::exception& e) 218 { 219 auto rc = errno; 220 log<level::ERR>("Failed to read sysfs file", 221 entry("FILENAME=%s", path.c_str())); 222 223 using metadata = xyz::openbmc_project::Common::Device::ReadFailure; 224 225 elog<ReadFailure>( 226 metadata::CALLOUT_ERRNO(rc), 227 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 228 } 229 230 return data; 231 } 232 233 std::vector<uint8_t> PMBus::readBinary(const std::string& name, Type type, 234 size_t length) 235 { 236 auto path = getPath(type) / name; 237 238 // Use C style IO because it's easier to handle telling the difference 239 // between hitting EOF or getting an actual error. 240 std::unique_ptr<FILE, FileCloser> file{fopen(path.c_str(), "rb")}; 241 242 if (file) 243 { 244 std::vector<uint8_t> data(length, 0); 245 246 auto bytes = 247 fread(data.data(), sizeof(decltype(data[0])), length, file.get()); 248 249 if (bytes != length) 250 { 251 // If hit EOF, just return the amount of data that was read. 252 if (feof(file.get())) 253 { 254 data.erase(data.begin() + bytes, data.end()); 255 } 256 else if (ferror(file.get())) 257 { 258 auto rc = errno; 259 log<level::ERR>("Failed to read sysfs file", 260 entry("FILENAME=%s", path.c_str())); 261 262 using metadata = 263 xyz::openbmc_project::Common::Device::ReadFailure; 264 265 elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc), 266 metadata::CALLOUT_DEVICE_PATH( 267 fs::canonical(basePath).c_str())); 268 } 269 } 270 return data; 271 } 272 273 return std::vector<uint8_t>{}; 274 } 275 276 void PMBus::write(const std::string& name, int value, Type type) 277 { 278 std::ofstream file; 279 fs::path path = getPath(type); 280 281 path /= name; 282 283 file.exceptions(std::ofstream::failbit | std::ofstream::badbit | 284 std::ofstream::eofbit); 285 286 try 287 { 288 file.open(path); 289 file << value; 290 } 291 catch (const std::exception& e) 292 { 293 auto rc = errno; 294 295 log<level::ERR>("Failed to write sysfs file", 296 entry("FILENAME=%s", path.c_str())); 297 298 using metadata = xyz::openbmc_project::Common::Device::WriteFailure; 299 300 elog<WriteFailure>( 301 metadata::CALLOUT_ERRNO(rc), 302 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 303 } 304 } 305 306 void PMBus::findHwmonDir() 307 { 308 fs::path path{basePath}; 309 path /= "hwmon"; 310 311 // Make sure the directory exists, otherwise for things that can be 312 // dynamically present or not present an exception will be thrown if the 313 // hwmon directory is not there, resulting in a program termination. 314 if (fs::is_directory(path)) 315 { 316 // look for <basePath>/hwmon/hwmonN/ 317 for (auto& f : fs::directory_iterator(path)) 318 { 319 if ((f.path().filename().string().find("hwmon") != 320 std::string::npos) && 321 (fs::is_directory(f.path()))) 322 { 323 hwmonDir = f.path().filename(); 324 break; 325 } 326 } 327 } 328 329 // Don't really want to crash here, just log it 330 // and let accesses fail later 331 if (hwmonDir.empty()) 332 { 333 log<level::INFO>("Unable to find hwmon directory " 334 "in device base path", 335 entry("DEVICE_PATH=%s", basePath.c_str())); 336 } 337 } 338 339 std::unique_ptr<PMBusBase> PMBus::createPMBus(std::uint8_t bus, 340 const std::string& address) 341 { 342 const std::string physpath = {"/sys/bus/i2c/devices/" + 343 std::to_string(bus) + "-" + address}; 344 auto interface = std::make_unique<PMBus>(physpath); 345 346 return interface; 347 } 348 349 std::unique_ptr<PMBusBase> createPMBus(std::uint8_t bus, 350 const std::string& address) 351 { 352 return PMBus::createPMBus(bus, address); 353 } 354 355 } // namespace pmbus 356 } // namespace phosphor 357