| /* |
| * 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; |
| } |