Support LED control for phosphor-nvme

- Add logic control of the LED
- Set inventory of NVMe drive to D-bus inventory manager

Change-Id: Ie1ef6b1a4523fcc80c3c2e9d274edd6fd02b2990
Signed-off-by: Tony Lee <tony.lee@quantatw.com>
diff --git a/nvme_manager.cpp b/nvme_manager.cpp
index 639f85d..0e0b7b7 100644
--- a/nvme_manager.cpp
+++ b/nvme_manager.cpp
@@ -7,8 +7,10 @@
 #include <nlohmann/json.hpp>
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
+#include <sdbusplus/message.hpp>
 #include <sstream>
 #include <string>
+#include <xyz/openbmc_project/Led/Physical/server.hpp>
 
 #include "i2c.h"
 #define MONITOR_INTERVAL_SECONDS 1
@@ -16,6 +18,7 @@
 #define GPIO_BASE_PATH "/sys/class/gpio/gpio"
 #define IS_PRESENT "0"
 #define POWERGD "1"
+#define NOWARNING_STRING "ff"
 
 static constexpr auto configFile = "/etc/nvme/nvme_config.json";
 static constexpr auto delay = std::chrono::milliseconds{100};
@@ -24,6 +27,13 @@
 static constexpr const uint8_t COMMAND_CODE_0 = 0;
 static constexpr const uint8_t COMMAND_CODE_8 = 8;
 
+static constexpr int CapacityFaultMask = 1;
+static constexpr int temperatureFaultMask = 1 << 1;
+static constexpr int DegradesFaultMask = 1 << 2;
+static constexpr int MediaFaultMask = 1 << 3;
+static constexpr int BackupDeviceFaultMask = 1 << 4;
+static constexpr int NOWARNING = 255;
+
 static constexpr int SERIALNUMBER_START_INDEX = 3;
 static constexpr int SERIALNUMBER_END_INDEX = 23;
 
@@ -39,6 +49,143 @@
 using namespace std;
 using namespace phosphor::logging;
 
