From b89bbf4a2a349cfc9cb047fce70ee064a193f82e Mon Sep 17 00:00:00 2001
From: wangjue <jue.wang2@luxshare-ict.com>
Date: Wed, 25 Sep 2024 19:27:30 +0800
Subject: [PATCH] Add DIMM Temp sensors

Signed-off-by: wangjue <jue.wang2@luxshare-ict.com>
---
 meson_options.txt                             |   1 +
 service_files/meson.build                     |   1 +
 ...xyz.openbmc_project.dimmtempsensor.service |  13 ++
 src/DIMMTempSensor.cpp                        | 194 +++++++++++++++++
 src/DIMMTempSensor.hpp                        |  43 ++++
 src/DIMMTempSensorMain.cpp                    | 201 ++++++++++++++++++
 src/meson.build                               |  15 ++
 7 files changed, 468 insertions(+)
 create mode 100644 service_files/xyz.openbmc_project.dimmtempsensor.service
 create mode 100644 src/DIMMTempSensor.cpp
 create mode 100644 src/DIMMTempSensor.hpp
 create mode 100644 src/DIMMTempSensorMain.cpp

diff --git a/meson_options.txt b/meson_options.txt
index d6a8b96..79bf6b2 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -12,3 +12,4 @@ option('external', type: 'feature', value: 'enabled', description: 'Enable Exter
 option('tests', type: 'feature', value: 'enabled', description: 'Build tests.',)
 option('validate-unsecure-feature', type : 'feature', value : 'disabled', description : 'Enables unsecure features required by validation. Note: mustbe turned off for production images.',)
 option('insecure-sensor-override', type : 'feature', value : 'disabled', description : 'Enables Sensor override feature without any check.',)
+option('dimmtemp', type: 'feature', value: 'enabled', description: 'Enable DIMMtemp sensor.',)
diff --git a/service_files/meson.build b/service_files/meson.build
index 5f5b78c..c60f76c 100644
--- a/service_files/meson.build
+++ b/service_files/meson.build
@@ -10,6 +10,7 @@ unit_files = [
     ['nvme', 'xyz.openbmc_project.nvmesensor.service'],
     ['psu', 'xyz.openbmc_project.psusensor.service'],
     ['external', 'xyz.openbmc_project.externalsensor.service'],
+    ['dimmtemp', 'xyz.openbmc_project.dimmtempsensor.service'],
 ]

 foreach tuple : unit_files
diff --git a/service_files/xyz.openbmc_project.dimmtempsensor.service b/service_files/xyz.openbmc_project.dimmtempsensor.service
new file mode 100644
index 0000000..bb31e3a
--- /dev/null
+++ b/service_files/xyz.openbmc_project.dimmtempsensor.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=DIMM Temp Sensor
+StopWhenUnneeded=false
+Requires=xyz.openbmc_project.EntityManager.service
+After=xyz.openbmc_project.EntityManager.service
+
+[Service]
+Restart=always
+RestartSec=5
+ExecStart=/usr/bin/dimmtempsensor
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/src/DIMMTempSensor.cpp b/src/DIMMTempSensor.cpp
new file mode 100644
index 0000000..7b0a1cd
--- /dev/null
+++ b/src/DIMMTempSensor.cpp
@@ -0,0 +1,194 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include "DIMMTempSensor.hpp"
+
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <chrono>
+#include <cmath>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <vector>
+
+#include <peci.h>
+extern "C"
+{
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+}
+
+constexpr const bool debug = false;
+
+#define PECI_MBX_INDEX_DDR_DIMM_TEMP 0x0E
+
+DIMMTempSensor::DIMMTempSensor(
+    std::shared_ptr<sdbusplus::asio::connection>& conn,
+    boost::asio::io_context& io, const std::string& sensorName,
+    const std::string& sensorConfiguration,
+    sdbusplus::asio::object_server& objectServer,
+    std::vector<thresholds::Threshold>&& thresholdData, const double maxReading,
+    const double minReading, const std::string& sensorType, const char* units,
+    const float pollRate, PowerState readState, uint8_t peciBus,
+    uint8_t peciAddr, uint8_t rank, double calibOffset) :
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"),
+           std::move(thresholdData), sensorConfiguration, "", false, false,
+           maxReading, minReading, conn, readState),
+    sensorPollMs(static_cast<unsigned int>(pollRate * 1000)), peciBus(peciBus),
+    peciAddr(peciAddr), dimmRank(rank), calibOffset(calibOffset),
+    objectServer(objectServer), waitTimer(io)
+{
+    std::string interfacePath = "/xyz/openbmc_project/sensors/" + sensorType +
+                                "/" + name;
+
+    sensorInterface = objectServer.add_interface(
+        interfacePath, "xyz.openbmc_project.Sensor.Value");
+
+    for (const auto& threshold : thresholds)
+    {
+        std::string interface = thresholds::getInterface(threshold.level);
+        thresholdInterfaces[static_cast<size_t>(threshold.level)] =
+            objectServer.add_interface(interfacePath, interface);
+    }
+
+    association = objectServer.add_interface(interfacePath,
+                                             association::interface);
+
+    setInitialProperties(units);
+}
+
+DIMMTempSensor::~DIMMTempSensor()
+{
+    waitTimer.cancel();
+
+    for (const auto& iface : thresholdInterfaces)
+    {
+        objectServer.remove_interface(iface);
+    }
+
+    objectServer.remove_interface(sensorInterface);
+    objectServer.remove_interface(association);
+}
+
+void DIMMTempSensor::init(void)
+{
+    read();
+}
+
+void DIMMTempSensor::checkThresholds(void)
+{
+    thresholds::checkThresholds(this);
+}
+
+int DIMMTempSensor::getDIMMRegsInfoWord(double* ps64data)
+{
+    int ret = -1;
+    ret = getDIMMRegsTemp(ps64data);
+    return ret;
+}
+
+int DIMMTempSensor::getDIMMRegsTemp(double* ps64data)
+{
+    int peciFd = -1;
+    std::string peciDevPath = "/dev/peci-" + std::to_string(peciBus);
+
+    peci_SetDevName(peciDevPath.data());
+
+    if ((peci_Lock(&peciFd, PECI_NO_WAIT) != PECI_CC_SUCCESS) ||
+        (peciFd < 0))
+    {
+        std::cerr << "unable to open " << peciDevPath << " "
+                    << std::strerror(errno) << "\n";
+        return -1;
+    }
+
+    if (peci_Ping(peciAddr) == PECI_CC_SUCCESS)
+    {
+        std::array<uint8_t, 8> pkgConfig{};
+        uint8_t cc = 0;
+
+        if (peci_RdPkgConfig(peciAddr, PECI_MBX_INDEX_DDR_DIMM_TEMP,
+                                dimmRank, 4, &pkgConfig[0],
+                                &cc) != PECI_CC_SUCCESS)
+        {
+            std::cerr << "PECI RdPkgConfig returns failure" << "\n";
+            return -1;
+        }
+
+        *ps64data = (double)pkgConfig[0];
+
+    } else {
+        std::cerr << "PECI Ping returns failure" << "\n";
+        return -1;
+    }
+    peci_Unlock(peciFd);
+    return 0;
+}
+
+void DIMMTempSensor::read(void)
+{
+    waitTimer.expires_after(std::chrono::milliseconds(sensorPollMs));
+    waitTimer.async_wait([this](const boost::system::error_code& ec) {
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            return; // we're being cancelled
+        }
+        // read timer error
+        if (ec)
+        {
+            std::cerr << "timer error\n";
+            return;
+        }
+        if (readingStateGood())
+        {
+            double temp = 0;
+            int ret = getDIMMRegsInfoWord(&temp);
+            if (ret == 0)
+            {
+                double v = temp;
+                if constexpr (debug)
+                {
+                    std::cerr << "Value update to " << v << ", raw reading "
+                              << temp << "\n";
+                }
+                v += calibOffset;
+                updateValue(v);
+            }
+            else
+            {
+                std::cerr << "Invalid read getDIMMRegsInfoWord.\n";
+                incrementError();
+            }
+        }
+        else
+        {
+            updateValue(std::numeric_limits<double>::quiet_NaN());
+        }
+        read();
+    });
+}
diff --git a/src/DIMMTempSensor.hpp b/src/DIMMTempSensor.hpp
new file mode 100644
index 0000000..dd52053
--- /dev/null
+++ b/src/DIMMTempSensor.hpp
@@ -0,0 +1,43 @@
+#pragma once
+#include "sensor.hpp"
+
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+
+#include <chrono>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+struct DIMMTempSensor : public Sensor
+{
+    DIMMTempSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
+                   boost::asio::io_context& io, const std::string& name,
+                   const std::string& sensorConfiguration,
+                   sdbusplus::asio::object_server& objectServer,
+                   std::vector<thresholds::Threshold>&& thresholds,
+                   const double maxReading, const double minReading,
+                   const std::string& sensorType, const char* units,
+                   const float pollRate, PowerState readState, uint8_t peciBus,
+                   uint8_t peciAddr, uint8_t rank, double calibOffset);
+    ~DIMMTempSensor() override;
+
+    void checkThresholds(void) override;
+    void read(void);
+    void init(void);
+
+    unsigned int sensorPollMs;
+
+    uint8_t peciBus;
+    uint8_t peciAddr;
+    uint8_t dimmRank;
+    double calibOffset;
+
+  private:
+    int getDIMMRegsInfoWord(double* ps64data);
+    int getDIMMRegsTemp(double* ps64data);
+   // int getDIMMRegsPower(double* ps64data);
+    sdbusplus::asio::object_server& objectServer;
+    boost::asio::steady_timer waitTimer;
+};
diff --git a/src/DIMMTempSensorMain.cpp b/src/DIMMTempSensorMain.cpp
new file mode 100644
index 0000000..042659f
--- /dev/null
+++ b/src/DIMMTempSensorMain.cpp
@@ -0,0 +1,201 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+// #include <math.h>
+#include <DIMMTempSensor.hpp>
+#include <Utils.hpp>
+#include <VariantVisitors.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <chrono>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <vector>
+
+constexpr const bool debug = false;
+constexpr const char* configInterface =
+    "xyz.openbmc_project.Configuration.DIMMTemp";
+
+static constexpr double maxTempReading = 127;
+static constexpr double minTempReading = -128;
+static constexpr double maxPowerReading = 500;
+static constexpr double minPowerReading = 0;
+
+void createSensors(
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+    boost::container::flat_map<std::string, std::unique_ptr<DIMMTempSensor>>&
+        sensors,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+    if (!dbusConnection)
+    {
+        std::cerr << "Connection not created\n";
+        return;
+    }
+
+    dbusConnection->async_method_call(
+        [&io, &objectServer, &dbusConnection, &sensors](
+            boost::system::error_code ec, const ManagedObjectType& resp) {
+        if (ec)
+        {
+            std::cerr << "Error contacting entity manager\n";
+            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))
+                {
+                    std::cerr << "error populating thresholds for " << name
+                              << "\n";
+                }
+
+                float pollRate = loadVariant<double>(entry.second, "PollRate");
+                uint8_t peciBus = loadVariant<uint8_t>(entry.second, "Bus");
+                uint8_t peciAddr = loadVariant<uint8_t>(entry.second, "Address");
+                uint8_t dimmRank = loadVariant<uint8_t>(entry.second, "Rank");
+
+                /* calibration offset */
+                auto findCalibOffset = entry.second.find("Offset");
+                double calibOffset = 0;
+                if (findCalibOffset != entry.second.end())
+                {
+                    calibOffset = std::visit(VariantToDoubleVisitor(),
+                                             findCalibOffset->second);
+                }
+
+                /* PowerState */
+                auto findPowerOn = entry.second.find("PowerState");
+                PowerState readState = PowerState::always;
+                if (findPowerOn != entry.second.end())
+                {
+                    std::string powerState = std::visit(
+                        VariantToStringVisitor(), findPowerOn->second);
+                    setReadState(powerState, readState);
+                }
+
+                if constexpr (debug)
+                {
+                    std::cerr << "Configuration parsed for \n\t" << entry.first
+                              << "\n"
+                              << "with\n"
+                              << "\tName: " << name << "\n"
+                              << "\tBus: " << static_cast<int>(peciBus) << "\n"
+                              << "\tAddress: " << static_cast<int>(peciAddr)
+                              << "\n"
+                              << "\tDimmRank: " << static_cast<int>(dimmRank)
+                              << "\tOffset: " << calibOffset << "\n";
+                }
+
+                auto& sensor = sensors[name];
+                sensor = nullptr;
+
+                if (name.ends_with("_Temp"))
+                {
+                    sensor = std::make_unique<DIMMTempSensor>(
+                        dbusConnection, io, name, pathPair.first, objectServer,
+                        std::move(sensorThresholds), maxTempReading,
+                        minTempReading, "temperature",
+                        sensor_paths::unitDegreesC, pollRate, readState,
+                        peciBus, peciAddr, dimmRank, 0);
+                }
+                else if (name.ends_with("_Power"))
+                {
+                    sensor = std::make_unique<DIMMTempSensor>(
+                        dbusConnection, io, name, pathPair.first, objectServer,
+                        std::move(sensorThresholds), maxPowerReading,
+                        minPowerReading, "power", sensor_paths::unitWatts,
+                        pollRate, readState, peciBus, peciAddr, dimmRank, 0);
+                }
+
+                sensor->init();
+            }
+        }
+    },
+        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.DIMMTempSensor");
+
+    boost::container::flat_map<std::string, std::unique_ptr<DIMMTempSensor>>
+        sensors;
+    boost::asio::post(
+        io, [&]() { createSensors(io, objectServer, sensors, systemBus); });
+
+    boost::asio::steady_timer configTimer(io);
+    std::function<void(sdbusplus::message::message&)> eventHandler =
+        [&](sdbusplus::message::message&) {
+        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)
+            {
+                std::cerr << "timer error\n";
+                return;
+            }
+            createSensors(io, objectServer, sensors, systemBus);
+            if (sensors.empty())
+            {
+                std::cout << "Configuration not detected\n";
+            }
+        });
+    };
+
+    sdbusplus::bus::match::match configMatch(
+        static_cast<sdbusplus::bus::bus&>(*systemBus),
+        "type='signal',member='PropertiesChanged',"
+        "path_namespace='" +
+            std::string(inventoryPath) +
+            "',"
+            "arg0namespace='" +
+            configInterface + "'",
+        eventHandler);
+
+    io.run();
+}
diff --git a/src/meson.build b/src/meson.build
index 9f56c31..7c33e3a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -234,3 +234,18 @@ if get_option('external').enabled()
         install: true,
     )
 endif
+
+if get_option('dimmtemp').allowed()
+    executable(
+        'dimmtempsensor',
+        'DIMMTempSensor.cpp',
+        'DIMMTempSensorMain.cpp',
+        dependencies: [
+            default_deps,
+            thresholds_dep,
+            utils_dep,
+            peci_dep,
+        ],
+        install: true,
+    )
+endif
\ No newline at end of file
--
2.34.1

