| /* | 
 |  * 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 <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 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}; | 
 |     std::array<struct i2c_msg, 2> msgs = { | 
 |         {{0, 0, 0, nullptr}, {0, 0, 0, nullptr}}}; | 
 |     std::array<uint8_t, 8> cmd{}; | 
 |  | 
 |     args.msgs = msgs.data(); | 
 |     args.nmsgs = msgs.size(); | 
 |  | 
 |     msgs[0].addr = addr; | 
 |     msgs[0].flags = 0; | 
 |     msgs[0].buf = cmd.data(); | 
 |     // handle two bytes offset | 
 |     if (offset > 255) | 
 |     { | 
 |         msgs[0].len = 2; | 
 |         msgs[0].buf[0] = offset >> 8; | 
 |         msgs[0].buf[1] = offset & 0xFF; | 
 |     } | 
 |     else | 
 |     { | 
 |         msgs[0].len = 1; | 
 |         msgs[0].buf[0] = offset & 0xFF; | 
 |     } | 
 |  | 
 |     msgs[1].addr = addr; | 
 |     msgs[1].flags = I2C_M_RD; | 
 |     msgs[1].len = length; | 
 |     msgs[1].buf = reading; | 
 |  | 
 |     // write offset | 
 |     // 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; | 
 |     } | 
 |     lg2::debug("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) | 
 |     { | 
 |         lg2::debug("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"); | 
 |             lg2::debug( | 
 |                 "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; | 
 | } |