+void Nvme::setNvmeInventoryProperties(
+    bool present, const phosphor::nvme::Nvme::NVMeData& nvmeData,
+    const std::string& inventoryPath)
+{
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 ITEM_IFACE, "Present", present);
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 ASSET_IFACE, "Manufacturer", nvmeData.vendor);
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 ASSET_IFACE, "SerialNumber",
+                                 nvmeData.serialNumber);
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "SmartWarnings",
+                                 nvmeData.smartWarnings);
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "StatusFlags",
+                                 nvmeData.statusFlags);
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "DriveLifeUsed",
+                                 nvmeData.driveLifeUsed);
+
+    auto smartWarning = (!nvmeData.smartWarnings.empty())
+                            ? std::stoi(nvmeData.smartWarnings, 0, 16)
+                            : NOWARNING;
+
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "CapacityFault",
+                                 !(smartWarning & CapacityFaultMask));
+
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "TemperatureFault",
+                                 !(smartWarning & temperatureFaultMask));
+
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "DegradesFault",
+                                 !(smartWarning & DegradesFaultMask));
+
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "MediaFault",
+                                 !(smartWarning & MediaFaultMask));
+
+    util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
+                                 NVME_STATUS_IFACE, "BackupDeviceFault",
+                                 !(smartWarning & BackupDeviceFaultMask));
+}
+
+void Nvme::setFaultLED(const std::string& locateLedGroupPath,
+                       const std::string& faultLedGroupPath, bool request)
+{
+    if (locateLedGroupPath.empty() || faultLedGroupPath.empty())
+    {
+        return;
+    }
+
+    // Before toggle LED, check whether is Identify or not.
+    if (!getLEDGroupState(locateLedGroupPath))
+    {
+        util::SDBusPlus::setProperty(bus, LED_GROUP_BUSNAME, faultLedGroupPath,
+                                     LED_GROUP_IFACE, "Asserted", request);
+    }
+}
+
+void Nvme::setLocateLED(const std::string& locateLedGroupPath,
+                        const std::string& locateLedBusName,
+                        const std::string& locateLedPath, bool isPresent)
+{
+    if (locateLedGroupPath.empty() || locateLedBusName.empty() ||
+        locateLedPath.empty())
+    {
+        return;
+    }
+
+    namespace server = sdbusplus::xyz::openbmc_project::Led::server;
+
+    if (!getLEDGroupState(locateLedGroupPath))
+    {
+        if (isPresent)
+            util::SDBusPlus::setProperty(
+                bus, locateLedBusName, locateLedPath, LED_CONTROLLER_IFACE,
+                "State",
+                server::convertForMessage(server::Physical::Action::On));
+        else
+            util::SDBusPlus::setProperty(
+                bus, locateLedBusName, locateLedPath, LED_CONTROLLER_IFACE,
+                "State",
+                server::convertForMessage(server::Physical::Action::Off));
+    }
+}
+
+bool Nvme::getLEDGroupState(const std::string& ledPath)
+{
+    auto asserted = util::SDBusPlus::getProperty<bool>(
+        bus, LED_GROUP_BUSNAME, ledPath, LED_GROUP_IFACE, "Asserted");
+
+    return asserted;
+}
+
+void Nvme::setLEDsStatus(const phosphor::nvme::Nvme::NVMeConfig& config,
+                         bool success,
+                         const phosphor::nvme::Nvme::NVMeData& nvmeData)
+{
+    static std::unordered_map<std::string, bool> isError;
+
+    if (success)
+    {
+        if (!nvmeData.smartWarnings.empty())
+        {
+            auto request =
+                (strcmp(nvmeData.smartWarnings.c_str(), NOWARNING_STRING) == 0)
+                    ? false
+                    : true;
+
+            setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath,
+                        request);
+            setLocateLED(config.locateLedGroupPath,
+                         config.locateLedControllerBusName,
+                         config.locateLedControllerPath, !request);
+        }
+        isError[config.index] = false;
+    }
+    else
+    {
+        if (isError[config.index] != true)
+        {
+            // Drive is present but can not get data, turn on fault LED.
+            log<level::ERR>("Drive status is good but can not get data.",
+                            entry("objPath = %s", config.index.c_str()));
+            isError[config.index] = true;
+        }
+
+        setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath, true);
+        setLocateLED(config.locateLedGroupPath,
+                     config.locateLedControllerBusName,
+                     config.locateLedControllerPath, false);
+    }
+}
+
 std::string intToHex(int input)
 {
     std::stringstream tmp;
@@ -141,6 +288,8 @@
 
 void Nvme::run()
 {
+    init();
+
     std::function<void()> callback(std::bind(&Nvme::read, this));
     try
     {
@@ -215,13 +364,26 @@
             {
                 uint8_t index = instance.value("NVMeDriveIndex", 0);
                 uint8_t busID = instance.value("NVMeDriveBusID", 0);
+                std::string faultLedGroupPath =
+                    instance.value("NVMeDriveFaultLEDGroupPath", "");
+                std::string locateLedGroupPath =
+                    instance.value("NVMeDriveLocateLEDGroupPath", "");
                 uint8_t presentPin = instance.value("NVMeDrivePresentPin", 0);
                 uint8_t pwrGoodPin = instance.value("NVMeDrivePwrGoodPin", 0);
+                std::string locateLedControllerBusName =
+                    instance.value("NVMeDriveLocateLEDControllerBusName", "");
+                std::string locateLedControllerPath =
+                    instance.value("NVMeDriveLocateLEDControllerPath", "");
 
                 nvmeConfig.index = std::to_string(index);
                 nvmeConfig.busID = busID;
+                nvmeConfig.faultLedGroupPath = faultLedGroupPath;
                 nvmeConfig.presentPin = presentPin;
                 nvmeConfig.pwrGoodPin = pwrGoodPin;
+                nvmeConfig.locateLedControllerBusName =
+                    locateLedControllerBusName;
+                nvmeConfig.locateLedControllerPath = locateLedControllerPath;
+                nvmeConfig.locateLedGroupPath = locateLedGroupPath;
                 nvmeConfig.criticalHigh = criticalHigh;
                 nvmeConfig.criticalLow = criticalLow;
                 nvmeConfig.warningHigh = warningHigh;
@@ -275,11 +437,39 @@
     return val;
 }
 
+void Nvme::createNVMeInventory()
+{
+    using Properties =
+        std::map<std::string, sdbusplus::message::variant<std::string, bool>>;
+    using Interfaces = std::map<std::string, Properties>;
+
+    std::string inventoryPath;
+    std::map<sdbusplus::message::object_path, Interfaces> obj;
+
+    for (const auto config : configs)
+    {
+        inventoryPath = "/system/chassis/motherboard/nvme" + config.index;
+
+        obj = {{
+            inventoryPath,
+            {{ITEM_IFACE, {}}, {NVME_STATUS_IFACE, {}}, {ASSET_IFACE, {}}},
+        }};
+        util::SDBusPlus::CallMethod(bus, INVENTORY_BUSNAME, INVENTORY_NAMESPACE,
+                                    INVENTORY_MANAGER_IFACE, "Notify", obj);
+    }
+}
+
+void Nvme::init()
+{
+    createNVMeInventory();
+}
+
 /** @brief Monitor NVMe drives every one second  */
 void Nvme::read()
 {
     std::string devPresentPath;
     std::string devPwrGoodPath;
+    std::string inventoryPath;
 
     static std::unordered_map<std::string, bool> isErrorPower;
 
@@ -292,6 +482,8 @@
         devPwrGoodPath =
             GPIO_BASE_PATH + std::to_string(config.pwrGoodPin) + "/value";
 
+        inventoryPath = NVME_INVENTORY_PATH + config.index;
+
         auto iter = nvmes.find(config.index);
 
         if (getGPIOValueOfNvme(devPresentPath) == IS_PRESENT)
@@ -301,7 +493,7 @@
             if (getGPIOValueOfNvme(devPwrGoodPath) == POWERGD)
             {
                 // get NVMe information through i2c by busID.
-                getNVMeInfobyBusID(config.busID, nvmeData);
+                auto success = getNVMeInfobyBusID(config.busID, nvmeData);
                 // can not find. create dbus
                 if (iter == nvmes.end())
                 {
@@ -313,6 +505,7 @@
                         bus, objPath.c_str());
                     nvmes.emplace(config.index, nvmeSSD);
 
+                    setNvmeInventoryProperties(true, nvmeData, inventoryPath);
                     nvmeSSD->setSensorValueToDbus(nvmeData.sensorValue);
                     nvmeSSD->setSensorThreshold(
                         config.criticalHigh, config.criticalLow,
@@ -320,19 +513,32 @@
                         config.warningLow);
 
                     nvmeSSD->checkSensorThreshold();
+                    setLEDsStatus(config, success, nvmeData);
                 }
                 else
                 {
+                    setNvmeInventoryProperties(true, nvmeData, inventoryPath);
                     iter->second->setSensorValueToDbus(nvmeData.sensorValue);
                     iter->second->checkSensorThreshold();
+                    setLEDsStatus(config, success, nvmeData);
                 }
+
                 isErrorPower[config.index] = false;
             }
             else
             {
                 // Present pin is true but power good pin is false
-                // remove nvme d-bus path
+                // remove nvme d-bus path, clean all properties in inventory
+                // and turn on fault LED
+
+                setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath,
+                            true);
+                setLocateLED(config.locateLedGroupPath,
+                             config.locateLedControllerBusName,
+                             config.locateLedControllerPath, false);
+
                 nvmeData = NVMeData();
+                setNvmeInventoryProperties(false, nvmeData, inventoryPath);
                 nvmes.erase(config.index);
 
                 if (isErrorPower[config.index] != true)
@@ -349,8 +555,18 @@
         }
         else
         {
-            // Drive not present, remove nvme d-bus path
+            // Drive not present, remove nvme d-bus path ,
+            // clean all properties in inventory
+            // and turn off fault and locate LED
+
+            setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath,
+                        false);
+            setLocateLED(config.locateLedGroupPath,
+                         config.locateLedControllerBusName,
+                         config.locateLedControllerPath, false);
+
             nvmeData = NVMeData();
+            setNvmeInventoryProperties(false, nvmeData, inventoryPath);
             nvmes.erase(config.index);
         }
     }