Support temp sensor on MCU which is available from I2C

Chassis MCU exposes register to report temperature for the peripheral board.
The MCU access is configurable as below:
        {
            "Bus": $bus,
            "Address": "0xAA",
            "Reg": "0xBB",
            "Class": "MCUTemp",
            "Name": "MCU Temp",
            "Thresholds": [
                xxx
            ]
            "Type": "MCUTempSensor"
        }

Tested:
The below interface was found under temperature sensor namespace:
          └─/xyz/openbmc_project/sensors/temperature/MCU_Temp

Run ipmitool sensor and check below from list:
MCU Temp     | 30.175      | degrees C  | cr    | na        | 0.000     | 5.000     | 110.000   | 115.000   | na

Change-Id: I8d54455ccc39ea4f60a5b4aee5c68be092d39a72
Signed-off-by: Yuan Li <yuan.li@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8279694..af1a41c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,7 @@
 option (DISABLE_HWMON_TEMP "Disable installing hwmon temp sensor" OFF)
 option (DISABLE_INTRUSION "Disable installing intrusion sensor" OFF)
 option (DISABLE_IPMB "Disable installing IPMB sensor" OFF)
+option (DISABLE_MCUTEMP "Disable installing MCU temperature sensor" OFF)
 option (DISABLE_PSU "Disable installing PSU sensor" OFF)
 
 include ("cmake/HunterGate.cmake")
@@ -42,6 +43,8 @@
 
 set (IPMB_SRC_FILES src/Utils.cpp src/Thresholds.cpp)
 
+set (MCUTEMP_SRC_FILES src/Utils.cpp src/Thresholds.cpp)
+
 set (PSU_SRC_FILES src/Utils.cpp src/PSUSensor.cpp src/Thresholds.cpp
      src/PwmSensor.cpp src/PSUEvent.cpp)
 
@@ -151,6 +154,11 @@
 add_dependencies (ipmbsensor sdbusplus-project)
 target_link_libraries (ipmbsensor ${SENSOR_LINK_LIBS})
 
+add_executable (mcutempsensor src/MCUTempSensor.cpp ${MCUTEMP_SRC_FILES})
+add_dependencies (mcutempsensor sdbusplus-project)
+target_link_libraries (mcutempsensor ${SENSOR_LINK_LIBS})
+target_link_libraries (mcutempsensor i2c)
+
 add_executable (psusensor src/PSUSensorMain.cpp ${PSU_SRC_FILES})
 add_dependencies (psusensor sdbusplus-project)
 target_link_libraries (psusensor ${SENSOR_LINK_LIBS})
@@ -163,6 +171,7 @@
     add_dependencies (hwmontempsensor ${EXTERNAL_PACKAGES})
     add_dependencies (intrusionsensor ${EXTERNAL_PACKAGES})
     add_dependencies (ipmbsensor ${EXTERNAL_PACKAGES})
+    add_dependencies (mcutempsensor ${EXTERNAL_PACKAGES})
     add_dependencies (psusensor ${EXTERNAL_PACKAGES})
 endif ()
 
@@ -223,6 +232,13 @@
                  DESTINATION ${SERVICE_FILE_INSTALL_DIR})
 endif ()
 
