/** * Copyright © 2017 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pmbus.hpp" #include #include #include #include #include #include #include namespace phosphor { namespace pmbus { using namespace phosphor::logging; using namespace sdbusplus::xyz::openbmc_project::Common::Error; using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; namespace fs = std::filesystem; /** * @brief Helper to close a file handle */ struct FileCloser { void operator()(FILE* fp) const { fclose(fp); } }; /** * @brief Returns the canonical path if it exists * otherwise returns the path passed in. * * @param[in] path - The path to check * * @return fs::path - Either the canonical or original path */ static fs::path tryCanonical(const fs::path& path) { std::error_code ec; auto canonical = fs::canonical(path, ec); if (ec) { lg2::error("Could not get canonical path from {PATH}", "PATH", path); return path; } return canonical; } std::string PMBus::insertPageNum(const std::string& templateName, size_t page) { auto name = templateName; // insert the page where the P was auto pos = name.find('P'); if (pos != std::string::npos) { name.replace(pos, 1, std::to_string(page)); } return name; } fs::path PMBus::getPath(Type type) { switch (type) { default: /* fall through */ case Type::Base: return basePath; break; case Type::Hwmon: return basePath / "hwmon" / hwmonDir; break; case Type::Debug: return debugPath / "pmbus" / hwmonDir; break; case Type::DeviceDebug: { auto dir = driverName + "." + std::to_string(instance); return debugPath / dir; break; } case Type::HwmonDeviceDebug: return debugPath / "pmbus" / hwmonDir / getDeviceName(); break; } } std::string PMBus::getDeviceName() { std::string name; std::ifstream file; auto path = basePath / "name"; file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); try { file.open(path); file >> name; } catch (const std::exception& e) { lg2::error("Unable to read PMBus device name PATH={PATH}", "PATH", path); } return name; } bool PMBus::readBitInPage(const std::string& name, size_t page, Type type) { auto pagedBit = insertPageNum(name, page); return readBit(pagedBit, type); } bool PMBus::readBit(const std::string& name, Type type) { unsigned long int value = 0; std::ifstream file; fs::path path = getPath(type); path /= name; file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); try { char* err = nullptr; std::string val(1, '\0'); file.open(path); file.read(&val[0], 1); value = strtoul(val.c_str(), &err, 10); if (*err) { lg2::error( "Invalid character in sysfs file FILE={FILE} CONTENTS={CONTENTS}", "FILE", path, "CONTENTS", val); // Catch below and handle as a read failure elog(); } } catch (const std::exception& e) { auto rc = errno; lg2::error( "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}", "ERRNO", rc, "FILENAME", path); using metadata = xyz::openbmc_project::Common::Device::ReadFailure; elog( metadata::CALLOUT_ERRNO(rc), metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str())); } return value != 0; } bool PMBus::exists(const std::string& name, Type type) { auto path = getPath(type); path /= name; return fs::exists(path); } uint64_t PMBus::read(const std::string& name, Type type, bool errTrace) { uint64_t data = 0; std::ifstream file; auto path = getPath(type); path /= name; file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); try { file.open(path); file >> std::hex >> data; } catch (const std::exception& e) { auto rc = errno; if (errTrace) { lg2::error( "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}", "ERRNO", rc, "FILENAME", path); using metadata = xyz::openbmc_project::Common::Device::ReadFailure; elog( metadata::CALLOUT_ERRNO(rc), metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str())); } else { throw ReadFailure(); } } return data; } std::string PMBus::readString(const std::string& name, Type type) { std::string data; std::ifstream file; auto path = getPath(type); path /= name; file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); try { file.open(path); file >> data; } catch (const std::exception& e) { auto rc = errno; lg2::error( "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}", "ERRNO", rc, "FILENAME", path); using metadata = xyz::openbmc_project::Common::Device::ReadFailure; elog( metadata::CALLOUT_ERRNO(rc), metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str())); } return data; } std::vector PMBus::readBinary(const std::string& name, Type type, size_t length) { auto path = getPath(type) / name; // Use C style IO because it's easier to handle telling the difference // between hitting EOF or getting an actual error. std::unique_ptr file{fopen(path.c_str(), "rb")}; if (file) { std::vector data(length, 0); auto bytes = fread(data.data(), sizeof(decltype(data[0])), length, file.get()); if (bytes != length) { // If hit EOF, just return the amount of data that was read. if (feof(file.get())) { data.erase(data.begin() + bytes, data.end()); } else if (ferror(file.get())) { auto rc = errno; lg2::error( "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}", "ERRNO", rc, "FILENAME", path); using metadata = xyz::openbmc_project::Common::Device::ReadFailure; elog(metadata::CALLOUT_ERRNO(rc), metadata::CALLOUT_DEVICE_PATH( tryCanonical(basePath).c_str())); } } return data; } return std::vector{}; } void PMBus::write(const std::string& name, int value, Type type) { std::ofstream file; fs::path path = getPath(type); path /= name; file.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit); try { file.open(path); file << value; } catch (const std::exception& e) { auto rc = errno; lg2::error( "Failed to write sysfs file errno={ERRNO} FILENAME={FILENAME}", "ERRNO", rc, "FILENAME", path); using metadata = xyz::openbmc_project::Common::Device::WriteFailure; elog( metadata::CALLOUT_ERRNO(rc), metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str())); } } void PMBus::writeBinary(const std::string& name, std::vector data, Type type) { std::ofstream file; fs::path path = getPath(type); path /= name; file.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit); try { // I need to specify binary mode when I construct the ofstream file.open(path, std::ios::out | std::ios_base::binary); lg2::debug("Write data to sysfs file FILENAME={FILENAME}", "FILENAME", path); file.write(reinterpret_cast(&data[0]), data.size()); } catch (const std::exception& e) { auto rc = errno; lg2::error( "Failed to write binary data to sysfs file errno={ERRNO} FILENAME={FILENAME}", "ERRNO", rc, "FILENAME", path); using metadata = xyz::openbmc_project::Common::Device::WriteFailure; elog( metadata::CALLOUT_ERRNO(rc), metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str())); } } void PMBus::findHwmonDir() { fs::path path{basePath}; path /= "hwmon"; // Make sure the directory exists, otherwise for things that can be // dynamically present or not present an exception will be thrown if the // hwmon directory is not there, resulting in a program termination. if (fs::is_directory(path)) { // look for /hwmon/hwmonN/ for (auto& f : fs::directory_iterator(path)) { if ((f.path().filename().string().find("hwmon") != std::string::npos) && (fs::is_directory(f.path()))) { hwmonDir = f.path().filename(); break; } } } // Don't really want to crash here, just log it // and let accesses fail later if (hwmonDir.empty()) { lg2::info("Unable to find hwmon directory in device base path " "DEVICE_PATH={DEVICE_PATH}", "DEVICE_PATH", basePath); } } std::unique_ptr PMBus::createPMBus(std::uint8_t bus, const std::string& address) { const std::string physpath = { "/sys/bus/i2c/devices/" + std::to_string(bus) + "-" + address}; auto interface = std::make_unique(physpath); return interface; } std::unique_ptr createPMBus(std::uint8_t bus, const std::string& address) { return PMBus::createPMBus(bus, address); } } // namespace pmbus } // namespace phosphor