Implement Receive Data from NC Command (0x4D) for NC-SI
This commit implements the "Receive Data from NC" command
(0x4D) as specified in the NC-SI standard. It also
includes related unit tests to ensure functionality.
The dump subcommand has been enhanced to allow specifying
the data handle and output file name when sending the
command.
Testing:
./ncsi-cmd -m 91 -p 0 -c 1f core-dump mctp_core.log
./ncsi-cmd -i 2 -p 0 -c 1f core-dump rbt_core.log
Change-Id: I43a9e8ba08772f3edc65ed5efb5b83cb4ae52e03
Signed-off-by: eddy lu <puzzy8338@gmail.com>
diff --git a/src/ncsi_cmd.cpp b/src/ncsi_cmd.cpp
index 83acb85..ea0bc12 100644
--- a/src/ncsi_cmd.cpp
+++ b/src/ncsi_cmd.cpp
@@ -29,7 +29,9 @@
#include <stdplus/str/conv.hpp>
#include <climits>
+#include <fstream>
#include <iostream>
+#include <map>
#include <memory>
#include <optional>
#include <string_view>
@@ -37,10 +39,13 @@
using namespace phosphor::network::ncsi;
+const uint32_t NCSI_CORE_DUMP_HANDLE = 0xFFFF0000;
+const uint32_t NCSI_CRASH_DUMP_HANDLE = 0xFFFF0001;
+
struct GlobalOptions
{
std::unique_ptr<Interface> interface;
- unsigned int package;
+ std::optional<unsigned int> package;
std::optional<unsigned int> channel;
};
@@ -70,11 +75,14 @@
<< "Usage:\n"
" " << progname << " <options> raw TYPE [PAYLOAD...]\n"
" " << progname << " <options> oem [PAYLOAD...]\n"
+ " " << progname << " <options> core-dump FILE\n"
+ " " << progname << " <options> crash-dump FILE\n"
"\n"
"Global options:\n"
" --interface IFACE, -i Specify net device by ifindex.\n"
" --mctp [NET,]EID, -m Specify MCTP network device.\n"
- " --package PACKAGE, -p Specify a package.\n"
+ " --package PACKAGE, -p For non-discovery commands this is required; for discovery it is optional and\n"
+ " restricts the discovery to a specific package index.\n"
" --channel CHANNEL, -c Specify a channel.\n"
"\n"
"A --package/-p argument is required, as well as interface type "
@@ -89,7 +97,13 @@
"\n"
"oem PAYLOAD\n"
" Send a single OEM command (type 0x50).\n"
- " PAYLOAD Command payload bytes, as hex\n";
+ " PAYLOAD Command payload bytes, as hex\n"
+ "\n"
+ "core-dump FILE\n"
+ " Perform NCSI core dump and save log to FILE.\n"
+ "\n"
+ "crash-dump FILE\n"
+ " Perform NCSI crash dump and save log to FILE.\n";
// clang-format on
}
@@ -346,7 +360,9 @@
static int ncsiCommand(GlobalOptions& options, uint8_t type,
std::vector<unsigned char> payload)
{
- NCSICommand cmd(type, options.package, options.channel, payload);
+ NCSICommand cmd(type, static_cast<uint8_t>(*options.package),
+ options.channel,
+ std::span<unsigned char>(payload.data(), payload.size()));
lg2::debug("Command: type {TYPE}, payload {PAYLOAD_LEN} bytes: {PAYLOAD}",
"TYPE", lg2::hex, type, "PAYLOAD_LEN", payload.size(), "PAYLOAD",
@@ -421,6 +437,243 @@
return ncsiCommand(options, oemType, *payload);
}
+static std::array<unsigned char, 12>
+ generateDumpCmdPayload(uint32_t chunkNum, uint32_t dataHandle, bool isAbort)
+{
+ std::array<unsigned char, 12> payload = {};
+ uint8_t opcode;
+
+ if (isAbort)
+ {
+ opcode = 3;
+ }
+ else if (chunkNum == 1)
+ {
+ // For the first chunk the chunk number field carries the data handle.
+ opcode = 0;
+ chunkNum = dataHandle;
+ }
+ else
+ {
+ opcode = 2;
+ }
+ payload[3] = opcode;
+ payload[8] = (chunkNum >> 24) & 0xFF;
+ payload[9] = (chunkNum >> 16) & 0xFF;
+ payload[10] = (chunkNum >> 8) & 0xFF;
+ payload[11] = chunkNum & 0xFF;
+
+ return payload;
+}
+
+std::string getDescForResponse(uint16_t response)
+{
+ static const std::map<uint16_t, std::string> descMap = {
+ {0x0000, "Command Completed"},
+ {0x0001, "Command Failed"},
+ {0x0002, "Command Unavailable"},
+ {0x0003, "Command Unsupported"},
+ {0x0004, "Delayed Response"}};
+
+ try
+ {
+ return descMap.at(response);
+ }
+ catch (std::exception&)
+ {
+ return "Unknown response code: " + std::to_string(response);
+ }
+}
+
+std::string getDescForReason(uint16_t reason)
+{
+ static const std::map<uint16_t, std::string> reasonMap = {
+ {0x0001, "Interface Initialization Required"},
+ {0x0002, "Parameter Is Invalid, Unsupported, or Out-of-Range"},
+ {0x0003, "Channel Not Ready"},
+ {0x0004, "Package Not Ready"},
+ {0x0005, "Invalid Payload Length"},
+ {0x0006, "Information Not Available"},
+ {0x0007, "Intervention Required"},
+ {0x0008, "Link Command Failed - Hardware Access Error"},
+ {0x0009, "Command Timeout"},
+ {0x000A, "Secondary Device Not Powered"},
+ {0x7FFF, "Unknown/Unsupported Command Type"},
+ {0x4D01, "Abort Transfer: NC cannot proceed with transfer."},
+ {0x4D02,
+ "Invalid Handle Value: Data Handle is invalid or not supported."},
+ {0x4D03,
+ "Sequence Count Error: Chunk Number requested is not consecutive with the previous number transmitted."}};
+
+ if (reason >= 0x8000)
+ {
+ return "OEM Reason Code" + std::to_string(reason);
+ }
+
+ try
+ {
+ return reasonMap.at(reason);
+ }
+ catch (std::exception&)
+ {
+ return "Unknown reason code: " + std::to_string(reason);
+ }
+}
+
+static int ncsiDump(GlobalOptions& options, uint32_t handle,
+ const std::string& fileName)
+{
+ constexpr auto ncsiCmdDump = 0x4D;
+ uint32_t chunkNum = 1;
+ bool isTransferComplete = false;
+ bool isAbort = false;
+ uint8_t opcode = 0;
+ uint32_t totalDataSize = 0;
+ std::ofstream outFile(fileName, std::ios::binary);
+
+ // Validate handle
+ if (handle != NCSI_CORE_DUMP_HANDLE && handle != NCSI_CRASH_DUMP_HANDLE)
+ {
+ std::cerr
+ << "Invalid data handle value. Expected NCSI_CORE_DUMP_HANDLE (0xFFFF0000) or NCSI_CRASH_DUMP_HANDLE (0xFFFF0001), got: "
+ << std::hex << handle << "\n";
+ if (outFile.is_open())
+ outFile.close();
+ return -1;
+ }
+
+ if (!outFile.is_open())
+ {
+ std::cerr << "Failed to open file: " << fileName << "\n";
+ return -1;
+ }
+
+ while (!isTransferComplete && !isAbort)
+ {
+ auto payloadArray = generateDumpCmdPayload(chunkNum, handle, false);
+ std::span<unsigned char> payload(payloadArray.data(),
+ payloadArray.size());
+
+ NCSICommand cmd(ncsiCmdDump, static_cast<uint8_t>(*options.package),
+ options.channel, payload);
+ auto resp = options.interface->sendCommand(cmd);
+ if (!resp)
+ {
+ std::cerr << "Failed to send NCSI command for chunk number "
+ << chunkNum << "\n";
+ outFile.close();
+ return -1;
+ }
+
+ auto response = resp->response;
+ auto reason = resp->reason;
+ auto length = resp->payload.size();
+
+ if (response != 0)
+ {
+ std::cerr << "Error encountered on chunk " << chunkNum << ":\n"
+ << "Response Description: "
+ << getDescForResponse(response) << "\n"
+ << "Reason Description: " << getDescForReason(reason)
+ << "\n";
+ outFile.close();
+ return -1;
+ }
+
+ if (length > 8)
+ {
+ auto dataSize = length - 8;
+ totalDataSize += dataSize;
+ opcode = resp->payload[7];
+ if (outFile.is_open())
+ {
+ outFile.write(
+ reinterpret_cast<const char*>(resp->payload.data() + 8),
+ dataSize);
+ }
+ else
+ {
+ std::cerr << "Failed to write to file. File is not open.\n";
+ isAbort = true;
+ }
+ }
+ else
+ {
+ std::cerr << "Received response with insufficient payload length: "
+ << length << " Expected more than 8 bytes. Chunk: "
+ << chunkNum << "\n";
+ isAbort = true;
+ }
+
+ switch (opcode)
+ {
+ case 0x1: // Initial chunk, continue to next
+ case 0x2: // Middle chunk, continue to next
+ chunkNum++;
+ break;
+ case 0x4: // Final chunk
+ case 0x5: // Initial and final chunk
+ isTransferComplete = true;
+ break;
+ case 0x8: // Abort transfer
+ std::cerr << "Transfer aborted by NIC\n";
+ isTransferComplete = true;
+ break;
+ default:
+ std::cerr << "Unexpected opcode: " << static_cast<int>(opcode)
+ << " at chunk " << chunkNum << "\n";
+ isAbort = true;
+ break;
+ }
+ }
+
+ // Handle abort explicitly if an unexpected opcode was encountered.
+ if (isAbort)
+ {
+ std::cerr << "Issuing explicit abort command...\n";
+ auto abortPayloadArray = generateDumpCmdPayload(chunkNum, handle, true);
+ std::span<unsigned char> abortPayload(abortPayloadArray.data(),
+ abortPayloadArray.size());
+ NCSICommand abortCmd(ncsiCmdDump,
+ static_cast<uint8_t>(*options.package),
+ options.channel, abortPayload);
+ auto abortResp = options.interface->sendCommand(abortCmd);
+ if (!abortResp)
+ {
+ std::cerr << "Failed to send abort command for chunk number "
+ << chunkNum << "\n";
+ }
+ else
+ {
+ std::cerr << "Abort command issued.\n";
+ }
+ }
+ else
+ {
+ std::cout << "Dump transfer complete. Total data size: "
+ << totalDataSize << " bytes\n";
+ }
+
+ outFile.close();
+ return 0;
+}
+
+static int ncsiCommandReceiveDump(GlobalOptions& options,
+ const std::string& subcommand, int argc,
+ const char* const* argv)
+{
+ if (argc != 2)
+ {
+ std::cerr << "Invalid arguments for '" << subcommand
+ << "' subcommand\n";
+ print_usage(argv[0]);
+ return -1;
+ }
+ uint32_t handle = (subcommand == "core-dump") ? NCSI_CORE_DUMP_HANDLE
+ : NCSI_CRASH_DUMP_HANDLE;
+ return ncsiDump(options, handle, argv[1]);
+}
+
/* A note on log output:
* For output that relates to command-line usage, we just output directly to
* stderr. Once we have a properly parsed command line invocation, we use lg2
@@ -463,6 +716,10 @@
{
ret = ncsiCommandOEM(globalOptions, argc, argv);
}
+ else if (subcommand == "core-dump" || subcommand == "crash-dump")
+ {
+ ret = ncsiCommandReceiveDump(globalOptions, subcommand, argc, argv);
+ }
else
{
std::cerr << "Unknown subcommand '" << subcommand << "'\n";