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