Add Chassis POH Counter

Added 32-bit counter, which shows how many hours the system has been
running. The value is a cumulative one and includes all working hours
since production. If the chassis state is Powered-on, This will be
incremented by one for every hour. It won't get updated when the
chassis state is powered-off.

Resolves openbmc/openbmc#2979

Change-Id: I18e9bb571d1a6e401b25450168249f70891be665
Signed-off-by: Nagaraju Goruganti <ngorugan@in.ibm.com>
diff --git a/chassis_state_manager.cpp b/chassis_state_manager.cpp
index ece0b1c..d78a6f7 100644
--- a/chassis_state_manager.cpp
+++ b/chassis_state_manager.cpp
@@ -1,6 +1,15 @@
 #include <sdbusplus/bus.hpp>
 #include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include "xyz/openbmc_project/Common/error.hpp"
 #include "chassis_state_manager.hpp"
+#include <cereal/archives/json.hpp>
+#include <fstream>
+#include "config.h"
+#include <experimental/filesystem>
+
+// Register class version with Cereal
+CEREAL_CLASS_VERSION(phosphor::state::manager::Chassis, CLASS_VERSION);
 
 namespace phosphor
 {
@@ -181,10 +190,127 @@
 
 Chassis::PowerState Chassis::currentPowerState(PowerState value)
 {
+    PowerState chassisPowerState;
     log<level::INFO>("Change to Chassis Power State",
                      entry("CHASSIS_CURRENT_POWER_STATE=%s",
                            convertForMessage(value).c_str()));
-    return server::Chassis::currentPowerState(value);
+
+    chassisPowerState = server::Chassis::currentPowerState(value);
+    if (chassisPowerState == PowerState::On)
+    {
+        timer->state(timer::ON);
+    }
+    else
+    {
+        timer->state(timer::OFF);
+    }
+    return chassisPowerState;
+}
+
+uint32_t Chassis::pOHCounter(uint32_t value)
+{
+    if (value != pOHCounter())
+    {
+        ChassisInherit::pOHCounter(value);
+        serialize();
+    }
+    return pOHCounter();
+}
+
+void Chassis::restorePOHCounter()
+{
+    uint32_t counter;
+    if (!deserialize(POH_COUNTER_PERSIST_PATH, counter))
+    {
+        // set to default value
+        pOHCounter(0);
+    }
+    else
+    {
+        pOHCounter(counter);
+    }
+}
+
+fs::path Chassis::serialize(const fs::path& path)
+{
+    std::ofstream os(path.c_str(), std::ios::binary);
+    cereal::JSONOutputArchive oarchive(os);
+    oarchive(pOHCounter());
+    return path;
+}
+
+bool Chassis::deserialize(const fs::path& path, uint32_t& pOHCounter)
+{
+    try
+    {
+        if (fs::exists(path))
+        {
+            std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+            cereal::JSONInputArchive iarchive(is);
+            iarchive(pOHCounter);
+            return true;
+        }
+        return false;
+    }
+    catch (cereal::Exception& e)
+    {
+        log<level::ERR>(e.what());
+        fs::remove(path);
+        return false;
+    }
+    catch (const fs::filesystem_error& e)
+    {
+        return false;
+    }
+
+    return false;
+}
+
+void Chassis::startPOHCounter()
+{
+    using namespace std::chrono_literals;
+    using namespace phosphor::logging;
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+    auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
+    fs::create_directories(dir);
+
+    sd_event* event = nullptr;
+    auto r = sd_event_default(&event);
+    if (r < 0)
+    {
+        log<level::ERR>("Error creating a default sd_event handler");
+        throw;
+    }
+
+    phosphor::state::manager::EventPtr eventP{event};
+    event = nullptr;
+
+    auto callback = [&]() {
+        if (ChassisInherit::currentPowerState() == PowerState::On)
+        {
+            pOHCounter(pOHCounter() + 1);
+        }
+    };
+
+    try
+    {
+        timer = std::make_unique<phosphor::state::manager::Timer>(
+            eventP, callback, std::chrono::seconds(POH::hour),
+            phosphor::state::manager::timer::ON);
+        bus.attach_event(eventP.get(), SD_EVENT_PRIORITY_NORMAL);
+        r = sd_event_loop(eventP.get());
+        if (r < 0)
+        {
+            log<level::ERR>("Error occurred during the sd_event_loop",
+                            entry("RC=%d", r));
+            elog<InternalFailure>();
+        }
+    }
+    catch (InternalFailure& e)
+    {
+        phosphor::logging::commit<InternalFailure>();
+    }
 }
 
 } // namespace manager