Add chassis intrusion sensor daemon

Chassis intrusion signal is read from PCH via I2C or from GPIO.
Create a new daemon to poll its status and expose to dbus.

Related patches to run test:
- meta-phosphor: dbus-sensors: Enable new service of intrusion sensor
https://gerrit.openbmc-project.xyz/#/c/openbmc/meta-phosphor/+/17063/
- redfish: chassis: add property of physical security
https://gerrit.openbmc-project.xyz/#/c/openbmc/bmcweb/+/17691/

Tested-by: Check intrusion status value by redfish,
verified PCH solution on WFP and GPIO solution on STP.

Change-Id: Id5e67abbd80bbf2ef502db49fa183d92d0d31bda
Signed-off-by: Qiang XU <qiang.xu@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7201ac0..498fcca 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,8 @@
 
 set (IPMB_SRC_FILES src/Utils.cpp src/Thresholds.cpp)
 
+set (INTRUSION_SRC_FILES src/Utils.cpp src/ChassisIntrusionSensor.cpp)
+
 set (EXTERNAL_PACKAGES Boost sdbusplus-project nlohmann-json)
 set (SENSOR_LINK_LIBS -lsystemd stdc++fs sdbusplus)
 
@@ -133,6 +135,12 @@
 add_dependencies (ipmbsensor sdbusplus)
 target_link_libraries (ipmbsensor ${SENSOR_LINK_LIBS})
 
+add_executable (intrusionsensor src/IntrusionSensorMain.cpp
+                ${INTRUSION_SRC_FILES})
+add_dependencies (intrusionsensor sdbusplus-project)
+target_link_libraries (intrusionsensor ${SENSOR_LINK_LIBS})
+target_link_libraries (intrusionsensor i2c)
+
 if (NOT YOCTO)
     add_dependencies (fansensor ${EXTERNAL_PACKAGES})
     add_dependencies (hwmontempsensor ${EXTERNAL_PACKAGES})
@@ -140,6 +148,7 @@
     add_dependencies (cpusensor ${EXTERNAL_PACKAGES})
     add_dependencies (exitairtempsensor ${EXTERNAL_PACKAGES})
     add_dependencies (ipmbsensor ${EXTERNAL_PACKAGES})
