psu-ng: Create errors for detected faults

Add in a function for creating errors using the Create D-Bus call.

Update the analyze function to create errors when faults are found.

We would want to log a fault per power supply, so move the faultLogged
concept down to the PowerSupply object itself.

Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
Change-Id: I94d22b2b8a495abde87fb921c9177c6d25225ef7
diff --git a/meson.build b/meson.build
index eef6b82..7d90fa3 100644
--- a/meson.build
+++ b/meson.build
@@ -40,6 +40,7 @@
 pthread = dependency('threads')
 stdplus = dependency('stdplus')
 boost = dependency('boost')
+fmt = dependency('fmt')
 
 systemd = dependency('systemd')
 servicedir = systemd.get_pkgconfig_variable('systemdsystemunitdir')
diff --git a/phosphor-power-supply/meson.build b/phosphor-power-supply/meson.build
index 342e4e4..fa83ae7 100644
--- a/phosphor-power-supply/meson.build
+++ b/phosphor-power-supply/meson.build
@@ -11,6 +11,7 @@
     dependencies: [
         sdbusplus,
         sdeventplus,
+        fmt,
     ],
     include_directories: '..',
     install: true,
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index 97acd6d..ab1caf9 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -129,6 +129,7 @@
     inputFault = false;
     mfrFault = false;
     vinUVFault = false;
+    faultLogged = false;
 
     // The PMBus device driver does not allow for writing CLEAR_FAULTS
     // directly. However, the pmbus hwmon device driver code will send a
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index c94871f..f1220fd 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -152,6 +152,22 @@
     }
 
     /**
+     * @brief Return whether a fault has been logged for this power supply
+     */
+    bool isFaultLogged() const
+    {
+        return faultLogged;
+    }
+
+    /**
+     * @brief Called when a fault for this power supply has been logged.
+     */
+    void setFaultLogged()
+    {
+        faultLogged = true;
+    }
+
+    /**
      * @brief Returns true if INPUT fault occurred.
      */
     bool hasInputFault() const
@@ -190,6 +206,9 @@
     /** @brief True if a fault has already been found and not cleared */
     bool faultFound = false;
 
+    /** @brief True if an error for a fault has already been logged. */
+    bool faultLogged = false;
+
     /** @brief True if bit 5 of STATUS_WORD high byte is on. */
     bool inputFault = false;
 
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index 0fcd566..c84201e 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -2,6 +2,10 @@
 
 #include "utility.hpp"
 
+#include <fmt/format.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 using namespace phosphor::logging;
 
 namespace phosphor::power::manager
@@ -125,6 +129,44 @@
     }
 }
 
+void PSUManager::createError(
+    const std::string& faultName,
+    const std::map<std::string, std::string>& additionalData)
+{
+    using namespace sdbusplus::xyz::openbmc_project;
+    constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
+    constexpr auto loggingCreateInterface =
+        "xyz.openbmc_project.Logging.Create";
+
+    try
+    {
+        auto service =
+            util::getService(loggingObjectPath, loggingCreateInterface, bus);
+
+        if (service.empty())
+        {
+            log<level::ERR>("Unable to get logging manager service");
+            return;
+        }
+
+        auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
+                                          loggingCreateInterface, "Create");
+
+        auto level = Logging::server::Entry::Level::Error;
+        method.append(faultName, level, additionalData);
+
+        auto reply = bus.call(method);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "Failed creating event log for fault {} due to error {}",
+                faultName, e.what())
+                .c_str());
+    }
+}
+
 void PSUManager::analyze()
 {
     for (auto& psu : psus)
@@ -135,21 +177,45 @@
     for (auto& psu : psus)
     {
         // TODO: Fault priorities #918
-        if (!faultLogged && psu->isFaulted())
+        if (!psu->isFaultLogged() && psu->isFaulted())
         {
-            if (psu->hasInputFault())
-            {
-                // TODO: Create error log
-            }
+            std::map<std::string, std::string> additionalData;
+            additionalData["_PID"] = std::to_string(getpid());
+            additionalData["STATUS_WORD"] =
+                std::to_string(psu->getStatusWord());
 
-            if (psu->hasMFRFault())
+            if ((psu->hasInputFault() || psu->hasVINUVFault()))
             {
-                // TODO: Create error log
+                /* The power supply location might be needed if the input fault
+                 * is due to a problem with the power supply itself. Include the
+                 * inventory path with a call out priority of low.
+                 */
+                additionalData["CALLOUT_INVENTORY_PATH"] =
+                    psu->getInventoryPath();
+                additionalData["CALLOUT_PRIORITY"] = "L";
+                createError(
+                    "xyz.openbmc_project.Power.PowerSupply.Error.InputFault",
+                    additionalData);
+                psu->setFaultLogged();
             }
-
-            if (psu->hasVINUVFault())
+            else if (psu->hasMFRFault())
             {
-                // TODO: Create error log
+                /* This can represent a variety of faults that result in calling
+                 * out the power supply for replacement:
+                 * Output OverCurrent, Output Under Voltage, and potentially
+                 * other faults.
+                 *
+                 * Also plan on putting specific fault in AdditionalData,
+                 * along with register names and register values
+                 * (STATUS_WORD, STATUS_MFR, etc.).*/
+
+                additionalData["CALLOUT_INVENTORY_PATH"] =
+                    psu->getInventoryPath();
+
+                createError("xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                            additionalData);
+
+                psu->setFaultLogged();
             }
         }
     }
diff --git a/phosphor-power-supply/psu_manager.hpp b/phosphor-power-supply/psu_manager.hpp
index ca8fd05..06d7c82 100644
--- a/phosphor-power-supply/psu_manager.hpp
+++ b/phosphor-power-supply/psu_manager.hpp
@@ -121,8 +121,6 @@
         {
             psu->clearFaults();
         }
-
-        faultLogged = false;
     }
 
   private:
@@ -139,16 +137,24 @@
         timer;
 
     /**
+     * Create an error
+     *
+     * @param[in] faultName - 'name' message for the BMC error log entry
+     * @param[in] additionalData - The AdditionalData property for the error
+     */
+    void createError(const std::string& faultName,
+                     const std::map<std::string, std::string>& additionalData);
+
+    /**
      * Analyze the status of each of the power supplies.
+     *
+     * Log errors for faults, when and where appropriate.
      */
     void analyze();
 
     /** @brief True if the power is on. */
     bool powerOn = false;
 
-    /** @brief True if fault logged. Clear in clearFaults(). */
-    bool faultLogged = false;
-
     /** @brief Used as part of subscribing to power on state changes*/
     std::string powerService;