dcmi: get temperature readings: read temp sensors

This commit adds the code to read temperatures from various sensors. A
JSON config file tells what D-Bus object(s) to look up for a specific
temperature reading.

Resolves openbmc/openbmc#2626

Change-Id: Ic357ec2a53ff250a2517d68defe5850ef353568a
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/dcmihandler.cpp b/dcmihandler.cpp
index e5a5384..8b47845 100644
--- a/dcmihandler.cpp
+++ b/dcmihandler.cpp
@@ -9,7 +9,7 @@
 #include <stdint.h>
 #include <fstream>
 #include <bitset>
-#include "nlohmann/json.hpp"
+#include <cmath>
 #include "xyz/openbmc_project/Common/error.hpp"
 
 using namespace phosphor::logging;
@@ -736,18 +736,180 @@
 namespace temp_readings
 {
 
+Json parseConfig()
+{
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.is_open())
+    {
+        log<level::ERR>("Temperature readings JSON file not found");
+        elog<InternalFailure>();
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("Temperature readings JSON parser failure");
+        elog<InternalFailure>();
+    }
+
+    return data;
+}
+
+Temperature readTemp(const std::string& dbusService,
+                     const std::string& dbusPath)
+{
+    // Read the temperature value from d-bus object. Need some conversion.
+    // As per the interface xyz.openbmc_project.Sensor.Value, the temperature
+    // is an int64_t and in degrees C. It needs to be scaled by using the
+    // formula Value * 10^Scale. The ipmi spec has the temperature as a uint8_t,
+    // with a separate single bit for the sign.
+
+    sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
+    auto result = ipmi::getAllDbusProperties(bus, dbusService, dbusPath,
+                                        "xyz.openbmc_project.Sensor.Value");
+    auto temperature = result.at("Value").get<int64_t>();
+    uint64_t absTemp = std::abs(temperature);
+
+    auto factor = result.at("Scale").get<int64_t>();
+    uint64_t scale = std::pow(10, factor); // pow() returns float/double
+    unsigned long long tempDegrees = 0;
+    // Overflow safe multiplication when the scale is > 0
+    if (scale && __builtin_umulll_overflow(
+                     absTemp, scale, &tempDegrees))
+    {
+        log<level::ERR>("Multiplication overflow detected",
+                        entry("TEMP_VALUE=%llu", absTemp),
+                        entry("SCALE_FACTOR=%llu", scale));
+        elog<InternalFailure>();
+    }
+    else
+    {
+        // The (uint64_t)scale value is 0, effectively this is division
+        tempDegrees = absTemp * std::pow(10, factor);
+    }
+    // Max absolute temp as per ipmi spec is 128.
+    if (tempDegrees > maxTemp)
+    {
+        tempDegrees = maxTemp;
+    }
+
+    return std::make_tuple(static_cast<uint8_t>(tempDegrees),
+                           (temperature < 0));
+}
+
 std::tuple<Response, NumInstances> read(const std::string& type,
                                         uint8_t instance)
 {
-    Response empty{};
-    return std::make_tuple(empty, 0);
+    Response response{};
+    sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
+
+    if (!instance)
+    {
+        log<level::ERR>("Expected non-zero instance");
+        elog<InternalFailure>();
+    }
+
+    auto data = parseConfig();
+    static const std::vector<Json> empty{};
+    std::vector<Json> readings = data.value(type, empty);
+    size_t numInstances = readings.size();
+    for (const auto& j : readings)
+    {
+        uint8_t instanceNum = j.value("instance", 0);
+        // Not the instance we're interested in
+        if (instanceNum != instance)
+        {
+            continue;
+        }
+
+        std::string path = j.value("dbus", "");
+        std::string service;
+        try
+        {
+            service =
+                ipmi::getService(bus,
+                                 "xyz.openbmc_project.Sensor.Value",
+                                 path);
+        }
+        catch (std::exception& e)
+        {
+            log<level::DEBUG>(e.what());
+            return std::make_tuple(response, numInstances);
+        }
+
+        response.instance = instance;
+        uint8_t temp{};
+        bool sign{};
+        std::tie(temp, sign) = readTemp(service, path);
+        response.temperature = temp;
+        response.sign = sign;
+
+        // Found the instance we're interested in
+        break;
+    }
+
+    if (numInstances > maxInstances)
+    {
+        numInstances = maxInstances;
+    }
+    return std::make_tuple(response, numInstances);
 }
 
 std::tuple<ResponseList, NumInstances> readAll(const std::string& type,
                                                uint8_t instanceStart)
 {
-    ResponseList empty{};
-    return std::make_tuple(empty, 0);
+    ResponseList response{};
+    sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
+
+    size_t numInstances = 0;
+    auto data = parseConfig();
+    static const std::vector<Json> empty{};
+    std::vector<Json> readings = data.value(type, empty);
+    numInstances = readings.size();
+    for (const auto& j : readings)
+    {
+        try
+        {
+            // Max of 8 response data sets
+            if (response.size() == maxDataSets)
+            {
+                break;
+            }
+
+            uint8_t instanceNum = j.value("instance", 0);
+            // Not in the instance range we're interested in
+            if (instanceNum < instanceStart)
+            {
+                continue;
+            }
+
+            std::string path = j.value("dbus", "");
+            auto service =
+                ipmi::getService(bus,
+                                 "xyz.openbmc_project.Sensor.Value",
+                                 path);
+
+            Response r{};
+            r.instance = instanceNum;
+            uint8_t temp{};
+            bool sign{};
+            std::tie(temp, sign) = readTemp(service, path);
+            r.temperature = temp;
+            r.sign = sign;
+            response.push_back(r);
+        }
+        catch (std::exception& e)
+        {
+            log<level::DEBUG>(e.what());
+            continue;
+        }
+    }
+
+    if (numInstances > maxInstances)
+    {
+        numInstances = maxInstances;
+    }
+    return std::make_tuple(response, numInstances);
 }
 
 } // namsespace temp_readings
