intrusionsensor: Add hwmon reading method

This commit adds a new reading method for intrusionsensor, which reads
the chassis intrusion status from hwmon.

Example:
    {
         "Class": "Aspeed2600_Hwmon",
         "Name": "Chassis_Intrusion_Status",
         "Type": "ChassisIntrusionSensor"
    }

intrusionsensor will search for the sysfs hwmon name string in the
compatibleHwmonNames map using the Class string (e.g intrusion0_alarm
for Aspeed2600_Hwmon class) and read the chassis intrusion status from
it. This currently just supports Aspeed2600_Hwmon class which is based
on the aspeed-chassis driver from Aspeed SDK. The driver reads the
Intrusion Status bit in the CHAI10 register from AST2600. This bit
needs to be cleared every read in order to get the current state (which
means to write 0 to the hwmon file). In the condition of being cleared
every read, it will show 0 in hwmon for the normal state, and 1 for the
intrusion state.

Tested:

$ busctl get-property xyz.openbmc_project.IntrusionSensor \
 /xyz/openbmc_project/Chassis/Intrusion \
 xyz.openbmc_project.Chassis.Intrusion Status

If the chassis cover is closed: "Normal"
If the chassis cover is open: "HardwareIntrusion"

Signed-off-by: Chau Ly <chaul@amperecomputing.com>
Change-Id: I6f6a42592c8acae7be4f10e65a8b52ee30a4bd4f
diff --git a/src/ChassisIntrusionSensor.cpp b/src/ChassisIntrusionSensor.cpp
index 841259f..3066359 100644
--- a/src/ChassisIntrusionSensor.cpp
+++ b/src/ChassisIntrusionSensor.cpp
@@ -54,6 +54,9 @@
 // Status bit field masks
 const static constexpr size_t pchRegMaskIntrusion = 0x01;
 
+// Value to clear intrusion status hwmon file
+const static constexpr size_t intrusionStatusHwmonClearValue = 0;
+
 void ChassisIntrusionSensor::updateValue(const size_t& value)
 {
     std::string newValue = value != 0 ? hwIntrusionValStr : normalValStr;
@@ -124,6 +127,7 @@
 void ChassisIntrusionPchSensor::pollSensorStatus()
 {
     std::weak_ptr<ChassisIntrusionPchSensor> weakRef = weak_from_this();
+
     // setting a new experation implicitly cancels any pending async wait
     mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec));
 
@@ -134,12 +138,14 @@
             std::cerr << "Timer of intrusion sensor is cancelled\n";
             return;
         }
+
         std::shared_ptr<ChassisIntrusionPchSensor> self = weakRef.lock();
         if (!self)
         {
             std::cerr << "ChassisIntrusionSensor no self\n";
             return;
         }
+
         int value = self->readSensor();
         if (value < 0)
         {
@@ -150,6 +156,7 @@
             intrusionSensorPollSec = defaultPollSec;
             self->updateValue(value);
         }
+
         // trigger next polling
         self->pollSensorStatus();
     });
@@ -174,6 +181,7 @@
         {
             return; // we're being destroyed
         }
+
         if (ec)
         {
             std::cerr << "Error on GPIO based intrusion sensor wait event\n";
@@ -191,6 +199,83 @@
     });
 }
 
+int ChassisIntrusionHwmonSensor::readSensor()
+{
+    int value = 0;
+
+    std::fstream stream(mHwmonPath, std::ios::in | std::ios::out);
+    if (!stream.good())
+    {
+        std::cerr << "Error reading status at " << mHwmonPath << "\n";
+        return -1;
+    }
+
+    std::string line;
+    if (!std::getline(stream, line))
+    {
+        std::cerr << "Error reading status at " << mHwmonPath << "\n";
+        return -1;
+    }
+
+    try
+    {
+        value = std::stoi(line);
+        if constexpr (debug)
+        {
+            std::cout << "Hwmon type: raw value is " << value << "\n";
+        }
+    }
+    catch (const std::invalid_argument& e)
+    {
+        std::cerr << "Error reading status at " << mHwmonPath << " : "
+                  << e.what() << "\n";
+        return -1;
+    }
+
+    // Reset chassis intrusion status after every reading
+    stream << intrusionStatusHwmonClearValue;
+
+    return value;
+}
+
+void ChassisIntrusionHwmonSensor::pollSensorStatus()
+{
+    std::weak_ptr<ChassisIntrusionHwmonSensor> weakRef = weak_from_this();
+
+    // setting a new experation implicitly cancels any pending async wait
+    mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec));
+
+    mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+        // case of being canceled
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            std::cerr << "Timer of intrusion sensor is cancelled\n";
+            return;
+        }
+
+        std::shared_ptr<ChassisIntrusionHwmonSensor> self = weakRef.lock();
+        if (!self)
+        {
+            std::cerr << "ChassisIntrusionSensor no self\n";
+            return;
+        }
+
+        int value = self->readSensor();
+        if (value < 0)
+        {
+            intrusionSensorPollSec = sensorFailedPollSec;
+        }
+        else
+        {
+            intrusionSensorPollSec = defaultPollSec;
+            self->updateValue(value);
+        }
+
+        // trigger next polling
+        self->pollSensorStatus();
+    });
+}
+
 int ChassisIntrusionSensor::setSensorValue(const std::string& req,
                                            std::string& propertyValue)
 {
@@ -237,7 +322,9 @@
                                     " address " + std::to_string(slaveAddr) +
                                     "\n");
     }
+
     mSlaveAddr = slaveAddr;
+
     std::string devPath = "/dev/i2c-" + std::to_string(busId);
     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
     mBusFd = open(devPath.c_str(), O_RDWR | O_CLOEXEC);
@@ -288,9 +375,46 @@
     {
         throw std::invalid_argument("Failed to get " + mPinName + " fd\n");
     }
+
     mGpioFd.assign(gpioLineFd);
 }
 
+ChassisIntrusionHwmonSensor::ChassisIntrusionHwmonSensor(
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
+    std::string hwmonName) :
+    ChassisIntrusionSensor(objServer),
+    mHwmonName(std::move(hwmonName)), mPollTimer(io)
+{
+    std::vector<fs::path> paths;
+
+    if (!findFiles(fs::path("/sys/class/hwmon"), mHwmonName, paths))
+    {
+        throw std::invalid_argument("Failed to find hwmon path in sysfs\n");
+    }
+
+    if (paths.empty())
+    {
+        throw std::invalid_argument("Hwmon file " + mHwmonName +
+                                    " can't be found in sysfs\n");
+    }
+
+    if (paths.size() > 1)
+    {
+        std::cerr << "Found more than 1 hwmon file to read chassis intrusion"
+                  << " status. Taking the first one. \n";
+    }
+
+    // Expecting only one hwmon file for one given chassis
+    mHwmonPath = paths[0].string();
+
+    if constexpr (debug)
+    {
+        std::cout << "Found " << paths.size()
+                  << " paths for intrusion status \n"
+                  << " The first path is: " << mHwmonPath << "\n";
+    }
+}
+
 ChassisIntrusionSensor::~ChassisIntrusionSensor()
 {
     mObjServer.remove_interface(mIface);
@@ -313,3 +437,8 @@
         mGpioLine.release();
     }
 }
+
+ChassisIntrusionHwmonSensor::~ChassisIntrusionHwmonSensor()
+{
+    mPollTimer.cancel();
+}