Implement get/set platform serial port parameters command

This command is used for BIOS to set/get host serial host configuration.

Tested:
set config:
ipmitool raw 0x32 0x90 1 0
get config:
ipmitool raw 0x32 0x90 0

Change-Id: I97feebbfd88033a186e19e6f53ab92a240902603
Signed-off-by: Yong Li <yong.b.li@linux.intel.com>
diff --git a/include/oemcommands.hpp b/include/oemcommands.hpp
index 01dca7d..a348db2 100644
--- a/include/oemcommands.hpp
+++ b/include/oemcommands.hpp
@@ -32,6 +32,11 @@
     cmdGetLEDStatus = 0xB0,
 };
 
+enum class IPMINetfnIntelOEMPlatformCmd
+{
+    cmdCfgHostSerialPortSpeed = 0x90,
+};
+
 enum class IPMIIntelOEMReturnCodes
 {
     ipmiCCPayloadActive = 0x80,
@@ -75,6 +80,9 @@
 static constexpr ipmi_netfn_t netfnIntcOEMGeneral =
     NETFUN_NONE; // Netfun_none. In our platform, we use it as "intel oem
                  // general". The code is 0x30
+
+// Intel OEM Platform code is 0x32
+static constexpr ipmi_netfn_t netfnIntcOEMPlatform = NETFUN_OEM;
 static constexpr const uint8_t maxBIOSIDLength = 0xFF;
 static constexpr const uint8_t maxCPUNum = 4;
 static constexpr const char* biosObjPath = "/xyz/openbmc_project/bios";
@@ -115,6 +123,20 @@
     "/xyz/openbmc_project/control/shutdown_policy_config";
 static constexpr const char* oemShutdownPolicyObjPathProp = "Policy";
 
+static constexpr const char* fwGetEnvCmd = "/sbin/fw_printenv";
+static constexpr const char* fwSetEnvCmd = "/sbin/fw_setenv";
+static constexpr const char* fwHostSerailCfgEnvName = "hostserialcfg";
+
+static constexpr const uint8_t getHostSerialCfgCmd = 0;
+static constexpr const uint8_t setHostSerialCfgCmd = 1;
+
+// parameters:
+// 0: host serial port 1 and 2 normal speed
+// 1: host serial port 1 high spend, port 2 normal speed
+// 2: host serial port 1 normal spend, port 2 high speed
+// 3: host serial port 1 and 2 high speed
+static constexpr const uint8_t HostSerialCfgParamMax = 3;
+
 enum class IPMINetfnIntelOEMAppCmd
 {
     mdrStatus = 0x20,
@@ -242,4 +264,9 @@
     uint8_t policySupport;
 };
 
+struct CfgHostSerialReq
+{
+    uint8_t command;
+    uint8_t parameter;
+};
 #pragma pack(pop)
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index 724d8e5..5791b28 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -20,6 +20,8 @@
 #include <ipmid/api.h>
 
 #include <array>
+#include <boost/process/child.hpp>
+#include <boost/process/io.hpp>
 #include <commandutils.hpp>
 #include <iostream>
 #include <oemcommands.hpp>
@@ -573,6 +575,135 @@
     return IPMI_CC_OK;
 }
 
+ipmi_ret_t ipmiOEMCfgHostSerialPortSpeed(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)
+{
+    CfgHostSerialReq* req = reinterpret_cast<CfgHostSerialReq*>(request);
+    uint8_t* resp = reinterpret_cast<uint8_t*>(response);
+
+    if (*dataLen == 0)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "CfgHostSerial: invalid input len!",
+            phosphor::logging::entry("LEN=%d", *dataLen));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    switch (req->command)
+    {
+        case getHostSerialCfgCmd:
+        {
+            if (*dataLen != 1)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "CfgHostSerial: invalid input len!");
+                *dataLen = 0;
+                return IPMI_CC_REQ_DATA_LEN_INVALID;
+            }
+
+            *dataLen = 0;
+
+            boost::process::ipstream is;
+            std::vector<std::string> data;
+            std::string line;
+            boost::process::child c1(fwGetEnvCmd, "-n", fwHostSerailCfgEnvName,
+                                     boost::process::std_out > is);
+
+            while (c1.running() && std::getline(is, line) && !line.empty())
+            {
+                data.push_back(line);
+            }
+
+            c1.wait();
+            if (c1.exit_code())
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "CfgHostSerial:: error on execute",
+                    phosphor::logging::entry("EXECUTE=%s", fwSetEnvCmd));
+                // Using the default value
+                *resp = 0;
+            }
+            else
+            {
+                if (data.size() != 1)
+                {
+                    phosphor::logging::log<phosphor::logging::level::ERR>(
+                        "CfgHostSerial:: error on read env");
+                    return IPMI_CC_UNSPECIFIED_ERROR;
+                }
+                try
+                {
+                    unsigned long tmp = std::stoul(data[0]);
+                    if (tmp > std::numeric_limits<uint8_t>::max())
+                    {
+                        throw std::out_of_range("Out of range");
+                    }
+                    *resp = static_cast<uint8_t>(tmp);
+                }
+                catch (const std::invalid_argument& e)
+                {
+                    phosphor::logging::log<phosphor::logging::level::ERR>(
+                        "invalid config ",
+                        phosphor::logging::entry("ERR=%s", e.what()));
+                    return IPMI_CC_UNSPECIFIED_ERROR;
+                }
+                catch (const std::out_of_range& e)
+                {
+                    phosphor::logging::log<phosphor::logging::level::ERR>(
+                        "out_of_range config ",
+                        phosphor::logging::entry("ERR=%s", e.what()));
+                    return IPMI_CC_UNSPECIFIED_ERROR;
+                }
+            }
+
+            *dataLen = 1;
+            break;
+        }
+        case setHostSerialCfgCmd:
+        {
+            if (*dataLen != sizeof(CfgHostSerialReq))
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "CfgHostSerial: invalid input len!");
+                *dataLen = 0;
+                return IPMI_CC_REQ_DATA_LEN_INVALID;
+            }
+
+            *dataLen = 0;
+
+            if (req->parameter > HostSerialCfgParamMax)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "CfgHostSerial: invalid input!");
+                return IPMI_CC_INVALID_FIELD_REQUEST;
+            }
+
+            boost::process::child c1(fwSetEnvCmd, fwHostSerailCfgEnvName,
+                                     std::to_string(req->parameter));
+
+            c1.wait();
+            if (c1.exit_code())
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "CfgHostSerial:: error on execute",
+                    phosphor::logging::entry("EXECUTE=%s", fwGetEnvCmd));
+                return IPMI_CC_UNSPECIFIED_ERROR;
+            }
+            break;
+        }
+        default:
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "CfgHostSerial: invalid input!");
+            *dataLen = 0;
+            return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+
+    return IPMI_CC_OK;
+}
+
 static void registerOEMFunctions(void)
 {
     phosphor::logging::log<phosphor::logging::level::INFO>(
@@ -639,6 +770,11 @@
         netfnIntcOEMGeneral,
         static_cast<ipmi_cmd_t>(IPMINetfnIntelOEMGeneralCmd::cmdGetLEDStatus),
         NULL, ipmiOEMGetLEDStatus, PRIVILEGE_ADMIN);
+    ipmiPrintAndRegister(
+        netfnIntcOEMPlatform,
+        static_cast<ipmi_cmd_t>(
+            IPMINetfnIntelOEMPlatformCmd::cmdCfgHostSerialPortSpeed),
+        NULL, ipmiOEMCfgHostSerialPortSpeed, PRIVILEGE_ADMIN);
     return;
 }