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