+if (NOT DISABLE_MCUTEMP)
+    install (TARGETS mcutempsensor DESTINATION bin)
+    install (FILES
+                 ${SERVICE_FILE_SRC_DIR}/xyz.openbmc_project.mcutempsensor.service
+                 DESTINATION ${SERVICE_FILE_INSTALL_DIR})
+endif ()
+
 if (NOT DISABLE_PSU)
     install (TARGETS psusensor DESTINATION bin)
     install (FILES
diff --git a/include/MCUTempSensor.hpp b/include/MCUTempSensor.hpp
new file mode 100644
index 0000000..5dd3c03
--- /dev/null
+++ b/include/MCUTempSensor.hpp
@@ -0,0 +1,33 @@
+#pragma once
+#include "sensor.hpp"
+
+#include <boost/asio/deadline_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <chrono>
+#include <limits>
+#include <vector>
+
+struct MCUTempSensor : public Sensor
+{
+    MCUTempSensor(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 busId, uint8_t mcuAddress, uint8_t tempReg);
+    ~MCUTempSensor();
+
+    void checkThresholds(void) override;
+    void read(void);
+    void init(void);
+
+    uint8_t busId;
+    uint8_t mcuAddress;
+    uint8_t tempReg;
+
+  private:
+    int getMCURegsInfoWord(uint8_t regs, int16_t* pu16data);
+    sdbusplus::asio::object_server& objectServer;
+    std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
+    boost::asio::deadline_timer waitTimer;
+};
diff --git a/service_files/xyz.openbmc_project.mcutempsensor.service b/service_files/xyz.openbmc_project.mcutempsensor.service
new file mode 100644
index 0000000..33a98f2
--- /dev/null
+++ b/service_files/xyz.openbmc_project.mcutempsensor.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=MCU Temp Sensor
+StopWhenUnneeded=false
+
+[Service]
+Restart=always
+RestartSec=5
+ExecStart=/usr/bin/mcutempsensor
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/src/MCUTempSensor.cpp b/src/MCUTempSensor.cpp
new file mode 100644
index 0000000..fd45327
--- /dev/null
+++ b/src/MCUTempSensor.cpp
@@ -0,0 +1,312 @@
+/*
+// 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 "MCUTempSensor.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>
+
+extern "C" {
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+}
+
+constexpr const bool debug = false;
+
+constexpr const char* configInterface =
+    "xyz.openbmc_project.Configuration.MCUTempSensor";
+static constexpr double mcuTempMaxReading = 0xFF;
+static constexpr double mcuTempMinReading = 0;
+
+boost::container::flat_map<std::string, std::unique_ptr<MCUTempSensor>> sensors;
+
+MCUTempSensor::MCUTempSensor(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 busId, uint8_t mcuAddress,
+                             uint8_t tempReg) :
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"),
+           std::move(thresholdData), sensorConfiguration,
+           "xyz.openbmc_project.Configuration.ExitAirTemp", mcuTempMaxReading,
+           mcuTempMinReading),
+    objectServer(objectServer), dbusConnection(conn), waitTimer(io),
+    busId(busId), mcuAddress(mcuAddress), tempReg(tempReg)
+{
+    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");
+    }
+    association = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/temperature/" + name,
+        "org.openbmc.Associations");
+}
+
+MCUTempSensor::~MCUTempSensor()
+{
+    waitTimer.cancel();
+    objectServer.remove_interface(thresholdInterfaceWarning);
+    objectServer.remove_interface(thresholdInterfaceCritical);
+    objectServer.remove_interface(sensorInterface);
+    objectServer.remove_interface(association);
+}
+
+void MCUTempSensor::init(void)
+{
+    setInitialProperties(dbusConnection);
+    read();
+}
+
+void MCUTempSensor::checkThresholds(void)
+{
+    thresholds::checkThresholds(this);
+}
+
+int MCUTempSensor::getMCURegsInfoWord(uint8_t regs, int16_t* pu16data)
+{
+    std::string i2cBus = "/dev/i2c-" + std::to_string(busId);
+    int fd = open(i2cBus.c_str(), O_RDWR);
+    size_t i = 0;
+
+    if (fd < 0)
+    {
+        std::cerr << " unable to open i2c device" << i2cBus << "  err=" << fd
+                  << "\n";
+        return -1;
+    }
+
+    if (ioctl(fd, I2C_SLAVE_FORCE, mcuAddress) < 0)
+    {
+        std::cerr << " unable to set device address\n";
+        close(fd);
+        return -1;
+    }
+
+    unsigned long funcs = 0;
+    if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
+    {
+        std::cerr << " not support I2C_FUNCS\n";
+        close(fd);
+        return -1;
+    }
+
+    if (!(funcs & I2C_FUNC_SMBUS_READ_WORD_DATA))
+    {
+        std::cerr << " not support I2C_FUNC_SMBUS_READ_WORD_DATA\n";
+        close(fd);
+        return -1;
+    }
+
+    *pu16data = i2c_smbus_read_word_data(fd, regs);
+    close(fd);
+
+    if (*pu16data < 0)
+    {
+        std::cerr << " read word data failed at " << static_cast<int>(regs)
+                  << "\n";
+        return -1;
+    }
+
+    return 0;
+}
+
+void MCUTempSensor::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 cancelled
+        }
+        // read timer error
+        else if (ec)
+        {
+            std::cerr << "timer error\n";
+            return;
+        }
+        int16_t temp;
+        int ret = getMCURegsInfoWord(tempReg, &temp);
+        if (ret >= 0)
+        {
+            double v = static_cast<double>(temp) / 1000;
+            if constexpr (debug)
+            {
+                std::cerr << "Value update to " << (double)v << "raw reading "
+                          << static_cast<int>(temp) << "\n";
+            }
+            updateValue(v);
+        }
+        else
+        {
+            std::cerr << "Invalid read getMCURegsInfoWord\n";
+            updateValue(-1);
+        }
+        read();
+    });
+}
+
+void createSensors(
+    boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
+    boost::container::flat_map<std::string, std::unique_ptr<MCUTempSensor>>&
+        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";
+                    }
+
+                    uint8_t busId = loadVariant<uint8_t>(entry.second, "Bus");
+
+                    uint8_t mcuAddress =
+                        loadVariant<uint8_t>(entry.second, "Address");
+
+                    uint8_t tempReg = loadVariant<uint8_t>(entry.second, "Reg");
+
+                    std::string sensorClass =
+                        loadVariant<std::string>(entry.second, "Class");
+
+                    if constexpr (debug)
+                    {
+                        std::cerr
+                            << "Configuration parsed for \n\t" << entry.first
+                            << "\n"
+                            << "with\n"
+                            << "\tName: " << name << "\n"
+                            << "\tBus: " << static_cast<int>(busId) << "\n"
+                            << "\tAddress: " << static_cast<int>(mcuAddress)
+                            << "\n"
+                            << "\tReg: " << static_cast<int>(tempReg) << "\n"
+                            << "\tClass: " << sensorClass << "\n";
+                    }
+
+                    auto& sensor = sensors[name];
+
+                    sensor = std::make_unique<MCUTempSensor>(
+                        dbusConnection, io, name, pathPair.first, objectServer,
+                        std::move(sensorThresholds), busId, mcuAddress,
+                        tempReg);
+
+                    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.MCUTempSensor");
+    sdbusplus::asio::object_server objectServer(systemBus);
+
+    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
+                }
+                // config timer error
+                else 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();
+}