Add Ipmb Sensor

Add daemon responsible for polling ipmb for sensor
readings. Currently base ME and VR bridge sensors
are supported. This daemon uses ipmbd to get sensor
readings.

Tested-by: Saw correct readings on d-bus and verified
sensor list.

Change-Id: I71c216ae57567470d42180dce76aba8f69ecb50e
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a31dda5..ba3eecb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,8 @@
 
 set (EXIT_AIR_SRC_FILES src/Utils.cpp src/Thresholds.cpp)
 
+set (IPMB_SRC_FILES src/Utils.cpp src/Thresholds.cpp)
+
 set (EXTERNAL_PACKAGES Boost sdbusplus-project nlohmann-json)
 set (SENSOR_LINK_LIBS -lsystemd stdc++fs sdbusplus)
 
@@ -126,12 +128,18 @@
 add_dependencies (exitairtempsensor sdbusplus-project)
 target_link_libraries (exitairtempsensor ${SENSOR_LINK_LIBS})
 
+add_executable (ipmbsensor src/IpmbSensor.cpp
+                ${IPMB_SRC_FILES})
+add_dependencies (ipmbsensor sdbusplus)
+target_link_libraries (ipmbsensor ${SENSOR_LINK_LIBS})
+
 if (NOT YOCTO)
     add_dependencies (fansensor ${EXTERNAL_PACKAGES})
     add_dependencies (hwmontempsensor ${EXTERNAL_PACKAGES})
     add_dependencies (adcsensor ${EXTERNAL_PACKAGES})
     add_dependencies (cpusensor ${EXTERNAL_PACKAGES})
     add_dependencies (exitairtempsensor ${EXTERNAL_PACKAGES})
