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/argument.cpp b/src/argument.cpp
index bcd5a13..0c49b11 100644
--- a/src/argument.cpp
+++ b/src/argument.cpp
@@ -72,6 +72,8 @@
std::cerr << " --set=<set> Set a specific package/channel.\n";
std::cerr
<< " --clear=<clear> Clear all the settings on the interface.\n";
+ std::cerr
+ << " --oem-payload=<hex data> Send an OEM command with payload.\n";
std::cerr << " --package=<package> Specify a package.\n";
std::cerr << " --channel=<channel> Specify a channel.\n";
std::cerr << " --index=<device index> Specify device ifindex.\n";
@@ -82,6 +84,7 @@
{"info", no_argument, NULL, 'i'},
{"set", no_argument, NULL, 's'},
{"clear", no_argument, NULL, 'r'},
+ {"oem-payload", required_argument, NULL, 'o'},
{"package", required_argument, NULL, 'p'},
{"channel", required_argument, NULL, 'c'},
{"index", required_argument, NULL, 'x'},
@@ -89,7 +92,7 @@
{0, 0, 0, 0},
};
-const char* ArgumentParser::optionStr = "i:s:r:p:c:x:h?";
+const char* ArgumentParser::optionStr = "i:s:r:o:p:c:x:h?";
const std::string ArgumentParser::trueString = "true";
const std::string ArgumentParser::emptyString = "";
diff --git a/src/meson.build b/src/meson.build
index 283995a..a2c3758 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,6 +12,7 @@
implicit_include_directories: false,
include_directories: src_includes,
dependencies: [
+ dependency('fmt'),
dependency('libnl-3.0'),
dependency('libnl-genl-3.0'),
phosphor_dbus_interfaces_dep,
diff --git a/src/ncsi_netlink_main.cpp b/src/ncsi_netlink_main.cpp
index 9db624f..12d59be 100644
--- a/src/ncsi_netlink_main.cpp
+++ b/src/ncsi_netlink_main.cpp
@@ -18,6 +18,7 @@
#include <iostream>
#include <string>
+#include <vector>
static void exitWithError(const char* err, char** argv)
{
@@ -85,8 +86,47 @@
channelInt = DEFAULT_VALUE;
}
- auto setCmd = (options)["set"];
- if (setCmd == "true")
+ auto payloadStr = (options)["oem-payload"];
+ if (!payloadStr.empty())
+ {
+ std::string byte(2, '\0');
+ std::vector<unsigned char> payload;
+
+ if (payloadStr.size() % 2)
+ exitWithError("Payload invalid: specify two hex digits per byte.",
+ argv);
+
+ // Parse the payload string (e.g. "000001572100") to byte data
+ for (unsigned int i = 1; i < payloadStr.size(); i += 2)
+ {
+ byte[0] = payloadStr[i - 1];
+ byte[1] = payloadStr[i];
+
+ try
+ {
+ payload.push_back(stoi(byte, nullptr, 16));
+ }
+ catch (const std::exception& e)
+ {
+ exitWithError("Payload invalid.", argv);
+ }
+ }
+
+ if (payload.empty())
+ {
+ exitWithError("No payload specified.", argv);
+ }
+
+ if (packageInt == DEFAULT_VALUE)
+ {
+ exitWithError("Package not specified.", argv);
+ }
+
+ return ncsi::sendOemCommand(
+ indexInt, packageInt, channelInt,
+ std::span<const unsigned char>(payload.begin(), payload.end()));
+ }
+ else if ((options)["set"] == "true")
{
// Can not perform set operation without package.
if (packageInt == DEFAULT_VALUE)
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
{
diff --git a/src/ncsi_util.hpp b/src/ncsi_util.hpp
index db754fe..eaa076d 100644
--- a/src/ncsi_util.hpp
+++ b/src/ncsi_util.hpp
@@ -1,3 +1,7 @@
+#pragma once
+
+#include <span>
+
namespace phosphor
{
namespace network
@@ -9,6 +13,20 @@
constexpr auto NONE = 0;
/* @brief This function will ask underlying NCSI driver
+ * to send an OEM command (command type 0x50) with
+ * the specified payload as the OEM data.
+ * This function talks with the NCSI driver over
+ * netlink messages.
+ * @param[in] ifindex - Interface Index.
+ * @param[in] package - NCSI Package.
+ * @param[in] channel - Channel number with in the package.
+ * @param[in] payload - OEM data to send.
+ * @returns 0 on success and negative value for failure.
+ */
+int sendOemCommand(int ifindex, int package, int channel,
+ std::span<const unsigned char> payload);
+
+/* @brief This function will ask underlying NCSI driver
* to set a specific package or package/channel
* combination as the preferred choice.
* This function talks with the NCSI driver over