Add chassis identify ipmi command.

Add command with support for chassis identify.

Resolves openbmc/openbmc#2621

Change-Id: Ie656b71eba68141117838363456c6e075225e3e6
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/chassishandler.cpp b/chassishandler.cpp
index 0eef0a0..43c3fe3 100644
--- a/chassishandler.cpp
+++ b/chassishandler.cpp
@@ -17,6 +17,8 @@
 #include <sstream>
 #include <array>
 #include <fstream>
+#include <future>
+#include <chrono>
 #include <experimental/filesystem>
 #include <string>
 #include <map>
@@ -47,6 +49,7 @@
 constexpr size_t MAX_PREFIX_VALUE = 32;
 constexpr size_t SIZE_COOKIE = 4;
 constexpr size_t SIZE_VERSION = 2;
+constexpr size_t DEFAULT_IDENTIFY_TIME_OUT = 15;
 
 //PetiBoot-Specific
 static constexpr uint8_t net_conf_initial_bytes[] = {0x80, 0x21, 0x70, 0x62,
@@ -67,6 +70,8 @@
 const char *settings_object_name  =  "/org/openbmc/settings/host0";
 const char *settings_intf_name    =  "org.freedesktop.DBus.Properties";
 const char *host_intf_name        =  "org.openbmc.settings.Host";
+const char *identify_led_object_name =
+    "/xyz/openbmc_project/led/groups/enclosure_identify";
 
 constexpr auto SETTINGS_ROOT = "/";
 constexpr auto SETTINGS_MATCH = "host0";
@@ -1025,6 +1030,111 @@
     return ( (rc < 0) ? IPMI_CC_INVALID : IPMI_CC_OK);
 }
 
+ipmi_ret_t ipmi_chassis_identify(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                                 ipmi_request_t request,
+                                 ipmi_response_t response,
+                                 ipmi_data_len_t data_len,
+                                 ipmi_context_t context)
+{
+    static std::atomic_size_t currentCallerId(0);
+    static std::unique_ptr<std::future<void>> future;
+    static std::condition_variable condition;
+    static std::mutex timeoutMutex;
+
+    if (*data_len > 2)
+    {
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+    uint8_t identifyInterval = *data_len > 0 ?
+                               (static_cast<uint8_t*>(request))[0] :
+                               DEFAULT_IDENTIFY_TIME_OUT;
+    bool forceIdentify =
+        *data_len == 2 ? (static_cast<uint8_t*>(request))[1] & 0x01 : false;
+
+    currentCallerId++;
+
+    // stop any threads currently running
+    condition.notify_all();
+
+    // lookup enclosure_identify group owner(s) in mapper
+    auto mapperCall = chassis::internal::dbus.new_method_call(
+                          ipmi::MAPPER_BUS_NAME,
+                          ipmi::MAPPER_OBJ,
+                          ipmi::MAPPER_INTF,
+                          "GetObject");
+
+    mapperCall.append(identify_led_object_name);
+    static const std::vector<std::string> interfaces =
+    {
+        "xyz.openbmc_project.Led.Group"
+    };
+    mapperCall.append(interfaces);
+    auto mapperReply = chassis::internal::dbus.call(mapperCall);
+    if (mapperReply.is_method_error())
+    {
+        log<level::ERR>("Chassis Identify: Error communicating to mapper.");
+        return IPMI_CC_RESPONSE_ERROR;
+    }
+    std::vector<std::pair<std::string, std::vector<std::string>>> mapperResp;
+    mapperReply.read(mapperResp);
+
+    for (auto& object : mapperResp)
+    {
+        std::string& connection = object.first;
+
+        if (identifyInterval || forceIdentify)
+        {
+            auto ledOn = chassis::internal::dbus.new_method_call(
+                             connection.c_str(),
+                             identify_led_object_name,
+                             "org.freedesktop.DBus.Properties", "Set");
+            ledOn.append(
+                "xyz.openbmc_project.Led.Group", "Asserted",
+                sdbusplus::message::variant<bool>(
+                    true));
+            auto ledReply = chassis::internal::dbus.call(ledOn);
+            if (ledReply.is_method_error())
+            {
+                log<level::ERR>("Chassis Identify: Error Setting State On\n");
+                return IPMI_CC_RESPONSE_ERROR;
+            }
+            if (forceIdentify)
+            {
+                return IPMI_CC_OK;
+            }
+        }
+
+        size_t threadCallerId = currentCallerId;
+        future = std::make_unique<std::future<void>>(
+                     std::async(std::launch::async,
+                                [connection,
+                                 identifyInterval,
+                                 threadCallerId]
+        {
+            std::unique_lock<std::mutex> lock(timeoutMutex);
+            if (condition.wait_for(lock,
+            std::chrono::seconds(identifyInterval),
+            [&threadCallerId]{return currentCallerId != threadCallerId;}))
+            {
+                return;  // another thread started.
+            }
+            auto ledOff = chassis::internal::dbus.new_method_call(
+                connection.c_str(),
+                identify_led_object_name,
+                "org.freedesktop.DBus.Properties", "Set");
+            ledOff.append("xyz.openbmc_project.Led.Group", "Asserted",
+            sdbusplus::message::variant<bool>(
+                false));
+            auto ledReply = chassis::internal::dbus.call(ledOff);
+            if (ledReply.is_method_error())
+            {
+                log<level::ERR>("Chassis Identify: Error Setting State Off\n");
+            }
+        }));
+    }
+    return IPMI_CC_OK;
+}
+
 namespace boot_options
 {
 
@@ -1377,6 +1487,11 @@
     ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_CHASSIS_CONTROL, NULL, ipmi_chassis_control,
                            PRIVILEGE_OPERATOR);
 
+    // <Chassis Identify>
+    printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_CHASSIS, IPMI_CMD_CHASSIS_IDENTIFY);
+    ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_CHASSIS_IDENTIFY, NULL,
+                           ipmi_chassis_identify, PRIVILEGE_OPERATOR);
+
     // <Set System Boot Options>
     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_CHASSIS, IPMI_CMD_SET_SYS_BOOT_OPTIONS);
     ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_SET_SYS_BOOT_OPTIONS, NULL,
diff --git a/chassishandler.h b/chassishandler.h
index 528a05d..c626158 100644
--- a/chassishandler.h
+++ b/chassishandler.h
@@ -10,8 +10,9 @@
     IPMI_CMD_GET_CHASSIS_CAP      = 0x00,
     // Chassis Status
     IPMI_CMD_CHASSIS_STATUS       = 0x01,
-	// Chassis Control
-	IPMI_CMD_CHASSIS_CONTROL	  = 0x02,
+    // Chassis Control
+    IPMI_CMD_CHASSIS_CONTROL      = 0x02,
+    IPMI_CMD_CHASSIS_IDENTIFY     = 0x04,
     // Get capability bits
     IPMI_CMD_SET_SYS_BOOT_OPTIONS = 0x08,
     IPMI_CMD_GET_SYS_BOOT_OPTIONS = 0x09,