blob: de387d662b9610068dc00685d0fad745ee0044a2 [file] [log] [blame]
/**
* 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 <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <xyz/openbmc_project/Common/Device/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <filesystem>
#include <fstream>
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);
}
};
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)
{
log<level::ERR>((std::string("Unable to read PMBus device name "
"PATH=") +
path.string())
.c_str());
}
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 = NULL;
std::string val{1, '\0'};
file.open(path);
file.read(&val[0], 1);
value = strtoul(val.c_str(), &err, 10);
if (*err)
{
log<level::ERR>((std::string("Invalid character in sysfs file"
" FILE=") +
path.string() + std::string(" CONTENTS=") + val)
.c_str());
// Catch below and handle as a read failure
elog<InternalFailure>();
}
}
catch (const std::exception& e)
{
auto rc = errno;
log<level::ERR>(
(std::string("Failed to read sysfs file "
"errno=") +
std::to_string(rc) + std::string(" FILENAME=") + path.string())
.c_str());
using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
elog<ReadFailure>(
metadata::CALLOUT_ERRNO(rc),
metadata::CALLOUT_DEVICE_PATH(fs::canonical(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)
{
log<level::ERR>((std::string("Failed to read sysfs file "
"errno=") +
std::to_string(rc) + " FILENAME=" + path.string())
.c_str());
using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
elog<ReadFailure>(
metadata::CALLOUT_ERRNO(rc),
metadata::CALLOUT_DEVICE_PATH(fs::canonical(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;
log<level::ERR>((std::string("Failed to read sysfs file "
"errno=") +
std::to_string(rc) + " FILENAME=" + path.string())
.c_str());
using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
elog<ReadFailure>(
metadata::CALLOUT_ERRNO(rc),
metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str()));
}
return data;
}
std::vector<uint8_t> 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, FileCloser> file{fopen(path.c_str(), "rb")};
if (file)
{
std::vector<uint8_t> 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;
log<level::ERR>(
(std::string("Failed to read sysfs file "
"errno=") +
std::to_string(rc) + " FILENAME=" + path.string())
.c_str());
using metadata =
xyz::openbmc_project::Common::Device::ReadFailure;
elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc),
metadata::CALLOUT_DEVICE_PATH(
fs::canonical(basePath).c_str()));
}
}
return data;
}
return std::vector<uint8_t>{};
}
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;
log<level::ERR>((std::string("Failed to write sysfs file "
"errno=") +
std::to_string(rc) + " FILENAME=" + path.string())
.c_str());
using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
elog<WriteFailure>(
metadata::CALLOUT_ERRNO(rc),
metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str()));
}
}
void PMBus::writeBinary(const std::string& name, std::vector<uint8_t> 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);
log<level::DEBUG>(std::string("Write data to sysfs file "
"FILENAME=" +
path.string())
.c_str());
file.write(reinterpret_cast<const char*>(&data[0]), data.size());
}
catch (const std::exception& e)
{
auto rc = errno;
log<level::ERR>(
(std::string("Failed to write binary data to sysfs file "
"errno=") +
std::to_string(rc) + " FILENAME=" + path.string())
.c_str());
using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
elog<WriteFailure>(
metadata::CALLOUT_ERRNO(rc),
metadata::CALLOUT_DEVICE_PATH(fs::canonical(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 <basePath>/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())
{
log<level::INFO>(std::string("Unable to find hwmon directory "
"in device base path"
" DEVICE_PATH=" +
basePath.string())
.c_str());
}
}
std::unique_ptr<PMBusBase>
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<PMBus>(physpath);
return interface;
}
std::unique_ptr<PMBusBase> createPMBus(std::uint8_t bus,
const std::string& address)
{
return PMBus::createPMBus(bus, address);
}
} // namespace pmbus
} // namespace phosphor