ncsi: Add a NCSI-over-MCTP transport

Add a facility for performing NCSI commands over a NCSI-over-MCTP
interface, alongside the existing Netlink transport.

This adds a new Interface subclass, MCTPInterface, which performs the
MCTP encapsulation/decapsulation, over an AF_MCTP socket.

Tested: able to perform NCSI commands over a MCTP link, to both emulated
and hardware NIC devices. The -m argument can now target a NIC using
MCTP.

For example, sending a raw command to perform a Get Version ID (type
0x15):

    root@bmc:~# ncsi-cmd -m 9 --package 0 raw 0x15
    <7> Command: type 0x15, payload 0 bytes:
    <7> Response 60 bytes: 00 01 00 2b 95 [...]

Change-Id: I9a7bfddfc4fd1b5bb8d0bff187936a0258d3dade
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
diff --git a/src/ncsi_util.cpp b/src/ncsi_util.cpp
index c9ac7b5..1f505f0 100644
--- a/src/ncsi_util.cpp
+++ b/src/ncsi_util.cpp
@@ -1,14 +1,17 @@
 #include "ncsi_util.hpp"
 
+#include <linux/mctp.h>
 #include <linux/ncsi.h>
 #include <netlink/genl/ctrl.h>
 #include <netlink/genl/genl.h>
 #include <netlink/netlink.h>
+#include <unistd.h>
 
 #include <phosphor-logging/lg2.hpp>
 
 #include <optional>
 #include <span>
+#include <system_error>
 #include <vector>
 
 namespace phosphor
@@ -563,6 +566,184 @@
     return 0;
 }
 
+static const uint8_t MCTP_TYPE_NCSI = 2;
+
+struct NCSIResponsePayload
+{
+    uint16_t response;
+    uint16_t reason;
+};
+
+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;
+    ssize_t wlen, rlen;
+
+    payloadLen = cmd.payload.size();
+
+    internal::NCSIPacketHeader cmdHeader{};
+    cmdHeader.MCID = mcid;
+    cmdHeader.revision = 1;
+    cmdHeader.id = iid;
+    cmdHeader.type = cmd.opcode;
+    cmdHeader.channel = (uint8_t)(cmd.package << 5 | cmd.getChannel());
+    cmdHeader.length = htons(payloadLen);
+
+    struct iovec iov[3];
+    iov[0].iov_base = &cmdHeader;
+    iov[0].iov_len = sizeof(cmdHeader);
+    iov[1].iov_base = cmd.payload.data();
+    iov[1].iov_len = payloadLen;
+
+    /* the checksum must appear on a 4-byte boundary */
+    padLen = 4 - (payloadLen & 0x3);
+    if (padLen == 4)
+    {
+        padLen = 0;
+    }
+    uint8_t crc32buf[8] = {};
+    /* todo: set csum; zeros currently indicate no checksum present */
+    uint32_t crc32 = 0;
+
+    memcpy(crc32buf + padLen, &crc32, sizeof(crc32));
+    padLen += sizeof(crc32);
+
+    iov[2].iov_base = crc32buf;
+    iov[2].iov_len = padLen;
+
+    struct sockaddr_mctp addr = {};
+    addr.smctp_family = AF_MCTP;
+    addr.smctp_network = net;
+    addr.smctp_addr.s_addr = eid;
+    addr.smctp_tag = MCTP_TAG_OWNER;
+    addr.smctp_type = MCTP_TYPE_NCSI;
+
+    struct msghdr msg = {};
+    msg.msg_name = &addr;
+    msg.msg_namelen = sizeof(addr);
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 3;
+
+    wlen = sendmsg(sd, &msg, 0);
+    if (wlen < 0)
+    {
+        lg2::error("Failed to send MCTP message, ERRNO: {ERRNO}", "ERRNO",
+                   -wlen);
+        return {};
+    }
+    else if ((size_t)wlen != sizeof(cmdHeader) + payloadLen + padLen)
+    {
+        lg2::error("Short write sending MCTP message, LEN: {LEN}", "LEN", wlen);
+        return {};
+    }
+
+    internal::NCSIPacketHeader* respHeader;
+    NCSIResponsePayload* respPayload;
+    NCSIResponse resp{};
+
+    resp.full_payload.resize(maxRespLen);
+    iov[0].iov_len = resp.full_payload.size();
+    iov[0].iov_base = resp.full_payload.data();
+
+    msg.msg_name = &addr;
+    msg.msg_namelen = sizeof(addr);
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+
+    /* we have set SO_RCVTIMEO, so this won't block forever... */
+    rlen = recvmsg(sd, &msg, MSG_TRUNC);
+    if (rlen < 0)
+    {
+        lg2::error("Failed to read MCTP response, ERRNO: {ERRNO}", "ERRNO",
+                   -rlen);
+        return {};
+    }
+    else if ((size_t)rlen < sizeof(*respHeader) + sizeof(*respPayload))
+    {
+        lg2::error("Short read receiving MCTP message, LEN: {LEN}", "LEN",
+                   rlen);
+        return {};
+    }
+    else if ((size_t)rlen > maxRespLen)
+    {
+        lg2::error("MCTP response is too large, LEN: {LEN}", "LEN", rlen);
+        return {};
+    }
+
+    resp.full_payload.resize(rlen);
+
+    respHeader =
+        reinterpret_cast<decltype(respHeader)>(resp.full_payload.data());
+
+    /* header validation */
+    if (respHeader->MCID != mcid)
+    {
+        lg2::error("Invalid MCID {MCID} in response", "MCID", lg2::hex,
+                   respHeader->MCID);
+        return {};
+    }
+
+    if (respHeader->id != iid)
+    {
+        lg2::error("Invalid IID {IID} in response", "IID", lg2::hex,
+                   respHeader->id);
+        return {};
+    }
+
+    if (respHeader->type != (cmd.opcode | 0x80))
+    {
+        lg2::error("Invalid opcode {OPCODE} in response", "OPCODE", lg2::hex,
+                   respHeader->type);
+        return {};
+    }
+
+    int rc = resp.parseFullPayload();
+    if (rc)
+    {
+        return {};
+    }
+
+    return resp;
+}
+
+std::string MCTPInterface::toString()
+{
+    return std::to_string(net) + "," + std::to_string(eid);
+}
+
+MCTPInterface::MCTPInterface(int net, uint8_t eid) : net(net), eid(eid)
+{
+    static const struct timeval receiveTimeout = {
+        .tv_sec = 1,
+        .tv_usec = 0,
+    };
+
+    int _sd = socket(AF_MCTP, SOCK_DGRAM, 0);
+    if (_sd < 0)
+    {
+        throw std::system_error(errno, std::system_category(),
+                                "Can't create MCTP socket");
+    }
+
+    int rc = setsockopt(_sd, SOL_SOCKET, SO_RCVTIMEO, &receiveTimeout,
+                        sizeof(receiveTimeout));
+    if (rc != 0)
+    {
+        throw std::system_error(errno, std::system_category(),
+                                "Can't set socket receive timemout");
+    }
+
+    sd = _sd;
+}
+
+MCTPInterface::~MCTPInterface()
+{
+    close(sd);
+}
+
 } // namespace ncsi
 } // namespace network
 } // namespace phosphor