add "set system info" command

Set/get system info commands are pair commands that customer could
use to save/restore system specified infomation like machine
serial numbner/OS version/.. etc.  One typical scenario is that
customer could check current OS version even when tareget system is
powered off. Current openbmc implements "Get system info" but
"Set system info" is not available. This changes implements
"Set system info" command.

Tested:
:~# ipmitool raw 0x6 0x58 0x1 0x0 0x0 0x2 0x30 0x31

:~# ipmitool raw 0x6 0x59 0x0 0x1 0x0 0x0
 11 00 00 02 30 31
:~# ipmitool raw 0x6 0x59 0x0 0x1 0x1 0x0
 Unable to send RAW command (channel=0x0 netfn=0x6 lun=0x0
 cmd=0x59 rsp=0xc9): Parameter out of range

Change-Id: Iaa66e30bcec708e28a01c2e81d612e6ffdd36383
Signed-off-by: Jia, chunhui <chunhui.jia@linux.intel.com>
diff --git a/apphandler.cpp b/apphandler.cpp
index e06a999..f187d95 100644
--- a/apphandler.cpp
+++ b/apphandler.cpp
@@ -968,26 +968,41 @@
 
 static constexpr size_t smallChunkSize = 14;
 static constexpr size_t fullChunkSize = 16;
+static constexpr uint8_t progressMask = 0x3;
 
 static constexpr uint8_t setComplete = 0x0;
 static constexpr uint8_t setInProgress = 0x1;
 static constexpr uint8_t commitWrite = 0x2;
 static uint8_t transferStatus = setComplete;
 
+static constexpr uint8_t configDataOverhead = 2;
+
+// For EFI based system, 256 bytes is recommended.
+static constexpr size_t maxBytesPerParameter = 256;
+
 namespace ipmi
 {
 constexpr Cc ccParmNotSupported = 0x80;
+constexpr Cc ccSetInProgressActive = 0x81;
+constexpr Cc ccSystemInfoParameterSetReadOnly = 0x82;
 
 static inline auto responseParmNotSupported()
 {
     return response(ccParmNotSupported);
 }
+static inline auto responseSetInProgressActive()
+{
+    return response(ccSetInProgressActive);
+}
+static inline auto responseSystemInfoParameterSetReadOnly()
+{
+    return response(ccSystemInfoParameterSetReadOnly);
+}
 } // namespace ipmi
 
-ipmi::RspType<
-    uint8_t,                // Parameter revision
-    std::optional<uint8_t>, // data1 / setSelector / ProgressStatus
-    std::optional<std::array<uint8_t, configParameterLength>>> // data2-17
+ipmi::RspType<uint8_t,                // Parameter revision
+              std::optional<uint8_t>, // data1 / setSelector / ProgressStatus
+              std::optional<std::vector<uint8_t>>> // data2-17
     ipmiAppGetSystemInfo(uint8_t getRevision, uint8_t paramSelector,
                          uint8_t setSelector, uint8_t BlockSelector)
 {
@@ -1023,33 +1038,107 @@
         return ipmi::responseParmNotSupported();
     }
     std::string& paramString = std::get<1>(ret);
-    std::array<uint8_t, configParameterLength> configData;
+    std::vector<uint8_t> configData;
     size_t count = 0;
     if (setSelector == 0)
-    {                         // First chunk has only 14 bytes.
-        configData.at(0) = 0; // encoding
-        configData.at(1) = paramString.length(); // string length
-        count = (paramString.length() > smallChunkSize) ? smallChunkSize
-                                                        : paramString.length();
+    {                               // First chunk has only 14 bytes.
+        configData.emplace_back(0); // encoding
+        configData.emplace_back(paramString.length()); // string length
+        count = std::min(paramString.length(), smallChunkSize);
+        configData.resize(count + configDataOverhead);
         std::copy_n(paramString.begin(), count,
-                    configData.begin() + 2); // 14 bytes thunk
+                    configData.begin() + configDataOverhead); // 14 bytes thunk
     }
     else
     {
-        size_t offset = (setSelector * fullChunkSize) - 2;
+        size_t offset = (setSelector * fullChunkSize) - configDataOverhead;
         if (offset >= paramString.length())
         {
             return ipmi::responseParmOutOfRange();
         }
-        count = ((paramString.length() - offset) > fullChunkSize)
-                    ? fullChunkSize
-                    : (paramString.length() - offset);
+        count = std::min(paramString.length() - offset, fullChunkSize);
+        configData.resize(count);
         std::copy_n(paramString.begin() + offset, count,
                     configData.begin()); // 16 bytes chunk
     }
     return ipmi::responseSuccess(paramRevision, setSelector, configData);
 }
 
+ipmi::RspType<> ipmiAppSetSystemInfo(uint8_t paramSelector, uint8_t data1,
+                                     std::vector<uint8_t> configData)
+{
+    if (paramSelector == 0)
+    {
+        // attempt to set the 'set in progress' value (in parameter #0)
+        // when not in the set complete state.
+        if ((transferStatus != setComplete) && (data1 == setInProgress))
+        {
+            return ipmi::responseSetInProgressActive();
+        }
+        // only following 2 states are supported
+        if (data1 > setInProgress)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "illegal SetInProgress status");
+            return ipmi::responseInvalidFieldRequest();
+        }
+
+        transferStatus = data1 & progressMask;
+        return ipmi::responseSuccess();
+    }
+
+    if (configData.size() > configParameterLength)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (!sysInfoParamStore)
+    {
+        sysInfoParamStore = std::make_unique<SysInfoParamStore>();
+        sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_NAME,
+                                  sysInfoReadSystemName);
+    }
+
+    // lookup
+    std::tuple<bool, std::string> ret =
+        sysInfoParamStore->lookup(paramSelector);
+    bool found = std::get<0>(ret);
+    std::string& paramString = std::get<1>(ret);
+    if (!found)
+    {
+        // parameter does not exist. Init new
+        paramString = "";
+    }
+
+    uint8_t setSelector = data1;
+    size_t count = 0;
+    if (setSelector == 0) // First chunk has only 14 bytes.
+    {
+        size_t stringLen = configData.at(1); // string length
+        // maxBytesPerParamter is 256. It will always be greater than stringLen
+        // (unit8_t) if maxBytes changes in future, then following line is
+        // needed.
+        // stringLen = std::min(stringLen, maxBytesPerParameter);
+        count = std::min(stringLen, smallChunkSize);
+        count = std::min(count, configData.size());
+        paramString.resize(stringLen); // reserve space
+        std::copy_n(configData.begin() + configDataOverhead, count,
+                    paramString.begin());
+    }
+    else
+    {
+        size_t offset = (setSelector * fullChunkSize) - configDataOverhead;
+        if (offset >= paramString.length())
+        {
+            return ipmi::responseParmOutOfRange();
+        }
+        count = std::min(paramString.length() - offset, configData.size());
+        std::copy_n(configData.begin(), count, paramString.begin() + offset);
+    }
+    sysInfoParamStore->update(paramSelector, paramString);
+    return ipmi::responseSuccess();
+}
+
 #ifdef ENABLE_I2C_WHITELIST_CHECK
 inline std::vector<uint8_t> convertStringToData(const std::string& command)
 {
@@ -1336,5 +1425,9 @@
     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
                           ipmi::app::cmdGetSystemInfoParameters,
                           ipmi::Privilege::User, ipmiAppGetSystemInfo);
+    // <Set System Info Command>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetSystemInfoParameters,
+                          ipmi::Privilege::Admin, ipmiAppSetSystemInfo);
     return;
 }