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>((std::string("Unable to read PMBus device name " 104 "PATH=") + 105 path.string()) 106 .c_str()); 107 } 108 109 return name; 110 } 111 112 bool PMBus::readBitInPage(const std::string& name, size_t page, Type type) 113 { 114 auto pagedBit = insertPageNum(name, page); 115 return readBit(pagedBit, type); 116 } 117 118 bool PMBus::readBit(const std::string& name, Type type) 119 { 120 unsigned long int value = 0; 121 std::ifstream file; 122 fs::path path = getPath(type); 123 124 path /= name; 125 126 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 127 std::ifstream::eofbit); 128 129 try 130 { 131 char* err = NULL; 132 std::string val{1, '\0'}; 133 134 file.open(path); 135 file.read(&val[0], 1); 136 137 value = strtoul(val.c_str(), &err, 10); 138 139 if (*err) 140 { 141 log<level::ERR>((std::string("Invalid character in sysfs file" 142 " FILE=") + 143 path.string() + std::string(" CONTENTS=") + val) 144 .c_str()); 145 146 // Catch below and handle as a read failure 147 elog<InternalFailure>(); 148 } 149 } 150 catch (std::exception& e) 151 { 152 auto rc = errno; 153 154 log<level::ERR>((std::string("Failed to read sysfs file " 155 "errno=") + 156 std::to_string(rc) + std::string(" FILENAME=") + 157 path.string()) 158 .c_str()); 159 160 using metadata = xyz::openbmc_project::Common::Device::ReadFailure; 161 162 elog<ReadFailure>( 163 metadata::CALLOUT_ERRNO(rc), 164 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 165 } 166 167 return value != 0; 168 } 169 170 bool PMBus::exists(const std::string& name, Type type) 171 { 172 auto path = getPath(type); 173 path /= name; 174 return fs::exists(path); 175 } 176 177 uint64_t PMBus::read(const std::string& name, Type type) 178 { 179 uint64_t data = 0; 180 std::ifstream file; 181 auto path = getPath(type); 182 path /= name; 183 184 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 185 std::ifstream::eofbit); 186 187 try 188 { 189 file.open(path); 190 file >> std::hex >> data; 191 } 192 catch (std::exception& e) 193 { 194 auto rc = errno; 195 log<level::ERR>((std::string("Failed to read sysfs file " 196 "errno=") + 197 std::to_string(rc) + " FILENAME=" + path.string()) 198 .c_str()); 199 200 using metadata = xyz::openbmc_project::Common::Device::ReadFailure; 201 202 elog<ReadFailure>( 203 metadata::CALLOUT_ERRNO(rc), 204 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 205 } 206 207 return data; 208 } 209 210 std::string PMBus::readString(const std::string& name, Type type) 211 { 212 std::string data; 213 std::ifstream file; 214 auto path = getPath(type); 215 path /= name; 216 217 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 218 std::ifstream::eofbit); 219 220 try 221 { 222 file.open(path); 223 file >> data; 224 } 225 catch (std::exception& e) 226 { 227 auto rc = errno; 228 log<level::ERR>((std::string("Failed to read sysfs file " 229 "errno=") + 230 std::to_string(rc) + " FILENAME=" + path.string()) 231 .c_str()); 232 233 using metadata = xyz::openbmc_project::Common::Device::ReadFailure; 234 235 elog<ReadFailure>( 236 metadata::CALLOUT_ERRNO(rc), 237 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 238 } 239 240 return data; 241 } 242 243 std::vector<uint8_t> PMBus::readBinary(const std::string& name, Type type, 244 size_t length) 245 { 246 auto path = getPath(type) / name; 247 248 // Use C style IO because it's easier to handle telling the difference 249 // between hitting EOF or getting an actual error. 250 std::unique_ptr<FILE, FileCloser> file{fopen(path.c_str(), "rb")}; 251 252 if (file) 253 { 254 std::vector<uint8_t> data(length, 0); 255 256 auto bytes = 257 fread(data.data(), sizeof(decltype(data[0])), length, file.get()); 258 259 if (bytes != length) 260 { 261 // If hit EOF, just return the amount of data that was read. 262 if (feof(file.get())) 263 { 264 data.erase(data.begin() + bytes, data.end()); 265 } 266 else if (ferror(file.get())) 267 { 268 auto rc = errno; 269 log<level::ERR>((std::string("Failed to read sysfs file " 270 "errno=") + 271 std::to_string(rc) + 272 " FILENAME=" + path.string()) 273 .c_str()); 274 using metadata = 275 xyz::openbmc_project::Common::Device::ReadFailure; 276 277 elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc), 278 metadata::CALLOUT_DEVICE_PATH( 279 fs::canonical(basePath).c_str())); 280 } 281 } 282 return data; 283 } 284 285 return std::vector<uint8_t>{}; 286 } 287 288 void PMBus::write(const std::string& name, int value, Type type) 289 { 290 std::ofstream file; 291 fs::path path = getPath(type); 292 293 path /= name; 294 295 file.exceptions(std::ofstream::failbit | std::ofstream::badbit | 296 std::ofstream::eofbit); 297 298 try 299 { 300 file.open(path); 301 file << value; 302 } 303 catch (const std::exception& e) 304 { 305 auto rc = errno; 306 log<level::ERR>((std::string("Failed to write sysfs file " 307 "errno=") + 308 std::to_string(rc) + " FILENAME=" + path.string()) 309 .c_str()); 310 311 using metadata = xyz::openbmc_project::Common::Device::WriteFailure; 312 313 elog<WriteFailure>( 314 metadata::CALLOUT_ERRNO(rc), 315 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 316 } 317 } 318 319 void PMBus::writeBinary(const std::string& name, std::vector<uint8_t> data, 320 Type type) 321 { 322 std::ofstream file; 323 fs::path path = getPath(type); 324 325 path /= name; 326 327 file.exceptions(std::ofstream::failbit | std::ofstream::badbit | 328 std::ofstream::eofbit); 329 330 try 331 { 332 // I need to specify binary mode when I construct the ofstream 333 file.open(path, std::ios::out | std::ios_base::binary); 334 log<level::DEBUG>(std::string("Write data to sysfs file " 335 "FILENAME=" + 336 path.string()) 337 .c_str()); 338 file.write(reinterpret_cast<const char*>(&data[0]), data.size()); 339 } 340 catch (const std::exception& e) 341 { 342 auto rc = errno; 343 log<level::ERR>( 344 (std::string("Failed to write binary data to sysfs file " 345 "errno=") + 346 std::to_string(rc) + " FILENAME=" + path.string()) 347 .c_str()); 348 349 using metadata = xyz::openbmc_project::Common::Device::WriteFailure; 350 351 elog<WriteFailure>( 352 metadata::CALLOUT_ERRNO(rc), 353 metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str())); 354 } 355 } 356 357 void PMBus::findHwmonDir() 358 { 359 fs::path path{basePath}; 360 path /= "hwmon"; 361 362 // Make sure the directory exists, otherwise for things that can be 363 // dynamically present or not present an exception will be thrown if the 364 // hwmon directory is not there, resulting in a program termination. 365 if (fs::is_directory(path)) 366 { 367 // look for <basePath>/hwmon/hwmonN/ 368 for (auto& f : fs::directory_iterator(path)) 369 { 370 if ((f.path().filename().string().find("hwmon") != 371 std::string::npos) && 372 (fs::is_directory(f.path()))) 373 { 374 hwmonDir = f.path().filename(); 375 break; 376 } 377 } 378 } 379 380 // Don't really want to crash here, just log it 381 // and let accesses fail later 382 if (hwmonDir.empty()) 383 { 384 log<level::INFO>(std::string("Unable to find hwmon directory " 385 "in device base path" 386 " DEVICE_PATH=" + 387 basePath.string()) 388 .c_str()); 389 } 390 } 391 392 std::unique_ptr<PMBusBase> PMBus::createPMBus(std::uint8_t bus, 393 const std::string& address) 394 { 395 const std::string physpath = {"/sys/bus/i2c/devices/" + 396 std::to_string(bus) + "-" + address}; 397 auto interface = std::make_unique<PMBus>(physpath); 398 399 return interface; 400 } 401 402 std::unique_ptr<PMBusBase> createPMBus(std::uint8_t bus, 403 const std::string& address) 404 { 405 return PMBus::createPMBus(bus, address); 406 } 407 408 } // namespace pmbus 409 } // namespace phosphor 410