ncsi-cmd: mctp: Add a simple IID allocator for NCSI commands

Currently, we just use a fixed IID of zero for NCSI commands. However,
DSP0222 has a requirement that the IID of a command should not match
that of a previously-issued command.

So, implement a straightforward per-EID IID allocator, which will
persist the current IID over invocations of ncsi-cmd. We keep state in
/run/ncsi-mctp-iids, with an IID byte per possible MCTP EID.

Tested: on first usage against a specific EID, commands are issued with
IID 1, and subsequent commands use an incremented IID.

Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
Change-Id: Iafc942abe6253a2560997ee5392a04af4412dc2e
diff --git a/src/ncsi_util.cpp b/src/ncsi_util.cpp
index 1f505f0..4def9fe 100644
--- a/src/ncsi_util.cpp
+++ b/src/ncsi_util.cpp
@@ -1,5 +1,7 @@
 #include "ncsi_util.hpp"
 
+#include <errno.h>
+#include <fcntl.h>
 #include <linux/mctp.h>
 #include <linux/ncsi.h>
 #include <netlink/genl/ctrl.h>
@@ -21,6 +23,8 @@
 namespace ncsi
 {
 
+static const char* mctp_iid_path = "/run/ncsi-mctp-iids";
+
 NCSICommand::NCSICommand(uint8_t opcode, uint8_t package,
                          std::optional<uint8_t> channel,
                          std::span<unsigned char> payload) :
@@ -576,7 +580,6 @@
 
 std::optional<NCSIResponse> MCTPInterface::sendCommand(NCSICommand& cmd)
 {
-    static constexpr uint8_t iid = 0;  /* we only have one cmd outstanding */
     static constexpr uint8_t mcid = 0; /* no need to distinguish controllers */
     static constexpr size_t maxRespLen = 16384;
     size_t payloadLen, padLen;
@@ -584,6 +587,13 @@
 
     payloadLen = cmd.payload.size();
 
+    auto tmp = allocateIID();
+    if (!tmp.has_value())
+    {
+        return {};
+    }
+    uint8_t iid = *tmp;
+
     internal::NCSIPacketHeader cmdHeader{};
     cmdHeader.MCID = mcid;
     cmdHeader.revision = 1;
@@ -744,6 +754,83 @@
     close(sd);
 }
 
+/* Small fd wrapper to provide RAII semantics, closing the IID file descriptor
+ * when we go out of scope.
+ */
+struct IidFd
+{
+    int fd;
+    IidFd(int _fd) : fd(_fd) {};
+    ~IidFd()
+    {
+        close(fd);
+    };
+};
+
+std::optional<uint8_t> MCTPInterface::allocateIID()
+{
+    int fd = open(mctp_iid_path, O_RDWR | O_CREAT, 0600);
+    if (fd < 0)
+    {
+        lg2::warning("Error opening IID database {FILE}: {ERROR}", "FILE",
+                     mctp_iid_path, "ERROR", strerror(errno));
+        return {};
+    }
+
+    IidFd iidFd(fd);
+
+    /* lock while we read/modity/write; the lock will be short-lived, so
+     * we keep it simple and lock the entire file range
+     */
+    struct flock flock = {
+        .l_type = F_WRLCK,
+        .l_whence = SEEK_SET,
+        .l_start = 0,
+        .l_len = 0,
+        .l_pid = 0,
+    };
+
+    int rc = fcntl(iidFd.fd, F_OFD_SETLKW, &flock);
+    if (rc)
+    {
+        lg2::warning("Error locking IID database {FILE}: {ERROR}", "FILE",
+                     mctp_iid_path, "ERROR", strerror(errno));
+        return {};
+    }
+
+    /* An EOF (rc == 0) would indicate that we don't yet have an entry for that
+     * eid, which we handle as iid = 0.
+     */
+    uint8_t iid = 0;
+    rc = pread(iidFd.fd, &iid, sizeof(iid), eid);
+    if (rc < 0)
+    {
+        lg2::warning("Error reading IID database {FILE}: {ERROR}", "FILE",
+                     mctp_iid_path, "ERROR", strerror(errno));
+        return {};
+    }
+
+    /* DSP0222 defines valid IIDs in the range [1, 0xff], so manually wrap */
+    if (iid == 0xff)
+    {
+        iid = 1;
+    }
+    else
+    {
+        iid++;
+    }
+
+    rc = pwrite(iidFd.fd, &iid, sizeof(iid), eid);
+    if (rc != sizeof(iid))
+    {
+        lg2::warning("Error writing IID database {FILE}: {ERROR}", "FILE",
+                     mctp_iid_path, "ERROR", strerror(errno));
+        return {};
+    }
+
+    return iid;
+}
+
 } // namespace ncsi
 } // namespace network
 } // namespace phosphor
diff --git a/src/ncsi_util.hpp b/src/ncsi_util.hpp
index 27397c8..4235429 100644
--- a/src/ncsi_util.hpp
+++ b/src/ncsi_util.hpp
@@ -165,6 +165,8 @@
     int sd;
     int net;
     uint8_t eid;
+
+    std::optional<uint8_t> allocateIID();
 };
 
 } // namespace ncsi