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/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();
+}