+    add_dependencies (intrusionsensor ${EXTERNAL_PACKAGES})
 endif ()
 
 set (
@@ -150,8 +159,9 @@
     ${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.hwmontempsensor.service
     ${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.exitairsensor.service
     ${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.ipmbsensor.service
+    ${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.intrusionsensor.service
 )
 
 install (TARGETS fansensor hwmontempsensor cpusensor adcsensor ipmbsensor
-                 exitairtempsensor DESTINATION sbin)
+                 exitairtempsensor intrusionsensor DESTINATION sbin)
 install (FILES ${SERVICE_FILES} DESTINATION /lib/systemd/system/)
diff --git a/include/ChassisIntrusionSensor.hpp b/include/ChassisIntrusionSensor.hpp
new file mode 100644
index 0000000..fe04fc9
--- /dev/null
+++ b/include/ChassisIntrusionSensor.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <sdbusplus/asio/object_server.hpp>
+
+enum IntrusionSensorType
+{
+    pch,
+    gpio
+};
+
+class ChassisIntrusionSensor
+{
+  public:
+    ChassisIntrusionSensor(
+        boost::asio::io_service& io,
+        std::shared_ptr<sdbusplus::asio::dbus_interface> iface);
+
+    ~ChassisIntrusionSensor();
+
+    void start(IntrusionSensorType type, int busId, int slaveAddr,
+               int gpioIndex, bool gpioInverted);
+
+  private:
+    std::shared_ptr<sdbusplus::asio::dbus_interface> mIface;
+    std::shared_ptr<sdbusplus::asio::connection> mDbusConn;
+
+    IntrusionSensorType mType;
+
+    // intrusion status. 0: not intruded, 1: intruded
+    std::string mValue = "unknown";
+    std::string mOldValue = "unknown";
+
+    // valid if it is PCH register via i2c
+    int mBusId;
+    int mSlaveAddr;
+    boost::asio::deadline_timer mPollTimer;
+
+    // valid if it is via GPIO
+    int mGpioIndex;
+    bool mGpioInverted;
+    boost::asio::ip::tcp::socket mInputDev;
+    int mFd;
+
+    // common members
+    std::string mOverriddenValue = "unknown";
+    bool mOverridenState = false;
+    bool mInternalSet = false;
+
+    bool mInitialized = false;
+
+    void updateValue(const std::string newValue);
+    int i2cReadFromPch(int busId, int slaveAddr);
+    void pollSensorStatusByPch();
+    void readGpio();
+    void pollSensorStatusByGpio();
+    void initGpioDeviceFile(const int index);
+    int setSensorValue(const std::string& req, std::string& propertyValue);
+};
diff --git a/service_files/xyz.openbmc_project.intrusionsensor.service b/service_files/xyz.openbmc_project.intrusionsensor.service
new file mode 100644
index 0000000..5b407e4
--- /dev/null
+++ b/service_files/xyz.openbmc_project.intrusionsensor.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Intrusion Sensor
+StopWhenUnneeded=false
+
+[Service]
+Restart=always
+RestartSec=5
+ExecStart=/usr/bin/env intrusionsensor
+SyslogIdentifier=intrusion-sensor
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/ChassisIntrusionSensor.cpp b/src/ChassisIntrusionSensor.cpp
new file mode 100644
index 0000000..22c03ab
--- /dev/null
+++ b/src/ChassisIntrusionSensor.cpp
@@ -0,0 +1,373 @@
+/*
+// Copyright (c) 2018 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 <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <ChassisIntrusionSensor.hpp>
+#include <boost/asio.hpp>
+#include <boost/bind.hpp>
+#include <chrono>
+#include <iostream>
+#include <sdbusplus/asio/object_server.hpp>
+#include <string>
+#include <thread>
+
+extern "C" {
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+}
+
+static constexpr bool DEBUG = false;
+
+static constexpr unsigned int intrusionSensorPollSec = 1;
+
+// SMLink Status Register
+const static constexpr size_t pchStatusRegIntrusion = 0x04;
+
+// Status bit field masks
+const static constexpr size_t pchRegMaskIntrusion = 0x01;
+
+// Hold current PCH register values
+static unsigned int intrudeValue;
+
+// gpio sysfs path
+constexpr const char* gpioPath = "/sys/class/gpio/";
+
+void ChassisIntrusionSensor::updateValue(const std::string newValue)
+{
+    // indicate that it is internal set call
+    mInternalSet = true;
+    mIface->set_property("Status", newValue);
+
+    mValue = newValue;
+
+    if (mOldValue == "Normal" && mValue != "Normal")
+    {
+        std::cerr << "save to SEL for intrusion assert event \n";
+        // TODO: call add SEL log API, depends on patch #13956
+        mOldValue = mValue;
+    }
+    else if (mOldValue != "Normal" && mValue == "Normal")
+    {
+        std::cerr << "save to SEL for intrusion de-assert event \n";
+        // TODO: call add SEL log API, depends on patch #13956
+        mOldValue = mValue;
+    }
+}
+
+int ChassisIntrusionSensor::i2cReadFromPch(int busId, int slaveAddr)
+{
+    std::string i2cBus = "/dev/i2c-" + std::to_string(busId);
+
+    int fd = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
+    if (fd < 0)
+    {
+        std::cerr << "unable to open i2c device \n";
+        return -1;
+    }
+    if (ioctl(fd, I2C_SLAVE_FORCE, slaveAddr) < 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_BYTE_DATA))
+    {
+        std::cerr << "not support I2C_FUNC_SMBUS_READ_BYTE_DATA \n";
+        close(fd);
+        return -1;
+    }
+
+    unsigned int statusValue;
+    unsigned int statusMask = pchRegMaskIntrusion;
+    unsigned int statusReg = pchStatusRegIntrusion;
+
+    statusValue = i2c_smbus_read_byte_data(fd, statusReg);
+    if (DEBUG)
+    {
+        std::cout << "\nRead bus " << busId << " addr " << slaveAddr
+                  << ", value = " << statusValue << "\n";
+    }
+
+    close(fd);
+
+    if (statusValue < 0)
+    {
+        std::cerr << "i2c_smbus_read_byte_data failed \n";
+        return -1;
+    }
+
+    // Get status value with mask
+    int newValue = statusValue & statusMask;
+
+    if (DEBUG)
+    {
+        std::cout << "statusValue is " << statusValue << "\n";
+        std::cout << "Intrusion sensor value is " << newValue << "\n";
+    }
+
+    return newValue;
+}
+
+void ChassisIntrusionSensor::pollSensorStatusByPch()
+{
+    // setting a new experation implicitly cancels any pending async wait
+    mPollTimer.expires_from_now(
+        boost::posix_time::seconds(intrusionSensorPollSec));
+
+    mPollTimer.async_wait([&](const boost::system::error_code& ec) {
+        // case of timer expired
+        if (!ec)
+        {
+            int statusValue = i2cReadFromPch(mBusId, mSlaveAddr);
+            std::string newValue = statusValue ? "HardwareIntrusion" : "Normal";
+
+            // save value
+            if (mOverridenState)
+            {
+                newValue = mOverriddenValue;
+            }
+
+            if (newValue != "unknown" && mValue != newValue)
+            {
+                std::cout << "update value from " << mValue << " to "
+                          << newValue << "\n";
+                updateValue(newValue);
+            }
+
+            // trigger next polling
+            pollSensorStatusByPch();
+        }
+        // case of being canceled
+        else if (ec == boost::asio::error::operation_aborted)
+        {
+            std::cerr << "Timer of intrusion sensor is cancelled. Return \n";
+            return;
+        }
+    });
+}
+
+void ChassisIntrusionSensor::readGpio()
+{
+    constexpr size_t readSize = sizeof("0");
+    std::string readBuf;
+    readBuf.resize(readSize);
+    lseek(mFd, 0, SEEK_SET);
+    size_t r = ::read(mFd, readBuf.data(), readSize);
+    if (r != readSize)
+    {
+        std::cerr << "Error reading gpio\n";
+    }
+    else
+    {
+        bool value = std::stoi(readBuf);
+        if (mGpioInverted)
+        {
+            value = !value;
+        }
+
+        // set string defined in chassis redfish schema
+        std::string newValue = value ? "HardwareIntrusion" : "Normal";
+
+        if (DEBUG)
+        {
+            std::cout << "\nGPIO value is " << value << "\n";
+            std::cout << "Intrusion sensor value is " << newValue << "\n";
+        }
+
+        // save value
+        if (mOverridenState)
+        {
+            newValue = mOverriddenValue;
+        }
+
+        if (newValue != "unknown" && mValue != newValue)
+        {
+            std::cout << "update value from " << mValue << " to " << newValue
+                      << "\n";
+            updateValue(newValue);
+        }
+    }
+}
+
+void ChassisIntrusionSensor::pollSensorStatusByGpio(void)
+{
+    mInputDev.async_wait(
+        boost::asio::ip::tcp::socket::wait_error,
+        [this](const boost::system::error_code& ec) {
+            if (ec == boost::system::errc::bad_file_descriptor)
+            {
+                return; // we're being destroyed
+            }
+            else if (ec)
+            {
+                std::cerr << "Error on GPIO based intrusion sensor socket\n";
+            }
+            else
+            {
+                readGpio();
+            }
+            pollSensorStatusByGpio();
+        });
+}
+
+void ChassisIntrusionSensor::initGpioDeviceFile(const int index)
+{
+    std::string device = gpioPath + std::string("gpio") + std::to_string(index);
+    mFd = open((device + "/value").c_str(), O_RDONLY);
+    if (mFd < 0)
+    {
+        std::cerr << "Error opening gpio " << index << "\n";
+        return;
+    }
+    mInputDev.assign(boost::asio::ip::tcp::v4(), mFd);
+}
+
+int ChassisIntrusionSensor::setSensorValue(const std::string& req,
+                                           std::string& propertyValue)
+{
+    if (mInternalSet)
+    {
+        mInternalSet = false;
+        propertyValue = req;
+    }
+    else
+    {
+        mOverriddenValue = req;
+        mOverridenState = true;
+    }
+    return 1;
+}
+
+void ChassisIntrusionSensor::start(IntrusionSensorType type, int busId,
+                                   int slaveAddr, int gpioIndex,
+                                   bool gpioInverted)
+{
+    if (DEBUG)
+    {
+        std::cerr << "enter ChassisIntrusionSensor::start, type = " << type
+                  << "\n";
+        if (type == IntrusionSensorType::pch)
+        {
+            std::cerr << "busId = " << busId << ", slaveAddr = " << slaveAddr
+                      << "\n";
+        }
+        else if (type == IntrusionSensorType::gpio)
+        {
+            std::cerr << "gpioIndex = " << gpioIndex
+                      << ", gpioInverted = " << gpioInverted << "\n";
+        }
+    }
+
+    if ((type == IntrusionSensorType::pch && busId == mBusId &&
+         slaveAddr == mSlaveAddr) ||
+        (type == IntrusionSensorType::gpio && gpioIndex == mGpioIndex &&
+         gpioInverted == mGpioInverted))
+    {
+        return;
+    }
+
+    mType = type;
+    mBusId = busId;
+    mSlaveAddr = slaveAddr;
+    mGpioIndex = gpioIndex;
+    mGpioInverted = gpioInverted;
+
+    if ((mType == IntrusionSensorType::pch && mBusId > 0 && mSlaveAddr > 0) ||
+        (mType == IntrusionSensorType::gpio && mGpioIndex > 0))
+    {
+        // initialize first if not initialized before
+        if (!mInitialized)
+        {
+            mIface->register_property(
+                "Status", mValue,
+                [&](const std::string& req, std::string& propertyValue) {
+                    return setSensorValue(req, propertyValue);
+                });
+            mIface->initialize();
+
+            if (mType == IntrusionSensorType::gpio)
+            {
+                initGpioDeviceFile(mGpioIndex);
+            }
+
+            mInitialized = true;
+        }
+
+        // start polling value
+        if (mType == IntrusionSensorType::pch)
+        {
+            pollSensorStatusByPch();
+        }
+        else if (mType == IntrusionSensorType::gpio && mFd > 0)
+        {
+            pollSensorStatusByGpio();
+        }
+    }
+
+    // invalid para, release resource
+    else
+    {
+        if (mInitialized)
+        {
+            if (mType == IntrusionSensorType::pch)
+            {
+                mPollTimer.cancel();
+            }
+            else if (mType == IntrusionSensorType::gpio)
+            {
+                mInputDev.close();
+                close(mFd);
+            }
+            mInitialized = false;
+        }
+    }
+}
+
+ChassisIntrusionSensor::ChassisIntrusionSensor(
+    boost::asio::io_service& io,
+    std::shared_ptr<sdbusplus::asio::dbus_interface> iface) :
+    mPollTimer(io),
+    mIface(iface), mInputDev(io), mType(IntrusionSensorType::gpio), mBusId(-1),
+    mSlaveAddr(-1), mGpioIndex(-1), mGpioInverted(false), mValue("unknown"),
+    mOldValue("unknown")
+{
+}
+
+ChassisIntrusionSensor::~ChassisIntrusionSensor()
+{
+    if (mType == IntrusionSensorType::pch)
+    {
+        mPollTimer.cancel();
+    }
+    else if (mType == IntrusionSensorType::gpio)
+    {
+        mInputDev.close();
+        close(mFd);
+    }
+}
diff --git a/src/IntrusionSensorMain.cpp b/src/IntrusionSensorMain.cpp
new file mode 100644
index 0000000..2def3c1
--- /dev/null
+++ b/src/IntrusionSensorMain.cpp
@@ -0,0 +1,231 @@
+/*
+// Copyright (c) 2018 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 <ChassisIntrusionSensor.hpp>
+#include <Utils.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/asio.hpp>
+#include <chrono>
+#include <ctime>
+#include <iostream>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/sd_event.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/server.hpp>
+#include <sdbusplus/timer.hpp>
+
+static constexpr bool DEBUG = false;
+
+static constexpr const char* sensorType =
+    "xyz.openbmc_project.Configuration.ChassisIntrusionSensor";
+
+static bool getIntrusionSensorConfig(
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    IntrusionSensorType* pType, int* pBusId, int* pSlaveAddr, int* pGpioIndex,
+    bool* pGpioInverted)
+{
+    // find matched configuration according to sensor type
+    ManagedObjectType sensorConfigurations;
+    bool useCache = false;
+
+    if (!getSensorConfiguration(sensorType, dbusConnection,
+                                sensorConfigurations, useCache))
+    {
+        std::cerr << "error communicating to entity manager\n";
+        return false;
+    }
+
+    const SensorData* sensorData = nullptr;
+    const std::pair<std::string,
+                    boost::container::flat_map<std::string, BasicVariantType>>*
+        baseConfiguration = nullptr;
+
+    // Get bus and addr of matched configuration
+    for (const std::pair<sdbusplus::message::object_path, SensorData>& sensor :
+         sensorConfigurations)
+    {
+        baseConfiguration = nullptr;
+        sensorData = &(sensor.second);
+
+        // match sensor type
+        auto sensorBase = sensorData->find(sensorType);
+        if (sensorBase == sensorData->end())
+        {
+            std::cerr << "error finding base configuration \n";
+            continue;
+        }
+
+        baseConfiguration = &(*sensorBase);
+
+        // judge class, "Gpio" or "I2C"
+        auto findClass = baseConfiguration->second.find("Class");
+        if (findClass != baseConfiguration->second.end() &&
+            sdbusplus::message::variant_ns::get<std::string>(
+                findClass->second) == "Gpio")
+        {
+            *pType = IntrusionSensorType::gpio;
+        }
+        else
+        {
+            *pType = IntrusionSensorType::pch;
+        }
+
+        // case to find GPIO info
+        if (*pType == IntrusionSensorType::gpio)
+        {
+            auto gpioConfig =
+                sensorData->find(sensorType + std::string(".GpioIntrusion"));
+
+            if (gpioConfig == sensorData->end())
+            {
+                std::cerr
+                    << "error finding GpioIntrusion info in configuration \n";
+                continue;
+            }
+
+            auto findGpioIndex = gpioConfig->second.find("Index");
+            auto findGpioPolarity = gpioConfig->second.find("Polarity");
+
+            if (findGpioIndex == gpioConfig->second.end() ||
+                findGpioPolarity == gpioConfig->second.end())
+            {
+                std::cerr << "error finding gpio info in configuration \n";
+                continue;
+            }
+
+            try
+            {
+                *pGpioIndex = sdbusplus::message::variant_ns::get<uint64_t>(
+                    findGpioIndex->second);
+                *pGpioInverted =
+                    (sdbusplus::message::variant_ns::get<std::string>(
+                         findGpioPolarity->second) == "Low");
+            }
+            catch (const std::bad_variant_access& e)
+            {
+                std::cerr << "invalid value for gpio info in config. \n";
+                continue;
+            }
+
+            if (DEBUG)
+            {
+                std::cout << "find matched GPIO index " << *pGpioIndex
+                          << ", polarity inverted flag is " << *pGpioInverted
+                          << "\n";
+            }
+
+            return true;
+        }
+
+        // case to find I2C info
+        else if (*pType == IntrusionSensorType::pch)
+        {
+            auto findBus = baseConfiguration->second.find("Bus");
+            auto findAddress = baseConfiguration->second.find("Address");
+            if (findBus == baseConfiguration->second.end() ||
+                findAddress == baseConfiguration->second.end())
+            {
+                std::cerr << "error finding bus or address in configuration \n";
+                continue;
+            }
+
+            try
+            {
+                *pBusId = sdbusplus::message::variant_ns::get<uint64_t>(
+                    findBus->second);
+                *pSlaveAddr = sdbusplus::message::variant_ns::get<uint64_t>(
+                    findAddress->second);
+            }
+            catch (const std::bad_variant_access& e)
+            {
+                std::cerr << "invalid value for bus or address in config. \n";
+                continue;
+            }
+
+            if (DEBUG)
+            {
+                std::cout << "find matched bus " << *pBusId
+                          << ", matched slave addr " << *pSlaveAddr << "\n";
+            }
+            return true;
+        }
+    }
+
+    std::cerr << "can't find matched I2C or GPIO configuration. \n";
+    *pBusId = -1;
+    *pSlaveAddr = -1;
+    *pGpioIndex = -1;
+    return false;
+}
+
+int main()
+{
+    int busId = -1, slaveAddr = -1, gpioIndex = -1;
+    bool gpioInverted = false;
+    IntrusionSensorType type = IntrusionSensorType::gpio;
+
+    // setup connection to dbus
+    boost::asio::io_service io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+    auto objServer = sdbusplus::asio::object_server(systemBus);
+
+    // setup object server, define interface
+    systemBus->request_name("xyz.openbmc_project.IntrusionSensor");
+
+    std::shared_ptr<sdbusplus::asio::dbus_interface> ifaceChassis =
+        objServer.add_interface(
+            "/xyz/openbmc_project/Intrusion/Chassis_Intrusion",
+            "xyz.openbmc_project.Chassis.Intrusion");
+
+    ChassisIntrusionSensor chassisIntrusionSensor(io, ifaceChassis);
+
+    if (getIntrusionSensorConfig(systemBus, &type, &busId, &slaveAddr,
+                                 &gpioIndex, &gpioInverted))
+    {
+        chassisIntrusionSensor.start(type, busId, slaveAddr, gpioIndex,
+                                     gpioInverted);
+    }
+
+    // callback to handle configuration change
+    std::function<void(sdbusplus::message::message&)> eventHandler =
+        [&](sdbusplus::message::message& message) {
+            if (message.is_method_error())
+            {
+                std::cerr << "callback method error\n";
+                return;
+            }
+
+            std::cout << "rescan due to configuration change \n";
+            if (getIntrusionSensorConfig(systemBus, &type, &busId, &slaveAddr,
+                                         &gpioIndex, &gpioInverted))
+            {
+                chassisIntrusionSensor.start(type, busId, slaveAddr, gpioIndex,
+                                             gpioInverted);
+            }
+        };
+
+    auto match = std::make_unique<sdbusplus::bus::match::match>(
+        static_cast<sdbusplus::bus::bus&>(*systemBus),
+        "type='signal',member='PropertiesChanged',path_namespace='" +
+            std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'",
+        eventHandler);
+
+    io.run();
+
+    return 0;
+}