Add fan profile host support

Implement the fan profile portions of get / set fan
profile ipmi commands. These commands have been overused
with too much debug information, currently we only plan
to support the part that the host needs.

Tested-by: Modified the settings on the host and they
were persisted correctly on the next boot

Change-Id: I5736703123154193b8e193ccb14fc2ffb9f5320b
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in
index 811ad43..f7161ac 100644
--- a/CMakeLists.txt.in
+++ b/CMakeLists.txt.in
@@ -9,7 +9,7 @@
 externalproject_add (
     sdbusplus-project PREFIX ${CMAKE_BINARY_DIR}/sdbusplus-project
     GIT_REPOSITORY https://github.com/openbmc/sdbusplus.git GIT_TAG
-    6b4fb2969cd0c853ff6aa7f9bdd3ddaa0081c204 SOURCE_DIR
+    f0dd3b5a3c6c54b4f38844b573e3f157f8064088 SOURCE_DIR
     ${CMAKE_BINARY_DIR}/sdbusplus-src BINARY_DIR
     ${CMAKE_BINARY_DIR}/sdbusplus-build CONFIGURE_COMMAND "" BUILD_COMMAND cd
     ${CMAKE_BINARY_DIR}/sdbusplus-src && ./bootstrap.sh clean && ./bootstrap.sh
@@ -22,7 +22,7 @@
     dbus-interfaces PREFIX ${CMAKE_BINARY_DIR}/phosphor-dbus-interfaces DEPENDS
     sdbusplus-project GIT_REPOSITORY
     https://github.com/openbmc/phosphor-dbus-interfaces GIT_TAG
-    4132f4b6b1de57a993af9bd2bcd039957786a227 SOURCE_DIR
+    05207d69427cc5f016f08dde801b702d1461cfec SOURCE_DIR
     ${CMAKE_BINARY_DIR}/phosphor-dbus-interfaces-src BINARY_DIR
     ${CMAKE_BINARY_DIR}/phosphor-dbus-interfaces-build CONFIGURE_COMMAND ""
     BUILD_COMMAND cd ${CMAKE_BINARY_DIR}/phosphor-dbus-interfaces-src && echo
@@ -52,7 +52,7 @@
     phosphor-logging PREFIX ${CMAKE_BINARY_DIR}/phosphor-logging DEPENDS cereal
     sdbusplus-project dbus-interfaces GIT_REPOSITORY
     https://github.com/openbmc/phosphor-logging GIT_TAG
-    477b731ad0fd8c116ffcaa8265a508c9fb112479 SOURCE_DIR
+    8024d1dc7dfff6360f3e1bdbce145652eb5698be SOURCE_DIR
     ${CMAKE_BINARY_DIR}/phosphor-logging-src BINARY_DIR
     ${CMAKE_BINARY_DIR}/phosphor-logging-build CONFIGURE_COMMAND ""
     BUILD_COMMAND cd ${CMAKE_BINARY_DIR}/phosphor-logging-src && echo
diff --git a/include/oemcommands.hpp b/include/oemcommands.hpp
index a348db2..df6b007 100644
--- a/include/oemcommands.hpp
+++ b/include/oemcommands.hpp
@@ -26,6 +26,8 @@
     cmdGetPowerRestoreDelay = 0x55,
     cmdSetShutdownPolicy = 0x60,
     cmdGetShutdownPolicy = 0x62,
+    cmdSetFanConfig = 0x89,
+    cmdGetFanConfig = 0x8a,
     cmdGetChassisIdentifier = 0x92,
     cmdGetProcessorErrConfig = 0x9A,
     cmdSetProcessorErrConfig = 0x9B,
@@ -127,6 +129,8 @@
 static constexpr const char* fwSetEnvCmd = "/sbin/fw_setenv";
 static constexpr const char* fwHostSerailCfgEnvName = "hostserialcfg";
 
+constexpr const char* settingsBusName = "xyz.openbmc_project.Settings";
+
 static constexpr const uint8_t getHostSerialCfgCmd = 0;
 static constexpr const uint8_t setHostSerialCfgCmd = 1;
 
@@ -264,9 +268,34 @@
     uint8_t policySupport;
 };
 
+struct SetFanConfigReq
+{
+    uint8_t selectedProfile;
+    uint8_t flags;
+    // other parameters from previous generation are not supported
+};
+
+struct GetFanConfigResp
+{
+    uint8_t supportMask;
+    uint8_t profileSupport;
+    uint8_t fanControlProfileEnable;
+    uint8_t flags;
+    uint8_t dimmPresenceMap[4];
+};
+
 struct CfgHostSerialReq
 {
     uint8_t command;
     uint8_t parameter;
 };
 #pragma pack(pop)
+
+enum class setFanProfileFlags : uint8_t
+{
+    setFanProfile = 7,
+    setPerfAcousMode = 6,
+    // reserved [5:3]
+    performAcousSelect = 2
+    // reserved [1:0]
+};
\ No newline at end of file
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index 5791b28..cc4a437 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -20,6 +20,7 @@
 #include <ipmid/api.h>
 
 #include <array>
+#include <boost/container/flat_map.hpp>
 #include <boost/process/child.hpp>
 #include <boost/process/io.hpp>
 #include <commandutils.hpp>
@@ -29,6 +30,7 @@
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
 #include <string>
+#include <variant>
 #include <vector>
 
 namespace ipmi
@@ -704,6 +706,146 @@
     return IPMI_CC_OK;
 }
 
