blob: 05b73ec262d47712045e442886646f2eef8d59a9 [file] [log] [blame] [edit]
/*
* SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION &
* AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#include "SmbpbiSensor.hpp"
#include "SensorPaths.hpp"
#include "Thresholds.hpp"
#include "Utils.hpp"
#include "sensor.hpp"
#include <linux/i2c.h>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/container/flat_map.hpp>
#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/bus/match.hpp>
#include <sdbusplus/message.hpp>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <functional>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
extern "C"
{
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
}
constexpr const bool debug = false;
constexpr const char* configInterface =
"xyz.openbmc_project.Configuration.SmbpbiVirtualEeprom";
constexpr const char* sensorRootPath = "/xyz/openbmc_project/sensors/";
constexpr const char* objectType = "SmbpbiVirtualEeprom";
boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>> sensors;
SmbpbiSensor::SmbpbiSensor(
std::shared_ptr<sdbusplus::asio::connection>& conn,
boost::asio::io_context& io, const std::string& sensorName,
const std::string& sensorConfiguration, const std::string& objType,
sdbusplus::asio::object_server& objectServer,
std::vector<thresholds::Threshold>&& thresholdData, uint8_t busId,
uint8_t addr, uint16_t offset, std::string& sensorUnits,
std::string& valueType, size_t pollTime, double minVal, double maxVal,
std::string& path) :
Sensor(escapeName(sensorName), std::move(thresholdData),
sensorConfiguration, objType, false, false, maxVal, minVal, conn),
busId(busId), addr(addr), offset(offset), sensorUnits(sensorUnits),
valueType(valueType), objectServer(objectServer),
inputDev(io, path, boost::asio::random_access_file::read_only),
waitTimer(io), pollRateSecond(pollTime)
{
sensorType = sensor_paths::getPathForUnits(sensorUnits);
std::string sensorPath = sensorRootPath + sensorType + "/";
sensorInterface =
objectServer.add_interface(sensorPath + name, sensorValueInterface);
for (const auto& threshold : thresholds)
{
std::string interface = thresholds::getInterface(threshold.level);
thresholdInterfaces[static_cast<size_t>(threshold.level)] =
objectServer.add_interface(sensorPath + name, interface);
}
association =
objectServer.add_interface(sensorPath + name, association::interface);
if (sensorType == "temperature")
{
setInitialProperties(sensor_paths::unitDegreesC);
}
else if (sensorType == "power")
{
setInitialProperties(sensor_paths::unitWatts);
}
else if (sensorType == "energy")
{
setInitialProperties(sensor_paths::unitJoules);
}
else if (sensorType == "voltage")
{
setInitialProperties(sensor_paths::unitVolts);
}
else
{
lg2::error("no sensor type found");
}
}
SmbpbiSensor::~SmbpbiSensor()
{
inputDev.close();
waitTimer.cancel();
for (const auto& iface : thresholdInterfaces)
{
objectServer.remove_interface(iface);
}
objectServer.remove_interface(sensorInterface);
objectServer.remove_interface(association);
}
void SmbpbiSensor::init()
{
read();
}
void SmbpbiSensor::checkThresholds()
{
thresholds::checkThresholds(this);
}
double SmbpbiSensor::convert2Temp(const uint8_t* raw)
{
// Temp data is encoded in SMBPBI format. The 3 MSBs denote
// the integer portion, LSB is an encoded fraction.
// this automatic convert to int (two's complement integer)
int32_t intg = (raw[3] << 24 | raw[2] << 16 | raw[1] << 8 | raw[0]);
uint8_t frac = uint8_t(raw[0]);
// shift operation on a int keeps the sign in two's complement
intg >>= 8;
double temp = 0;
if (intg > 0)
{
temp = double(intg) + double(frac / 256.0);
}
else
{
temp = double(intg) - double(frac / 256.0);
}
return temp;
}
double SmbpbiSensor::convert2Power(const uint8_t* raw)
{
// Power data is encoded as a 4-byte unsigned integer
uint32_t val = (raw[3] << 24) + (raw[2] << 16) + (raw[1] << 8) + raw[0];
// mWatts to Watts
double power = static_cast<double>(val) / 1000;
return power;
}
int SmbpbiSensor::i2cReadDataBytesDouble(double& reading)
{
constexpr int length =
i2CReadLenValues[static_cast<size_t>(I2C_READ_LEN_INDEX::FLOAT64)];
static_assert(length == sizeof(reading), "Unsupported arch");
std::array<uint8_t, length> buf{};
int ret = i2cReadDataBytes(buf.data(), length);
if (ret < 0)
{
return ret;
}
// there is no value updated from HMC if reading data is all 0xff
// Return NaN since reading is already a double
if (checkInvalidReading(buf.data(), length))
{
reading = std::numeric_limits<double>::quiet_NaN();
return 0;
}
uint64_t tempd = 0;
for (int byteI = 0; byteI < length; byteI++)
{
tempd |= static_cast<uint64_t>(buf[byteI]) << (8 * byteI);
}
std::memcpy(&reading, &tempd, sizeof(reading));
return 0;
}
int SmbpbiSensor::i2cReadDataBytesUI64(uint64_t& reading)
{
constexpr int length =
i2CReadLenValues[static_cast<size_t>(I2C_READ_LEN_INDEX::UINT64)];
static_assert(length == sizeof(reading), "Unsupported arch");
std::array<uint8_t, length> buf{};
int ret = i2cReadDataBytes(buf.data(), length);
if (ret < 0)
{
return ret;
}
reading = 0;
for (int byteI = 0; byteI < length; byteI++)
{
reading |= static_cast<uint64_t>(buf[byteI]) << (8 * byteI);
}
return 0;
}
// Generic i2c Command to read bytes
int SmbpbiSensor::i2cReadDataBytes(uint8_t* reading, int length)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
const int fd = inputDev.native_handle();
if (fd < 0)
{
lg2::error(" unable to open i2c device on bus {BUS} err={FD}", "BUS",
busId, "FD", fd);
return -1;
}
unsigned long funcs = 0;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
{
lg2::error(" I2C_FUNCS not supported");
return -1;
}
int ret = 0;
struct i2c_rdwr_ioctl_data args = {nullptr, 0};
struct i2c_msg msg = {0, 0, 0, nullptr};
std::array<uint8_t, 8> cmd{};
msg.addr = addr;
args.msgs = &msg;
args.nmsgs = 1;
msg.flags = 0;
msg.buf = cmd.data();
// handle two bytes offset
if (offset > 255)
{
msg.len = 2;
msg.buf[0] = offset >> 8;
msg.buf[1] = offset & 0xFF;
}
else
{
msg.len = 1;
msg.buf[0] = offset & 0xFF;
}
// write offset
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
ret = ioctl(fd, I2C_RDWR, &args);
if (ret < 0)
{
return ret;
}
msg.flags = I2C_M_RD;
msg.len = length;
msg.buf = reading;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
ret = ioctl(fd, I2C_RDWR, &args);
if (ret < 0)
{
return ret;
}
return 0;
}
int SmbpbiSensor::readRawEEPROMData(double& data)
{
uint64_t reading = 0;
int ret = i2cReadDataBytesUI64(reading);
if (ret < 0)
{
return ret;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
if (checkInvalidReading(reinterpret_cast<uint8_t*>(&reading),
sizeof(reading)))
{
data = std::numeric_limits<double>::quiet_NaN();
return 0;
}
if (debug)
{
lg2::error("offset: {OFFSET} reading: {READING}", "OFFSET", offset,
"READING", reading);
}
if (sensorType == "temperature")
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
data = convert2Temp(reinterpret_cast<uint8_t*>(&reading));
}
else if (sensorType == "power")
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
data = convert2Power(reinterpret_cast<uint8_t*>(&reading));
}
else if (sensorType == "energy")
{
data = reading / 1000.0; // mJ to J (double)
}
else
{
data = reading; // Voltage
}
return 0;
}
int SmbpbiSensor::readFloat64EEPROMData(double& data)
{
double reading = 0;
int ret = i2cReadDataBytesDouble(reading);
if (ret < 0)
{
return ret;
}
data = reading;
return 0;
}
void SmbpbiSensor::waitReadCallback(const boost::system::error_code& ec)
{
if (ec == boost::asio::error::operation_aborted)
{
// we're being cancelled
return;
}
// read timer error
if (ec)
{
lg2::error("timer error");
return;
}
double temp = 0;
int ret = 0;
// Sensor reading value types are sensor-specific. So, read
// and interpret sensor data based on it's value type.
if (valueType == "UINT64")
{
ret = readRawEEPROMData(temp);
}
else if (valueType == "FLOAT64")
{
ret = readFloat64EEPROMData(temp);
}
else
{
return;
}
if (ret >= 0)
{
if constexpr (debug)
{
lg2::error("Value update to {TEMP}", "TEMP", temp);
}
updateValue(temp);
}
else
{
lg2::error("Invalid read getRegsInfo");
incrementError();
}
read();
}
void SmbpbiSensor::read()
{
size_t pollTime = getPollRate(); // in seconds
waitTimer.expires_after(std::chrono::seconds(pollTime));
waitTimer.async_wait([this](const boost::system::error_code& ec) {
this->waitReadCallback(ec);
});
}
static void createSensorCallback(
boost::system::error_code ec, const ManagedObjectType& resp,
boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>>&
sensors)
{
if (ec)
{
lg2::error("Error contacting entity manager");
return;
}
for (const auto& pathPair : resp)
{
for (const auto& entry : pathPair.second)
{
if (entry.first != configInterface)
{
continue;
}
std::string name = loadVariant<std::string>(entry.second, "Name");
std::vector<thresholds::Threshold> sensorThresholds;
if (!parseThresholdsFromConfig(pathPair.second, sensorThresholds))
{
lg2::error("error populating thresholds for {NAME}", "NAME",
name);
}
uint8_t busId = loadVariant<uint8_t>(entry.second, "Bus");
uint8_t addr = loadVariant<uint8_t>(entry.second, "Address");
uint16_t off = loadVariant<uint16_t>(entry.second, "ReadOffset");
std::string sensorUnits =
loadVariant<std::string>(entry.second, "Units");
std::string valueType =
loadVariant<std::string>(entry.second, "ValueType");
if (valueType != "UINT64" && valueType != "FLOAT64")
{
lg2::error("Invalid ValueType for sensor: {NAME}", "NAME",
name);
break;
}
size_t rate = loadVariant<uint8_t>(entry.second, "PollRate");
double minVal = loadVariant<double>(entry.second, "MinValue");
double maxVal = loadVariant<double>(entry.second, "MaxValue");
if constexpr (debug)
{
lg2::info("Configuration parsed for \n\t {CONF}\nwith\n"
"\tName: {NAME}\n"
"\tBus: {BUS}\n"
"\tAddress:{ADDR}\n"
"\tOffset: {OFF}\n"
"\tType : {TYPE}\n"
"\tValue Type : {VALUETYPE}\n"
"\tPollrate: {RATE}\n"
"\tMinValue: {MIN}\n"
"\tMaxValue: {MAX}\n",
"CONF", entry.first, "NAME", name, "BUS",
static_cast<int>(busId), "ADDR",
static_cast<int>(addr), "OFF", static_cast<int>(off),
"UNITS", sensorUnits, "VALUETYPE", valueType, "RATE",
rate, "MIN", minVal, "MAX", maxVal);
}
auto& sensor = sensors[name];
sensor = nullptr;
std::string path = "/dev/i2c-" + std::to_string(busId);
sensor = std::make_unique<SmbpbiSensor>(
dbusConnection, io, name, pathPair.first, objectType,
objectServer, std::move(sensorThresholds), busId, addr, off,
sensorUnits, valueType, rate, minVal, maxVal, path);
sensor->init();
}
}
}
void createSensors(
boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>>&
sensors,
std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
{
if (!dbusConnection)
{
lg2::error("Connection not created");
return;
}
dbusConnection->async_method_call(
[&io, &objectServer, &dbusConnection, &sensors](
boost::system::error_code ec, const ManagedObjectType& resp) {
createSensorCallback(ec, resp, io, objectServer, dbusConnection,
sensors);
},
entityManagerName, "/xyz/openbmc_project/inventory",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
}
int main()
{
boost::asio::io_context io;
auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objectServer(systemBus, true);
objectServer.add_manager("/xyz/openbmc_project/sensors");
systemBus->request_name("xyz.openbmc_project.SMBPBI");
boost::asio::post(io, [&]() {
createSensors(io, objectServer, sensors, systemBus);
});
boost::asio::steady_timer configTimer(io);
std::function<void(sdbusplus::message_t&)> eventHandler =
[&](sdbusplus::message_t&) {
configTimer.expires_after(std::chrono::seconds(1));
// create a timer because normally multiple properties change
configTimer.async_wait([&](const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
return; // we're being canceled
}
// config timer error
if (ec)
{
lg2::error("timer error");
return;
}
createSensors(io, objectServer, sensors, systemBus);
if (sensors.empty())
{
lg2::info("Configuration not detected");
}
});
};
sdbusplus::bus::match_t configMatch(
static_cast<sdbusplus::bus_t&>(*systemBus),
"type='signal',member='PropertiesChanged',"
"path_namespace='" +
std::string(inventoryPath) +
"',"
"arg0namespace='" +
configInterface + "'",
eventHandler);
setupManufacturingModeMatch(*systemBus);
io.run();
return 0;
}