Add support for sending NCSI command
Provide a means to send an OEM command to an NIC via NCSI netlink.
This may be invoked from a systemd Unit file to configure NIC
behavior.
Some NICs provide OEM commands to influence their behavior, for
example maintaining full speed even when the host is down instead
of negotiating a lower speed for power.
Signed-off-by: Eddie James <eajames@linux.ibm.com>
Change-Id: Id920b618422e8fbfc51984fbf932045bfb5e56e6
diff --git a/src/ncsi_util.cpp b/src/ncsi_util.cpp
index 0436987..ec6074f 100644
--- a/src/ncsi_util.cpp
+++ b/src/ncsi_util.cpp
@@ -1,10 +1,12 @@
#include "ncsi_util.hpp"
+#include <fmt/format.h>
#include <linux/ncsi.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/netlink.h>
+#include <iomanip>
#include <iostream>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/log.hpp>
@@ -25,10 +27,44 @@
namespace internal
{
+struct NCSIPacketHeader
+{
+ uint8_t MCID;
+ uint8_t revision;
+ uint8_t reserved;
+ uint8_t id;
+ uint8_t type;
+ uint8_t channel;
+ uint16_t length;
+ uint32_t rsvd[2];
+};
+
+class Command
+{
+ public:
+ Command() = delete;
+ ~Command() = default;
+ Command(const Command&) = delete;
+ Command& operator=(const Command&) = delete;
+ Command(Command&&) = default;
+ Command& operator=(Command&&) = default;
+ Command(
+ int c, int nc = DEFAULT_VALUE,
+ std::span<const unsigned char> p = std::span<const unsigned char>()) :
+ cmd(c),
+ ncsi_cmd(nc), payload(p)
+ {
+ }
+
+ int cmd;
+ int ncsi_cmd;
+ std::span<const unsigned char> payload;
+};
+
using nlMsgPtr = std::unique_ptr<nl_msg, decltype(&::nlmsg_free)>;
using nlSocketPtr = std::unique_ptr<nl_sock, decltype(&::nl_socket_free)>;
-CallBack infoCallBack = [](struct nl_msg* msg, void* /*arg*/) {
+CallBack infoCallBack = [](struct nl_msg* msg, void* arg) {
using namespace phosphor::network::ncsi;
auto nlh = nlmsg_hdr(msg);
@@ -50,6 +86,8 @@
{NLA_FLAG, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_UNSPEC, 0, 0},
};
+ *(int*)arg = 0;
+
auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
if (!tb[NCSI_ATTR_PACKAGE_LIST])
{
@@ -175,10 +213,43 @@
return (int)NL_SKIP;
};
-int applyCmd(int ifindex, int cmd, int package = DEFAULT_VALUE,
+CallBack sendCallBack = [](struct nl_msg* msg, void* arg) {
+ using namespace phosphor::network::ncsi;
+ auto nlh = nlmsg_hdr(msg);
+ struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
+ static struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
+ {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
+ {NLA_U32, 0, 0}, {NLA_U32, 0, 0}, {NLA_BINARY, 0, 0},
+ {NLA_FLAG, 0, 0}, {NLA_U32, 0, 0}, {NLA_U32, 0, 0},
+ };
+
+ *(int*)arg = 0;
+
+ auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
+ if (ret)
+ {
+ std::cerr << "Failed to parse package" << std::endl;
+ return ret;
+ }
+
+ auto data_len = nla_len(tb[NCSI_ATTR_DATA]) - sizeof(NCSIPacketHeader);
+ unsigned char* data =
+ (unsigned char*)nla_data(tb[NCSI_ATTR_DATA]) + sizeof(NCSIPacketHeader);
+ auto s = std::span<const unsigned char>(data, data_len);
+
+ // Dump the response to stdout. Enhancement: option to save response data
+ std::cout << "Response : " << std::dec << data_len << " bytes" << std::endl;
+ fmt::print("{:02x}", fmt::join(s.begin(), s.end(), " "));
+ std::cout << std::endl;
+
+ return 0;
+};
+
+int applyCmd(int ifindex, const Command& cmd, int package = DEFAULT_VALUE,
int channel = DEFAULT_VALUE, int flags = NONE,
CallBack function = nullptr)
{
+ int cb_ret = 0;
nlSocketPtr socket(nl_socket_alloc(), &::nl_socket_free);
if (socket == nullptr)
{
@@ -207,10 +278,10 @@
return -ENOMEM;
}
- auto msgHdr = genlmsg_put(msg.get(), 0, 0, driverID, 0, flags, cmd, 0);
+ auto msgHdr = genlmsg_put(msg.get(), 0, 0, driverID, 0, flags, cmd.cmd, 0);
if (!msgHdr)
{
- std::cerr << "Unable to add the netlink headers , COMMAND : " << cmd
+ std::cerr << "Unable to add the netlink headers , COMMAND : " << cmd.cmd
<< std::endl;
return -ENOMEM;
}
@@ -247,11 +318,37 @@
return ret;
}
+ if (cmd.ncsi_cmd != DEFAULT_VALUE)
+ {
+ std::vector<unsigned char> pl(sizeof(NCSIPacketHeader) +
+ cmd.payload.size());
+ NCSIPacketHeader* hdr = (NCSIPacketHeader*)pl.data();
+
+ std::copy(cmd.payload.begin(), cmd.payload.end(),
+ pl.begin() + sizeof(NCSIPacketHeader));
+
+ hdr->type = cmd.ncsi_cmd;
+ hdr->length = htons(cmd.payload.size());
+
+ ret = nla_put(msg.get(), ncsi_nl_attrs::NCSI_ATTR_DATA, pl.size(),
+ pl.data());
+ if (ret < 0)
+ {
+ std::cerr << "Failed to set the data attribute, RC : " << ret
+ << std::endl;
+ return ret;
+ }
+
+ nl_socket_disable_seq_check(socket.get());
+ }
+
if (function)
{
+ cb_ret = 1;
+
// Add a callback function to the socket
nl_socket_modify_cb(socket.get(), NL_CB_VALID, NL_CB_CUSTOM, function,
- nullptr);
+ &cb_ret);
}
ret = nl_send_auto(socket.get(), msg.get());
@@ -261,32 +358,63 @@
return ret;
}
- ret = nl_recvmsgs_default(socket.get());
- if (ret < 0)
+ do
{
- std::cerr << "Failed to receive the message , RC : " << ret
- << std::endl;
- }
+ ret = nl_recvmsgs_default(socket.get());
+ if (ret < 0)
+ {
+ std::cerr << "Failed to receive the message , RC : " << ret
+ << std::endl;
+ break;
+ }
+ } while (cb_ret);
+
return ret;
}
} // namespace internal
+int sendOemCommand(int ifindex, int package, int channel,
+ std::span<const unsigned char> payload)
+{
+ constexpr auto cmd = 0x50;
+
+ std::cout << "Send OEM Command, CHANNEL : " << std::hex << channel
+ << ", PACKAGE : " << std::hex << package
+ << ", IFINDEX: " << std::hex << ifindex << std::endl;
+ if (!payload.empty())
+ {
+ std::cout << "Payload :";
+ for (auto& i : payload)
+ {
+ std::cout << " " << std::hex << std::setfill('0') << std::setw(2)
+ << (int)i;
+ }
+ std::cout << std::endl;
+ }
+
+ return internal::applyCmd(
+ ifindex,
+ internal::Command(ncsi_nl_commands::NCSI_CMD_SEND_CMD, cmd, payload),
+ package, channel, NONE, internal::sendCallBack);
+}
+
int setChannel(int ifindex, int package, int channel)
{
std::cout << "Set Channel : " << std::hex << channel
<< ", PACKAGE : " << std::hex << package
<< ", IFINDEX : " << std::hex << ifindex << std::endl;
- return internal::applyCmd(ifindex, ncsi_nl_commands::NCSI_CMD_SET_INTERFACE,
- package, channel);
+ return internal::applyCmd(
+ ifindex, internal::Command(ncsi_nl_commands::NCSI_CMD_SET_INTERFACE),
+ package, channel);
}
int clearInterface(int ifindex)
{
std::cout << "ClearInterface , IFINDEX :" << std::hex << ifindex
<< std::endl;
- return internal::applyCmd(ifindex,
- ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE);
+ return internal::applyCmd(
+ ifindex, internal::Command(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE));
}
int getInfo(int ifindex, int package)
@@ -295,9 +423,9 @@
<< ", IFINDEX : " << std::hex << ifindex << std::endl;
if (package == DEFAULT_VALUE)
{
- return internal::applyCmd(ifindex, ncsi_nl_commands::NCSI_CMD_PKG_INFO,
- package, DEFAULT_VALUE, NLM_F_DUMP,
- internal::infoCallBack);
+ return internal::applyCmd(
+ ifindex, internal::Command(ncsi_nl_commands::NCSI_CMD_PKG_INFO),
+ package, DEFAULT_VALUE, NLM_F_DUMP, internal::infoCallBack);
}
else
{