+constexpr const char* thermalModeInterface =
+    "xyz.openbmc_project.Control.ThermalMode";
+constexpr const char* thermalModePath =
+    "/xyz/openbmc_project/control/thermal_mode";
+
+bool getFanProfileInterface(
+    sdbusplus::bus::bus& bus,
+    boost::container::flat_map<
+        std::string, std::variant<std::vector<std::string>, std::string>>& resp)
+{
+    auto call = bus.new_method_call(settingsBusName, thermalModePath, PROP_INTF,
+                                    "GetAll");
+    call.append(thermalModeInterface);
+    try
+    {
+        auto data = bus.call(call);
+        data.read(resp);
+    }
+    catch (sdbusplus::exception_t& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "getFanProfileInterface: can't get thermal mode!",
+            phosphor::logging::entry("ERR=%s", e.what()));
+        return false;
+    }
+    return true;
+}
+
+ipmi_ret_t ipmiOEMSetFanConfig(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                               ipmi_request_t request, ipmi_response_t response,
+                               ipmi_data_len_t dataLen, ipmi_context_t context)
+{
+
+    if (*dataLen < 2 || *dataLen > 7)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmiOEMSetFanConfig: invalid input len!");
+        *dataLen = 0;
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    // todo: tell bios to only send first 2 bytes
+
+    SetFanConfigReq* req = reinterpret_cast<SetFanConfigReq*>(request);
+    boost::container::flat_map<
+        std::string, std::variant<std::vector<std::string>, std::string>>
+        profileData;
+    if (!getFanProfileInterface(dbus, profileData))
+    {
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    std::vector<std::string>* supported =
+        std::get_if<std::vector<std::string>>(&profileData["Supported"]);
+    if (supported == nullptr)
+    {
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+    std::string mode;
+    if (req->flags &
+        (1 << static_cast<uint8_t>(setFanProfileFlags::setPerfAcousMode)))
+    {
+        bool performanceMode =
+            (req->flags & (1 << static_cast<uint8_t>(
+                               setFanProfileFlags::performAcousSelect))) > 0;
+
+        if (performanceMode)
+        {
+
+            if (std::find(supported->begin(), supported->end(),
+                          "Performance") != supported->end())
+            {
+                mode = "Performance";
+            }
+        }
+        else
+        {
+
+            if (std::find(supported->begin(), supported->end(), "Acoustic") !=
+                supported->end())
+            {
+                mode = "Acoustic";
+            }
+        }
+        if (mode.empty())
+        {
+            return IPMI_CC_INVALID_FIELD_REQUEST;
+        }
+        setDbusProperty(dbus, settingsBusName, thermalModePath,
+                        thermalModeInterface, "Current", mode);
+    }
+
+    return IPMI_CC_OK;
+}
+
+ipmi_ret_t ipmiOEMGetFanConfig(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                               ipmi_request_t request, ipmi_response_t response,
+                               ipmi_data_len_t dataLen, ipmi_context_t context)
+{
+
+    if (*dataLen > 1)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmiOEMGetFanConfig: invalid input len!");
+        *dataLen = 0;
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    // todo: talk to bios about needing less information
+
+    GetFanConfigResp* resp = reinterpret_cast<GetFanConfigResp*>(response);
+    *dataLen = sizeof(GetFanConfigResp);
+
+    boost::container::flat_map<
+        std::string, std::variant<std::vector<std::string>, std::string>>
+        profileData;
+
+    if (!getFanProfileInterface(dbus, profileData))
+    {
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    std::string* current = std::get_if<std::string>(&profileData["Current"]);
+
+    if (current == nullptr)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmiOEMGetFanConfig: can't get current mode!");
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+    bool performance = (*current == "Performance");
+
+    if (performance)
+    {
+        resp->flags |= 1 << 2;
+    }
+
+    return IPMI_CC_OK;
+}
+
 static void registerOEMFunctions(void)
 {
     phosphor::logging::log<phosphor::logging::level::INFO>(
@@ -766,6 +908,17 @@
                          static_cast<ipmi_cmd_t>(
                              IPMINetfnIntelOEMGeneralCmd::cmdGetShutdownPolicy),
                          NULL, ipmiOEMGetShutdownPolicy, PRIVILEGE_ADMIN);
+
+    ipmiPrintAndRegister(
+        netfnIntcOEMGeneral,
+        static_cast<ipmi_cmd_t>(IPMINetfnIntelOEMGeneralCmd::cmdSetFanConfig),
+        NULL, ipmiOEMSetFanConfig, PRIVILEGE_USER);
+
+    ipmiPrintAndRegister(
+        netfnIntcOEMGeneral,
+        static_cast<ipmi_cmd_t>(IPMINetfnIntelOEMGeneralCmd::cmdGetFanConfig),
+        NULL, ipmiOEMGetFanConfig, PRIVILEGE_USER);
+
     ipmiPrintAndRegister(
         netfnIntcOEMGeneral,
         static_cast<ipmi_cmd_t>(IPMINetfnIntelOEMGeneralCmd::cmdGetLEDStatus),
diff --git a/src/utils.cpp b/src/utils.cpp
index 8dac5ab..7619186 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -1,9 +1,8 @@
-#include "phosphor-ipmi-host/utils.hpp"
-
 #include <arpa/inet.h>
 #include <dirent.h>
 #include <net/if.h>
 
+#include <phosphor-ipmi-host/utils.hpp>
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>