diff --git a/dcmihandler.hpp b/dcmihandler.hpp
index a7cfe8a..e23c8da 100644
--- a/dcmihandler.hpp
+++ b/dcmihandler.hpp
@@ -5,6 +5,7 @@
 #include <string>
 #include <vector>
 #include <sdbusplus/bus.hpp>
+#include "nlohmann/json.hpp"
 
 namespace dcmi
 {
@@ -50,6 +51,8 @@
     static constexpr auto maxDataSets = 8;
     static constexpr auto maxInstances = 255;
     static constexpr auto maxTemp = 128; // degrees C
+    static constexpr auto configFile =
+        "/usr/share/ipmi-providers/dcmi_temp_readings.json";
 
     /** @struct Response
      *
@@ -70,6 +73,10 @@
 
     using ResponseList = std::vector<Response>;
     using NumInstances = size_t;
+    using Value = uint8_t;
+    using Sign = bool;
+    using Temperature = std::tuple<Value, Sign>;
+    using Json = nlohmann::json;
 }
 
 static constexpr auto groupExtId = 0xDC;
@@ -375,6 +382,24 @@
 
 namespace temp_readings
 {
+    /** @brief Read temperature from a d-bus object, scale it as per dcmi
+     *         get temperature reading requirements.
+     *
+     *  @param[in] dbusService - the D-Bus service
+     *  @param[in] dbusPath - the D-Bus path
+     *
+     *  @return A temperature reading
+     */
+    Temperature readTemp(const std::string& dbusService,
+                         const std::string& dbusPath);
+
+    /** @brief Parse out JSON config file containing information
+     *         related to temperature readings.
+     *
+     *  @return A json object
+     */
+    Json parseConfig();
+
     /** @brief Read temperatures and fill up DCMI response for the Get
      *         Temperature Readings command. This looks at a specific
      *         instance.