blob: a542880f7bdf4fa28c15b62a9e15f7ac03bab93b [file] [log] [blame]
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "FspYhPsu.h"
#include "ietf-hardware/thresholds.h"
#include "utils/UniqueResource.h"
#include "utils/log.h"
namespace velia::ietf_hardware {
TransientI2C::TransientI2C(const uint8_t bus, const uint8_t address, const std::string& driverName)
: m_address(address)
, m_driverName(driverName)
, m_isPresentPath("/dev/i2c-" + std::to_string(bus))
, m_bindPath("/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/new_device")
, m_unbindPath("/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/delete_device")
{
std::ostringstream addressString;
addressString << std::showbase << std::hex << int{m_address};
m_addressString = addressString.str();
}
TransientI2C::~TransientI2C() = default;
bool TransientI2C::isPresent() const
{
auto file = open(m_isPresentPath.c_str(), O_RDWR);
if (file < 0) {
throw std::system_error(errno, std::system_category(), "TransientI2C::isPresent: open()");
}
auto fdClose = utils::make_unique_resource([] {}, [file] {
close(file);
});
if (ioctl(file, I2C_SLAVE_FORCE, m_address) < 0) {
throw std::system_error(errno, std::system_category(), "TransientI2C::isPresent: ioctl()");
}
char bufferIn[1];
return read(file, bufferIn, 1) != -1;
}
void TransientI2C::bind() const
{
spdlog::get("hardware")->info("Registering PSU at {}", m_addressString);
std::ofstream ofs(m_bindPath);
if (!ofs.is_open()) {
throw std::runtime_error("TransientI2C::bind(): can't open file '" + m_bindPath + "'");
}
ofs << m_driverName << " " << m_addressString;
if (ofs.bad()) {
throw std::runtime_error("TransientI2C::bind(): can't write file '" + m_bindPath + "'");
}
}
void TransientI2C::unbind() const
{
spdlog::get("hardware")->info("Deregistering PSU from {}", m_addressString);
std::ofstream ofs(m_unbindPath);
if (!ofs.is_open()) {
throw std::runtime_error("TransientI2C::unbind(): can't open file '" + m_unbindPath + "'");
}
ofs << m_addressString;
if (ofs.bad()) {
throw std::runtime_error("TransientI2C::unbind(): can't write file '" + m_unbindPath + "'");
}
}
FspYhPsu::FspYhPsu(const std::filesystem::path& hwmonDir, const std::string& psuName, std::shared_ptr<TransientI2C> i2c)
: m_i2c(i2c)
, m_hwmonDir(hwmonDir)
, m_namePrefix("ne:"s + psuName)
, m_staticData(velia::ietf_hardware::data_reader::StaticData(m_namePrefix, "ne", {{"class", "iana-hardware:power-supply"}})())
{
m_exit = false;
m_psuWatcher = std::thread([this] {
while (!m_exit) {
if (m_i2c->isPresent()) {
if (!std::filesystem::is_directory(m_hwmonDir)) {
m_i2c->bind();
}
// The driver might already be loaded before the program starts. This ensures that the properties still
// get initialized if that's the case.
if (!m_hwmon) {
std::lock_guard lk(m_mtx);
createPower();
}
} else if (std::filesystem::is_directory(m_hwmonDir)) {
{
std::lock_guard lk(m_mtx);
m_hwmon = nullptr;
m_properties.clear();
}
m_i2c->unbind();
}
// There is a bug, where TSan falsely reports "double lock of a mutex", one here, in the watcher thread and
// another, in readValues(). These locks are in a different thread. The report can be fixed by using
// different mutexes for this condition variable and for the reading/binding/unbinding. Work around this by
// using different mutexes (instead of creating a suppression).
// FIXME: This false positive is fixed in LLVM 12, so remove this after it's available.
// // https://github.com/google/sanitizers/issues/1259
std::unique_lock lock(m_condMtx);
m_cond.wait_for(lock, std::chrono::seconds(3));
}
});
}
FspYhPsu::~FspYhPsu()
{
m_exit = true;
m_psuWatcher.join();
}
void FspYhPsu::createPower()
{
m_hwmon = std::make_shared<velia::ietf_hardware::sysfs::HWMon>(m_hwmonDir);
using velia::ietf_hardware::OneThreshold;
using velia::ietf_hardware::Thresholds;
using velia::ietf_hardware::data_reader::Fans;
using velia::ietf_hardware::data_reader::SensorType;
using velia::ietf_hardware::data_reader::SysfsValue;
auto registerReader = [&]<typename DataReaderType>(DataReaderType&& reader) {
m_thresholds.merge(reader.thresholds());
m_properties.emplace_back(reader);
};
registerReader(SysfsValue<SensorType::Temperature>(m_namePrefix + ":temperature-1",
m_namePrefix,
m_hwmon,
1,
Thresholds<int64_t>{
.criticalLow = std::nullopt,
.warningLow = std::nullopt,
.warningHigh = OneThreshold<int64_t>{40000, 1000},
.criticalHigh = OneThreshold<int64_t>{45000, 1000},
}));
registerReader(SysfsValue<SensorType::Temperature>(m_namePrefix + ":temperature-2",
m_namePrefix,
m_hwmon,
2,
Thresholds<int64_t>{
.criticalLow = std::nullopt,
.warningLow = std::nullopt,
.warningHigh = OneThreshold<int64_t>{40000, 1000},
.criticalHigh = OneThreshold<int64_t>{45000, 1000},
}));
registerReader(SysfsValue<SensorType::Current>(m_namePrefix + ":current-in", m_namePrefix, m_hwmon, 1));
registerReader(SysfsValue<SensorType::Current>(m_namePrefix + ":current-12V", m_namePrefix, m_hwmon, 2));
registerReader(SysfsValue<SensorType::VoltageAC>(m_namePrefix + ":voltage-in",
m_namePrefix,
m_hwmon,
1,
Thresholds<int64_t>{
.criticalLow = OneThreshold<int64_t>{90000, 3000},
.warningLow = OneThreshold<int64_t>{100000, 3000},
.warningHigh = OneThreshold<int64_t>{245000, 3000},
.criticalHigh = OneThreshold<int64_t>{264000, 3000},
}));
registerReader(SysfsValue<SensorType::VoltageDC>(m_namePrefix + ":voltage-12V",
m_namePrefix,
m_hwmon,
2,
Thresholds<int64_t>{
.criticalLow = OneThreshold<int64_t>{11300, 50},
.warningLow = OneThreshold<int64_t>{11500, 50},
.warningHigh = OneThreshold<int64_t>{12500, 50},
.criticalHigh = OneThreshold<int64_t>{12700, 50},
}));
registerReader(SysfsValue<SensorType::Power>(m_namePrefix + ":power-in", m_namePrefix, m_hwmon, 1));
registerReader(SysfsValue<SensorType::Power>(m_namePrefix + ":power-out", m_namePrefix, m_hwmon, 2));
registerReader(Fans(m_namePrefix + ":fan",
m_namePrefix,
m_hwmon,
1,
Thresholds<int64_t>{
.criticalLow = OneThreshold<int64_t>{1500, 150}, // datasheet YH5151 (sec. 3.4) says critical is 1000 and warning 2000; giving 500rpm extra reserve
.warningLow = OneThreshold<int64_t>{2500, 150},
.warningHigh = std::nullopt,
.criticalHigh = std::nullopt,
}));
registerReader(SysfsValue<SensorType::Current>(m_namePrefix + ":current-5Vsb", m_namePrefix, m_hwmon, 3));
registerReader(SysfsValue<SensorType::VoltageDC>(m_namePrefix + ":voltage-5Vsb",
m_namePrefix,
m_hwmon,
3,
Thresholds<int64_t>{
.criticalLow = OneThreshold<int64_t>{4600, 50},
.warningLow = OneThreshold<int64_t>{4700, 50},
.warningHigh = OneThreshold<int64_t>{5300, 50},
.criticalHigh = OneThreshold<int64_t>{5400, 50},
}));
}
DataTree FspYhPsu::readValues()
{
std::unique_lock lock(m_mtx);
DataTree res(m_staticData);
if (m_properties.empty()) {
res["/ietf-hardware:hardware/component[name='" + m_namePrefix + "']/state/oper-state"] = "disabled";
return res;
}
for (auto& reader : m_properties) {
try {
res.merge(reader());
} catch (std::logic_error& ex) {
// The PSU might get disconnected before the watcher thread is able to react. Because of this, the sysfs
// read can fail. We must react to this and catch the exception from readFileInt64. If we cannot get all
// data, we'll consider the data we got as invalid, so we'll return an empty map.
spdlog::get("hardware")->warn("Couldn't read PSU sysfs data (maybe the PSU was just ejected?): "s + ex.what());
res = m_staticData;
res["/ietf-hardware:hardware/component[name='" + m_namePrefix + "']/state/oper-state"] = "disabled";
lock.unlock();
m_cond.notify_all();
return res;
}
}
return res;
}
ThresholdsBySensorPath FspYhPsu::thresholds() const
{
return m_thresholds;
}
}