+    add_dependencies (ipmbsensor ${EXTERNAL_PACKAGES})
 endif ()
 
 set (
@@ -143,6 +151,6 @@
     ${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.exitairsensor.service
 )
 
-install (TARGETS fansensor hwmontempsensor cpusensor adcsensor
+install (TARGETS fansensor hwmontempsensor cpusensor adcsensor ipmbsensor
                  exitairtempsensor DESTINATION sbin)
 install (FILES ${SERVICE_FILES} DESTINATION /lib/systemd/system/)
diff --git a/include/ADCSensor.hpp b/include/ADCSensor.hpp
index 2b5cdd4..2397093 100644
--- a/include/ADCSensor.hpp
+++ b/include/ADCSensor.hpp
@@ -4,11 +4,6 @@
 #include <sdbusplus/asio/object_server.hpp>
 #include <sensor.hpp>
 
-enum class PowerState : bool
-{
-    on,
-    always
-};
 class ADCSensor : public Sensor
 {
   public:
diff --git a/include/IpmbSensor.hpp b/include/IpmbSensor.hpp
new file mode 100644
index 0000000..d5b0290
--- /dev/null
+++ b/include/IpmbSensor.hpp
@@ -0,0 +1,49 @@
+#pragma once
+#include "sensor.hpp"
+
+#include <boost/asio/deadline_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <chrono>
+#include <limits>
+#include <vector>
+
+enum class IpmbType
+{
+    meSensor,
+    PXE1410CVR,
+    IR38363VR,
+    mpsVR
+};
+
+struct IpmbSensor : public Sensor
+{
+    IpmbSensor(std::shared_ptr<sdbusplus::asio::connection> &conn,
+               boost::asio::io_service &io, const std::string &name,
+               const std::string &sensorConfiguration,
+               sdbusplus::asio::object_server &objectServer,
+               std::vector<thresholds::Threshold> &&thresholds,
+               uint8_t deviceAddress);
+    ~IpmbSensor();
+
+    void checkThresholds(void) override;
+    void read(void);
+    void init(void);
+    void loadDefaults(void);
+
+    IpmbType type;
+    uint8_t commandAddress;
+    uint8_t netfn;
+    uint8_t command;
+    uint8_t deviceAddress;
+    std::vector<uint8_t> commandData;
+    std::optional<uint8_t> initCommand;
+    std::vector<uint8_t> initData;
+
+    // to date all ipmb sensors are power on only
+    PowerState readState = PowerState::on;
+
+  private:
+    sdbusplus::asio::object_server &objectServer;
+    std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
+    boost::asio::deadline_timer waitTimer;
+};
\ No newline at end of file
diff --git a/include/Utils.hpp b/include/Utils.hpp
index 006c2bc..598662a 100644
--- a/include/Utils.hpp
+++ b/include/Utils.hpp
@@ -45,6 +45,12 @@
 void findLimits(std::pair<double, double>& limits,
                 const SensorBaseConfiguration* data);
 
+enum class PowerState : bool
+{
+    on,
+    always
+};
+
 template <typename T>
 inline T loadVariant(
     const boost::container::flat_map<std::string, BasicVariantType>& data,
@@ -61,6 +67,11 @@
         return sdbusplus::message::variant_ns::visit(VariantToDoubleVisitor(),
                                                      it->second);
     }
+    else if constexpr (std::is_unsigned_v<T>)
+    {
+        return sdbusplus::message::variant_ns::visit(
+            VariantToUnsignedIntVisitor(), it->second);
+    }
     else if constexpr (std::is_same_v<T, std::string>)
     {
         return sdbusplus::message::variant_ns::visit(VariantToStringVisitor(),
diff --git a/src/IpmbSensor.cpp b/src/IpmbSensor.cpp
new file mode 100644
index 0000000..71d4a5b
--- /dev/null
+++ b/src/IpmbSensor.cpp
@@ -0,0 +1,377 @@
+/*
+// 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 "IpmbSensor.hpp"
+
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
+#include <math.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <chrono>
+#include <iostream>
+#include <limits>
+#include <numeric>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <vector>
+
+constexpr const bool debug = false;
+
+constexpr const char* configInterface =
+    "xyz.openbmc_project.Configuration.IpmbSensor";
+static constexpr double ipmbMaxReading = 0xFF;
+static constexpr double ipmbMinReading = 0;
+
+static constexpr uint8_t meAddress = 1;
+static constexpr uint8_t lun = 0;
+
+using IpmbMethodType =
+    std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>;
+
+IpmbSensor::IpmbSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
+                       boost::asio::io_service& io,
+                       const std::string& sensorName,
+                       const std::string& sensorConfiguration,
+                       sdbusplus::asio::object_server& objectServer,
+                       std::vector<thresholds::Threshold>&& thresholdData,
+                       uint8_t deviceAddress) :
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"),
+           "" /* todo: remove arg from base*/, std::move(thresholdData),
+           sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
+           ipmbMaxReading, ipmbMinReading),
+    objectServer(objectServer), dbusConnection(conn), waitTimer(io),
+    deviceAddress(deviceAddress)
+{
+    sensorInterface = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/temperature/" + name,
+        "xyz.openbmc_project.Sensor.Value");
+
+    if (thresholds::hasWarningInterface(thresholds))
+    {
+        thresholdInterfaceWarning = objectServer.add_interface(
+            "/xyz/openbmc_project/sensors/temperature/" + name,
+            "xyz.openbmc_project.Sensor.Threshold.Warning");
+    }
+    if (thresholds::hasCriticalInterface(thresholds))
+    {
+        thresholdInterfaceCritical = objectServer.add_interface(
+            "/xyz/openbmc_project/sensors/temperature/" + name,
+            "xyz.openbmc_project.Sensor.Threshold.Critical");
+    }
+    setupPowerMatch(conn);
+}
+
+IpmbSensor::~IpmbSensor()
+{
+    waitTimer.cancel();
+    objectServer.remove_interface(thresholdInterfaceWarning);
+    objectServer.remove_interface(thresholdInterfaceCritical);
+    objectServer.remove_interface(sensorInterface);
+}
+
+void IpmbSensor::init(void)
+{
+    setInitialProperties(dbusConnection);
+    loadDefaults();
+    if (initCommand)
+    {
+        dbusConnection->async_method_call(
+            [this](boost::system::error_code ec,
+                   const IpmbMethodType& response) {
+                const int& status = std::get<0>(response);
+
+                if (ec || status)
+                {
+                    std::cerr
+                        << "Error setting init command for device: " << name
+                        << "\n";
+                }
+                read();
+            },
+            "xyz.openbmc_project.Ipmi.Channel.Ipmb",
+            "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
+            "sendRequest", commandAddress, netfn, lun, *initCommand, initData);
+    }
+    else
+    {
+        read();
+    }
+}
+
+void IpmbSensor::loadDefaults()
+{
+    if (type == IpmbType::meSensor)
+    {
+        commandAddress = meAddress;
+        netfn = 0x4;    // sensor
+        command = 0x2d; // get sensor reading
+        commandData = {deviceAddress};
+    }
+    else if (type == IpmbType::PXE1410CVR)
+    {
+        commandAddress = meAddress;
+        netfn = 0x2e;       // me bridge
+        command = 0xd9;     // send raw pmbus
+        initCommand = 0xd9; // send raw pmbus
+        commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
+                       0x00, 0x00, 0x00, 0x01, 0x02, 0x29};
+        initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
+                    0x00, 0x00, 0x00, 0x02, 0x00, 0x00,          0x60};
+    }
+    else if (type == IpmbType::IR38363VR)
+    {
+        commandAddress = meAddress;
+        netfn = 0x2e;   // me bridge
+        command = 0xd9; // send raw pmbus
+        commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
+                       0x00, 0x00, 0x00, 0x01, 0x02, 0x8D};
+    }
+    else if (type == IpmbType::mpsVR)
+    {
+        commandAddress = meAddress;
+        netfn = 0x2e;       // me bridge
+        command = 0xd9;     // send raw pmbus
+        initCommand = 0xd9; // send raw pmbus
+        commandData = {0x57, 0x01, 0x00, 0x16, 0x3,  deviceAddress, 0x00,
+                       0x00, 0x00, 0x00, 0x01, 0x02, 0x8d};
+        initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
+                    0x00, 0x00, 0x00, 0x02, 0x00, 0x00,          0x00};
+    }
+    else
+    {
+        throw std::runtime_error("Invalid sensor type");
+    }
+}
+
+void IpmbSensor::checkThresholds(void)
+{
+    if (readState == PowerState::on && !isPowerOn())
+    {
+        return;
+    }
+    thresholds::checkThresholds(this);
+}
+
+void IpmbSensor::read(void)
+{
+    static constexpr size_t pollTime = 1; // in seconds
+
+    waitTimer.expires_from_now(boost::posix_time::seconds(pollTime));
+    waitTimer.async_wait([this](const boost::system::error_code& ec) {
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            return; // we're being canceled
+        }
+        if (!isPowerOn() && readState == PowerState::on)
+        {
+            updateValue(0);
+            read();
+            return;
+        }
+        dbusConnection->async_method_call(
+            [this](boost::system::error_code ec,
+                   const IpmbMethodType& response) {
+                const int& status = std::get<0>(response);
+                if (ec || status)
+                {
+                    std::cerr << "Error reading from device: " << name << "\n";
+                    updateValue(0);
+                    read();
+                    return;
+                }
+                if (!isPowerOn() && readState == PowerState::on)
+                {
+                    updateValue(0);
+                    read();
+                    return;
+                }
+                const std::vector<uint8_t>& data = std::get<5>(response);
+                if constexpr (debug)
+                {
+                    std::cout << name << ": ";
+                    for (size_t d : data)
+                    {
+                        std::cout << d << " ";
+                    }
+                    std::cout << "\n";
+                }
+                uint16_t value = 0;
+                if (type == IpmbType::meSensor)
+                {
+                    if (data.empty())
+                    {
+                        std::cerr << "Invalid data from device: " << name
+                                  << "\n";
+                        read();
+                        return;
+                    }
+                    value = data[0];
+                }
+                else if (type == IpmbType::PXE1410CVR ||
+                         type == IpmbType::IR38363VR)
+                {
+                    if (data.size() < 4)
+                    {
+                        std::cerr << "Invalid data from device: " << name
+                                  << "\n";
+                        read();
+                        return;
+                    }
+                    // format based on the 11 bit linear data format
+                    value = ((data[4] << 8) | data[3]) >> 3;
+                }
+                else if (type == IpmbType::mpsVR)
+                {
+                    if (data.size() < 4)
+                    {
+                        std::cerr << "Invalid data from device: " << name
+                                  << "\n";
+                        read();
+                        return;
+                    }
+                    value = data[3];
+                }
+                else
+                {
+                    throw std::runtime_error("Invalid sensor type");
+                }
+                updateValue(value);
+                read();
+            },
+            "xyz.openbmc_project.Ipmi.Channel.Ipmb",
+            "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
+            "sendRequest", commandAddress, netfn, lun, command, commandData);
+    });
+}
+void createSensors(
+    boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
+    boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>>&
+        sensors,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+    if (!dbusConnection)
+    {
+        std::cerr << "Connection not created\n";
+        return;
+    }
+    dbusConnection->async_method_call(
+        [&](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";
+                    }
+                    uint8_t deviceAddress =
+                        loadVariant<uint8_t>(entry.second, "Address");
+
+                    std::string sensorClass =
+                        loadVariant<std::string>(entry.second, "Class");
+                    auto& sensor = sensors[name];
+                    sensor = std::make_unique<IpmbSensor>(
+                        dbusConnection, io, name, pathPair.first, objectServer,
+                        std::move(sensorThresholds), deviceAddress);
+
+                    if (sensorClass == "PxeBridgeTemp")
+                    {
+                        sensor->type = IpmbType::PXE1410CVR;
+                    }
+                    else if (sensorClass == "IRBridgeTemp")
+                    {
+                        sensor->type = IpmbType::IR38363VR;
+                    }
+                    else if (sensorClass == "MpsBridgeTemp")
+                    {
+                        sensor->type = IpmbType::mpsVR;
+                    }
+                    else if (sensorClass == "METemp")
+                    {
+                        sensor->type = IpmbType::meSensor;
+                    }
+                    else
+                    {
+                        std::cerr << "Invalid class " << sensorClass << "\n";
+                        continue;
+                    }
+                    sensor->init();
+                }
+            }
+        },
+        entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
+        "GetManagedObjects");
+}
+
+int main(int argc, char** argv)
+{
+
+    boost::asio::io_service io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+    systemBus->request_name("xyz.openbmc_project.IpmbSensor");
+    sdbusplus::asio::object_server objectServer(systemBus);
+    boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>>
+        sensors;
+
+    io.post([&]() { createSensors(io, objectServer, sensors, systemBus); });
+
+    boost::asio::deadline_timer configTimer(io);
+
+    std::function<void(sdbusplus::message::message&)> eventHandler =
+        [&](sdbusplus::message::message& message) {
+            configTimer.expires_from_now(boost::posix_time::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
+                }
+                createSensors(io, objectServer, sensors, systemBus);
+                if (sensors.empty())
+                {
+                    std::cout << "Configuration not detected\n";
+                }
+            });
+        };
+
+    sdbusplus::bus::match::match match(
+        static_cast<sdbusplus::bus::bus&>(*systemBus),
+        "type='signal',member='PropertiesChanged',path_namespace='" +
+            std::string(inventoryPath) + "',arg0namespace='" + configInterface +
+            "'",
+        eventHandler);
+
+    io.run();
+}
diff --git a/src/TachSensor.cpp b/src/TachSensor.cpp
index 132ca69..d16c36b 100644
--- a/src/TachSensor.cpp
+++ b/src/TachSensor.cpp
@@ -64,7 +64,7 @@
             "xyz.openbmc_project.Sensor.Threshold.Critical");
     }
     setInitialProperties(conn);
-    setupPowerMatch(conn); // first call initializes
+    setupPowerMatch(conn);
     setupRead();
 }
 
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 1a4210d..52f5d1d 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -29,8 +29,8 @@
 const static constexpr char* powerObjectName =
     "/xyz/openbmc_project/Chassis/Control/Power0";
 
-bool powerStatusOn = false;
-std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
+static bool powerStatusOn = false;
+static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
 
 bool getSensorConfiguration(
     const std::string& type,