/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include } 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> sensors; SmbpbiSensor::SmbpbiSensor( std::shared_ptr& 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&& 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(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(val) / 1000; return power; } int SmbpbiSensor::i2cReadDataBytesDouble(double& reading) { constexpr int length = i2CReadLenValues[static_cast(I2C_READ_LEN_INDEX::FLOAT64)]; static_assert(length == sizeof(reading), "Unsupported arch"); std::array 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::quiet_NaN(); return 0; } uint64_t tempd = 0; for (int byteI = 0; byteI < length; byteI++) { tempd |= static_cast(buf[byteI]) << (8 * byteI); } std::memcpy(&reading, &tempd, sizeof(reading)); return 0; } int SmbpbiSensor::i2cReadDataBytesUI64(uint64_t& reading) { constexpr int length = i2CReadLenValues[static_cast(I2C_READ_LEN_INDEX::UINT64)]; static_assert(length == sizeof(reading), "Unsupported arch"); std::array buf{}; int ret = i2cReadDataBytes(buf.data(), length); if (ret < 0) { return ret; } reading = 0; for (int byteI = 0; byteI < length; byteI++) { reading |= static_cast(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 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(&reading), sizeof(reading))) { data = std::numeric_limits::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(&reading)); } else if (sensorType == "power") { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) data = convert2Power(reinterpret_cast(&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& dbusConnection, boost::container::flat_map>& 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(entry.second, "Name"); std::vector sensorThresholds; if (!parseThresholdsFromConfig(pathPair.second, sensorThresholds)) { lg2::error("error populating thresholds for {NAME}", "NAME", name); } uint8_t busId = loadVariant(entry.second, "Bus"); uint8_t addr = loadVariant(entry.second, "Address"); uint16_t off = loadVariant(entry.second, "ReadOffset"); std::string sensorUnits = loadVariant(entry.second, "Units"); std::string valueType = loadVariant(entry.second, "ValueType"); if (valueType != "UINT64" && valueType != "FLOAT64") { lg2::error("Invalid ValueType for sensor: {NAME}", "NAME", name); break; } size_t rate = loadVariant(entry.second, "PollRate"); double minVal = loadVariant(entry.second, "MinValue"); double maxVal = loadVariant(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(busId), "ADDR", static_cast(addr), "OFF", static_cast(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( 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>& sensors, std::shared_ptr& 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(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 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(*systemBus), "type='signal',member='PropertiesChanged'," "path_namespace='" + std::string(inventoryPath) + "'," "arg0namespace='" + configInterface + "'", eventHandler); setupManufacturingModeMatch(*systemBus); io.run(); return 0; }