blob: e6cf534af8e7212686d543ff89818768f8da6fbc [file] [log] [blame]
Alexander Hansen306542d2025-10-30 17:50:11 +01001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2018 IBM Corporation
3// SPDX-FileCopyrightText: Copyright 2024 Code Construct
4
Jeremy Kerrbad17c02024-11-21 16:44:39 +08005#include "ncsi_util.hpp"
6
7#include <assert.h>
8#include <getopt.h>
Jeremy Kerrca9d8672024-09-16 14:22:02 +08009#include <linux/mctp.h>
Jeremy Kerrbad17c02024-11-21 16:44:39 +080010#include <stdint.h>
11#include <stdlib.h>
12#include <string.h>
13
14#include <phosphor-logging/lg2.hpp>
15#include <stdplus/numeric/str.hpp>
16#include <stdplus/str/buf.hpp>
17#include <stdplus/str/conv.hpp>
18
19#include <climits>
eddyluef2be3c2024-12-13 11:11:05 +080020#include <fstream>
Jeremy Kerrbad17c02024-11-21 16:44:39 +080021#include <iostream>
eddyluef2be3c2024-12-13 11:11:05 +080022#include <map>
Jeremy Kerrbad17c02024-11-21 16:44:39 +080023#include <memory>
24#include <optional>
25#include <string_view>
26#include <vector>
27
28using namespace phosphor::network::ncsi;
29
eddyluef2be3c2024-12-13 11:11:05 +080030const uint32_t NCSI_CORE_DUMP_HANDLE = 0xFFFF0000;
31const uint32_t NCSI_CRASH_DUMP_HANDLE = 0xFFFF0001;
32
Jeremy Kerrbad17c02024-11-21 16:44:39 +080033struct GlobalOptions
34{
35 std::unique_ptr<Interface> interface;
eddylu36658cc2025-02-08 14:39:36 +080036 std::optional<uint8_t> package;
37 std::optional<uint8_t> channel;
Jeremy Kerrbad17c02024-11-21 16:44:39 +080038};
39
Jeremy Kerrca9d8672024-09-16 14:22:02 +080040struct MCTPAddress
41{
42 int network;
43 uint8_t eid;
44};
45
46/* MCTP EIDs below 8 are invalid, 255 is broadcast */
47static constexpr uint8_t MCTP_EID_MIN = 8;
48static constexpr uint8_t MCTP_EID_MAX = 254;
49
Jeremy Kerrbad17c02024-11-21 16:44:39 +080050const struct option options[] = {
51 {"package", required_argument, NULL, 'p'},
52 {"channel", required_argument, NULL, 'c'},
53 {"interface", required_argument, NULL, 'i'},
Jeremy Kerrca9d8672024-09-16 14:22:02 +080054 {"mctp", required_argument, NULL, 'm'},
Jeremy Kerrbad17c02024-11-21 16:44:39 +080055 {"help", no_argument, NULL, 'h'},
56 {0, 0, 0, 0},
57};
58
59static void print_usage(const char* progname)
60{
61 // clang-format off
62 std::cerr
63 << "Usage:\n"
64 " " << progname << " <options> raw TYPE [PAYLOAD...]\n"
65 " " << progname << " <options> oem [PAYLOAD...]\n"
eddylu36658cc2025-02-08 14:39:36 +080066 " " << progname << " <options> discover\n"
eddyluef2be3c2024-12-13 11:11:05 +080067 " " << progname << " <options> core-dump FILE\n"
68 " " << progname << " <options> crash-dump FILE\n"
Jeremy Kerrbad17c02024-11-21 16:44:39 +080069 "\n"
70 "Global options:\n"
71 " --interface IFACE, -i Specify net device by ifindex.\n"
Jeremy Kerrca9d8672024-09-16 14:22:02 +080072 " --mctp [NET,]EID, -m Specify MCTP network device.\n"
eddyluef2be3c2024-12-13 11:11:05 +080073 " --package PACKAGE, -p For non-discovery commands this is required; for discovery it is optional and\n"
74 " restricts the discovery to a specific package index.\n"
Jeremy Kerrbad17c02024-11-21 16:44:39 +080075 " --channel CHANNEL, -c Specify a channel.\n"
76 "\n"
Jeremy Kerrca9d8672024-09-16 14:22:02 +080077 "A --package/-p argument is required, as well as interface type "
78 "(--interface/-i or --mctp/-m)\n"
Jeremy Kerrbad17c02024-11-21 16:44:39 +080079 "\n"
80 "Subcommands:\n"
81 "\n"
eddylu36658cc2025-02-08 14:39:36 +080082 "discover\n"
83 " Scan for available NC-SI packages and channels.\n"
84 "\n"
Jeremy Kerrbad17c02024-11-21 16:44:39 +080085 "raw TYPE [PAYLOAD...]\n"
86 " Send a single command using raw type/payload data.\n"
87 " TYPE NC-SI command type, in hex\n"
88 " PAYLOAD Command payload bytes, as hex\n"
89 "\n"
90 "oem PAYLOAD\n"
91 " Send a single OEM command (type 0x50).\n"
eddyluef2be3c2024-12-13 11:11:05 +080092 " PAYLOAD Command payload bytes, as hex\n"
93 "\n"
94 "core-dump FILE\n"
95 " Perform NCSI core dump and save log to FILE.\n"
96 "\n"
97 "crash-dump FILE\n"
98 " Perform NCSI crash dump and save log to FILE.\n";
Jeremy Kerrbad17c02024-11-21 16:44:39 +080099 // clang-format on
100}
101
Patrick Williams618ac412025-04-27 02:31:49 -0400102static std::optional<unsigned int> parseUnsigned(const char* str,
103 const char* label)
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800104{
105 try
106 {
107 unsigned long tmp = std::stoul(str, NULL, 16);
108 if (tmp <= UINT_MAX)
109 return tmp;
110 }
111 catch (const std::exception& e)
112 {}
113 std::cerr << "Invalid " << label << " argument '" << str << "'\n";
114 return {};
115}
116
Jeremy Kerrca9d8672024-09-16 14:22:02 +0800117static std::optional<MCTPAddress> parseMCTPAddress(const std::string& str)
118{
119 std::string::size_type sep = str.find(',');
120 std::string eid_str;
121 MCTPAddress addr;
122
123 if (sep == std::string::npos)
124 {
125 addr.network = MCTP_NET_ANY;
126 eid_str = str;
127 }
128 else
129 {
130 std::string net_str = str.substr(0, sep);
131 try
132 {
133 addr.network = stoi(net_str);
134 }
135 catch (const std::exception& e)
136 {
137 return {};
138 }
139 eid_str = str.substr(sep + 1);
140 }
141
142 unsigned long tmp;
143 try
144 {
145 tmp = stoul(eid_str);
146 }
147 catch (const std::exception& e)
148 {
149 return {};
150 }
151
152 if (tmp < MCTP_EID_MIN || tmp > MCTP_EID_MAX)
153 {
154 return {};
155 }
156
157 addr.eid = tmp;
158
159 return addr;
160}
161
Patrick Williams618ac412025-04-27 02:31:49 -0400162static std::optional<std::vector<unsigned char>> parsePayload(
163 int argc, const char* const argv[])
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800164{
165 /* we have already checked that there are sufficient args in callers */
166 assert(argc >= 1);
167
168 std::vector<unsigned char> payload;
169
170 /* we support two formats of payload - all as one argument:
171 * 00010c202f
172 *
173 * or single bytes in separate arguments:
174 * 00 01 0c 20 2f
175 *
176 * both are assumed as entirely hex, but the latter format does not
177 * need to be exactly two chars per byte:
178 * 0 1 c 20 2f
179 */
180
181 size_t len0 = strlen(argv[0]);
182 if (argc == 1 && len0 > 2)
183 {
184 /* single argument format, parse as multiple bytes */
185 if (len0 % 2 != 0)
186 {
187 std::cerr << "Invalid payload length " << len0
188 << " (must be a multiple of 2 chars)\n";
189 return {};
190 }
191
192 std::string str(argv[0]);
193 std::string_view sv(str);
194
195 for (unsigned int i = 0; i < sv.size(); i += 2)
196 {
197 unsigned char byte;
198 auto begin = sv.data() + i;
199 auto end = begin + 2;
200
201 auto [next, err] = std::from_chars(begin, end, byte, 16);
202
203 if (err != std::errc() || next != end)
204 {
205 std::cerr << "Invalid payload string\n";
206 return {};
207 }
208 payload.push_back(byte);
209 }
210 }
211 else
212 {
213 /* multiple payload arguments, each is a separate hex byte */
214 for (int i = 0; i < argc; i++)
215 {
216 unsigned char byte;
217 auto begin = argv[i];
218 auto end = begin + strlen(begin);
219
220 auto [next, err] = std::from_chars(begin, end, byte, 16);
221
222 if (err != std::errc() || next != end)
223 {
224 std::cerr << "Invalid payload argument '" << begin << "'\n";
225 return {};
226 }
227 payload.push_back(byte);
228 }
229 }
230
231 return payload;
232}
233
Patrick Williams618ac412025-04-27 02:31:49 -0400234static std::optional<std::tuple<GlobalOptions, int>> parseGlobalOptions(
235 int argc, char* const* argv)
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800236{
237 std::optional<unsigned int> chan, package, interface;
Jeremy Kerrca9d8672024-09-16 14:22:02 +0800238 std::optional<MCTPAddress> mctp;
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800239 const char* progname = argv[0];
240 GlobalOptions opts{};
241
242 for (;;)
243 {
244 /* We're using + here as we want to stop parsing at the subcommand
245 * name
246 */
Jeremy Kerrca9d8672024-09-16 14:22:02 +0800247 int opt = getopt_long(argc, argv, "+p:c:i:m:h", options, NULL);
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800248 if (opt == -1)
249 {
250 break;
251 }
252
253 switch (opt)
254 {
255 case 'i':
256 interface = parseUnsigned(optarg, "interface");
257 if (!interface.has_value())
258 {
259 return {};
260 }
261 break;
262
263 case 'p':
264 package = parseUnsigned(optarg, "package");
265 if (!package.has_value())
266 {
267 return {};
268 }
269 break;
270
Jeremy Kerrca9d8672024-09-16 14:22:02 +0800271 case 'm':
272 mctp = parseMCTPAddress(optarg);
273 if (!mctp.has_value())
274 {
275 return {};
276 }
277 break;
278
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800279 case 'c':
280 chan = parseUnsigned(optarg, "channel");
281 if (!chan.has_value())
282 {
283 return {};
284 }
285 opts.channel = *chan;
286 break;
287
288 case 'h':
289 default:
290 print_usage(progname);
291 return {};
292 }
293 }
294
Jeremy Kerrca9d8672024-09-16 14:22:02 +0800295 if (interface.has_value() && mctp.has_value())
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800296 {
Jeremy Kerrca9d8672024-09-16 14:22:02 +0800297 std::cerr << "Only one of --interface or --mctp can be provided\n";
298 return {};
299 }
300 else if (interface.has_value())
301 {
302 opts.interface = std::make_unique<NetlinkInterface>(*interface);
303 }
304 else if (mctp.has_value())
305 {
306 MCTPAddress m = *mctp;
307 opts.interface = std::make_unique<MCTPInterface>(m.network, m.eid);
308 }
309 else
310 {
311 std::cerr << "Missing interface description, "
312 "add a --mctp or --interface argument\n";
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800313 return {};
314 }
315
eddylu36658cc2025-02-08 14:39:36 +0800316 // For non-discovery commands, package is required.
317 // If the subcommand is "discover", leave opts.package as nullopt.
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800318 if (!package.has_value())
319 {
eddylu36658cc2025-02-08 14:39:36 +0800320 if (optind < argc && std::string(argv[optind]) == "discover")
321 {
322 // Do nothing; package remains nullopt for discovery.
323 }
324 else
325 {
326 std::cerr << "Missing package, add a --package argument\n";
327 return {};
328 }
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800329 }
eddylu36658cc2025-02-08 14:39:36 +0800330 else
331 {
332 opts.package = static_cast<uint8_t>(*package);
333 }
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800334
335 return std::make_tuple(std::move(opts), optind);
336}
337
338static stdplus::StrBuf toHexStr(std::span<const uint8_t> c) noexcept
339{
340 stdplus::StrBuf ret;
341 if (c.empty())
342 {
343 /* workaround for lg2's handling of string_view */
344 *ret.data() = '\0';
345 return ret;
346 }
347 stdplus::IntToStr<16, uint8_t> its;
348 auto oit = ret.append(c.size() * 3);
349 auto cit = c.begin();
350 oit = its(oit, *cit++, 2);
351 for (; cit != c.end(); ++cit)
352 {
353 *oit++ = ' ';
354 oit = its(oit, *cit, 2);
355 }
356 *oit = 0;
357 return ret;
358}
359
360/* Helper for the 'raw' and 'oem' command handlers: Construct a single command,
361 * issue it to the interface, and print the resulting response payload.
362 */
363static int ncsiCommand(GlobalOptions& options, uint8_t type,
364 std::vector<unsigned char> payload)
365{
eddylu36658cc2025-02-08 14:39:36 +0800366 uint8_t pkg = options.package.value_or(0);
367 auto ch = options.channel;
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800368
eddylu36658cc2025-02-08 14:39:36 +0800369 NCSICommand cmd(type, pkg, ch,
370 std::span<unsigned char>(payload.data(), payload.size()));
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800371 lg2::debug("Command: type {TYPE}, payload {PAYLOAD_LEN} bytes: {PAYLOAD}",
372 "TYPE", lg2::hex, type, "PAYLOAD_LEN", payload.size(), "PAYLOAD",
373 toHexStr(payload));
374
375 auto resp = options.interface->sendCommand(cmd);
376 if (!resp)
377 {
378 return -1;
379 }
380
381 lg2::debug("Response {DATA_LEN} bytes: {DATA}", "DATA_LEN",
382 resp->full_payload.size(), "DATA", toHexStr(resp->full_payload));
383
384 return 0;
385}
386
387static int ncsiCommandRaw(GlobalOptions& options, int argc,
388 const char* const* argv)
389{
390 std::vector<unsigned char> payload;
391 std::optional<uint8_t> type;
392
393 if (argc < 2)
394 {
395 std::cerr << "Invalid arguments for 'raw' subcommand\n";
396 return -1;
397 }
398
399 /* Not only does the type need to fit into one byte, but the top bit
400 * is used for the request/response flag, so check for 0x80 here as
401 * our max here.
402 */
403 type = parseUnsigned(argv[1], "command type");
404 if (!type.has_value() || *type > 0x80)
405 {
406 std::cerr << "Invalid command type value\n";
407 return -1;
408 }
409
410 if (argc >= 3)
411 {
412 auto tmp = parsePayload(argc - 2, argv + 2);
413 if (!tmp.has_value())
414 {
415 return -1;
416 }
417
418 payload = *tmp;
419 }
420
421 return ncsiCommand(options, *type, payload);
422}
423
424static int ncsiCommandOEM(GlobalOptions& options, int argc,
425 const char* const* argv)
426{
427 constexpr uint8_t oemType = 0x50;
428
429 if (argc < 2)
430 {
431 std::cerr << "Invalid arguments for 'oem' subcommand\n";
432 return -1;
433 }
434
435 auto payload = parsePayload(argc - 1, argv + 1);
436 if (!payload.has_value())
437 {
438 return -1;
439 }
440
441 return ncsiCommand(options, oemType, *payload);
442}
443
Patrick Williams618ac412025-04-27 02:31:49 -0400444static std::array<unsigned char, 12> generateDumpCmdPayload(
445 uint32_t chunkNum, uint32_t dataHandle, bool isAbort)
eddyluef2be3c2024-12-13 11:11:05 +0800446{
447 std::array<unsigned char, 12> payload = {};
448 uint8_t opcode;
449
450 if (isAbort)
451 {
452 opcode = 3;
453 }
454 else if (chunkNum == 1)
455 {
456 // For the first chunk the chunk number field carries the data handle.
457 opcode = 0;
458 chunkNum = dataHandle;
459 }
460 else
461 {
462 opcode = 2;
463 }
464 payload[3] = opcode;
465 payload[8] = (chunkNum >> 24) & 0xFF;
466 payload[9] = (chunkNum >> 16) & 0xFF;
467 payload[10] = (chunkNum >> 8) & 0xFF;
468 payload[11] = chunkNum & 0xFF;
469
470 return payload;
471}
472
473std::string getDescForResponse(uint16_t response)
474{
475 static const std::map<uint16_t, std::string> descMap = {
476 {0x0000, "Command Completed"},
477 {0x0001, "Command Failed"},
478 {0x0002, "Command Unavailable"},
479 {0x0003, "Command Unsupported"},
480 {0x0004, "Delayed Response"}};
481
482 try
483 {
484 return descMap.at(response);
485 }
486 catch (std::exception&)
487 {
488 return "Unknown response code: " + std::to_string(response);
489 }
490}
491
492std::string getDescForReason(uint16_t reason)
493{
494 static const std::map<uint16_t, std::string> reasonMap = {
495 {0x0001, "Interface Initialization Required"},
496 {0x0002, "Parameter Is Invalid, Unsupported, or Out-of-Range"},
497 {0x0003, "Channel Not Ready"},
498 {0x0004, "Package Not Ready"},
499 {0x0005, "Invalid Payload Length"},
500 {0x0006, "Information Not Available"},
501 {0x0007, "Intervention Required"},
502 {0x0008, "Link Command Failed - Hardware Access Error"},
503 {0x0009, "Command Timeout"},
504 {0x000A, "Secondary Device Not Powered"},
505 {0x7FFF, "Unknown/Unsupported Command Type"},
506 {0x4D01, "Abort Transfer: NC cannot proceed with transfer."},
507 {0x4D02,
508 "Invalid Handle Value: Data Handle is invalid or not supported."},
509 {0x4D03,
510 "Sequence Count Error: Chunk Number requested is not consecutive with the previous number transmitted."}};
511
512 if (reason >= 0x8000)
513 {
514 return "OEM Reason Code" + std::to_string(reason);
515 }
516
517 try
518 {
519 return reasonMap.at(reason);
520 }
521 catch (std::exception&)
522 {
523 return "Unknown reason code: " + std::to_string(reason);
524 }
525}
526
527static int ncsiDump(GlobalOptions& options, uint32_t handle,
528 const std::string& fileName)
529{
530 constexpr auto ncsiCmdDump = 0x4D;
531 uint32_t chunkNum = 1;
532 bool isTransferComplete = false;
533 bool isAbort = false;
534 uint8_t opcode = 0;
535 uint32_t totalDataSize = 0;
536 std::ofstream outFile(fileName, std::ios::binary);
537
538 // Validate handle
539 if (handle != NCSI_CORE_DUMP_HANDLE && handle != NCSI_CRASH_DUMP_HANDLE)
540 {
541 std::cerr
542 << "Invalid data handle value. Expected NCSI_CORE_DUMP_HANDLE (0xFFFF0000) or NCSI_CRASH_DUMP_HANDLE (0xFFFF0001), got: "
543 << std::hex << handle << "\n";
544 if (outFile.is_open())
545 outFile.close();
546 return -1;
547 }
548
549 if (!outFile.is_open())
550 {
551 std::cerr << "Failed to open file: " << fileName << "\n";
552 return -1;
553 }
554
555 while (!isTransferComplete && !isAbort)
556 {
557 auto payloadArray = generateDumpCmdPayload(chunkNum, handle, false);
558 std::span<unsigned char> payload(payloadArray.data(),
559 payloadArray.size());
eddylu36658cc2025-02-08 14:39:36 +0800560 uint8_t pkg = options.package.value_or(0);
561 auto ch = options.channel;
562 NCSICommand cmd(ncsiCmdDump, pkg, ch, payload);
eddyluef2be3c2024-12-13 11:11:05 +0800563 auto resp = options.interface->sendCommand(cmd);
564 if (!resp)
565 {
566 std::cerr << "Failed to send NCSI command for chunk number "
567 << chunkNum << "\n";
568 outFile.close();
569 return -1;
570 }
571
572 auto response = resp->response;
573 auto reason = resp->reason;
574 auto length = resp->payload.size();
575
576 if (response != 0)
577 {
578 std::cerr << "Error encountered on chunk " << chunkNum << ":\n"
579 << "Response Description: "
580 << getDescForResponse(response) << "\n"
581 << "Reason Description: " << getDescForReason(reason)
582 << "\n";
583 outFile.close();
584 return -1;
585 }
586
587 if (length > 8)
588 {
589 auto dataSize = length - 8;
590 totalDataSize += dataSize;
591 opcode = resp->payload[7];
592 if (outFile.is_open())
593 {
594 outFile.write(
595 reinterpret_cast<const char*>(resp->payload.data() + 8),
596 dataSize);
597 }
598 else
599 {
600 std::cerr << "Failed to write to file. File is not open.\n";
601 isAbort = true;
602 }
603 }
604 else
605 {
606 std::cerr << "Received response with insufficient payload length: "
607 << length << " Expected more than 8 bytes. Chunk: "
608 << chunkNum << "\n";
609 isAbort = true;
610 }
611
612 switch (opcode)
613 {
614 case 0x1: // Initial chunk, continue to next
615 case 0x2: // Middle chunk, continue to next
616 chunkNum++;
617 break;
618 case 0x4: // Final chunk
619 case 0x5: // Initial and final chunk
620 isTransferComplete = true;
621 break;
622 case 0x8: // Abort transfer
623 std::cerr << "Transfer aborted by NIC\n";
624 isTransferComplete = true;
625 break;
626 default:
627 std::cerr << "Unexpected opcode: " << static_cast<int>(opcode)
628 << " at chunk " << chunkNum << "\n";
629 isAbort = true;
630 break;
631 }
632 }
633
634 // Handle abort explicitly if an unexpected opcode was encountered.
635 if (isAbort)
636 {
637 std::cerr << "Issuing explicit abort command...\n";
638 auto abortPayloadArray = generateDumpCmdPayload(chunkNum, handle, true);
639 std::span<unsigned char> abortPayload(abortPayloadArray.data(),
640 abortPayloadArray.size());
eddylu36658cc2025-02-08 14:39:36 +0800641 uint8_t pkg = options.package.value_or(0);
642 auto ch = options.channel;
643 NCSICommand abortCmd(ncsiCmdDump, pkg, ch, abortPayload);
eddyluef2be3c2024-12-13 11:11:05 +0800644 auto abortResp = options.interface->sendCommand(abortCmd);
645 if (!abortResp)
646 {
647 std::cerr << "Failed to send abort command for chunk number "
648 << chunkNum << "\n";
649 }
650 else
651 {
652 std::cerr << "Abort command issued.\n";
653 }
654 }
655 else
656 {
657 std::cout << "Dump transfer complete. Total data size: "
658 << totalDataSize << " bytes\n";
659 }
660
661 outFile.close();
662 return 0;
663}
664
665static int ncsiCommandReceiveDump(GlobalOptions& options,
666 const std::string& subcommand, int argc,
667 const char* const* argv)
668{
669 if (argc != 2)
670 {
671 std::cerr << "Invalid arguments for '" << subcommand
672 << "' subcommand\n";
673 print_usage(argv[0]);
674 return -1;
675 }
676 uint32_t handle = (subcommand == "core-dump") ? NCSI_CORE_DUMP_HANDLE
677 : NCSI_CRASH_DUMP_HANDLE;
678 return ncsiDump(options, handle, argv[1]);
679}
680
eddylu36658cc2025-02-08 14:39:36 +0800681static int ncsiDiscover(GlobalOptions& options)
682{
683 constexpr uint8_t ncsiClearInitialState = 0x00;
684 constexpr uint8_t ncsiGetCapabilities = 0x16;
685 constexpr unsigned int maxPackageIndex = 8; // Packages 0–7
686 constexpr unsigned int maxChannelCount = 32; // Channels 0–31
687
688 std::cout << "Starting NC-SI Package and Channel Discovery...\n";
689
690 unsigned int startPackage = 0, endPackage = maxPackageIndex;
691 if (options.package.has_value())
692 {
693 startPackage = *options.package;
694 endPackage = *options.package;
695 std::cout << "Restricting discovery to Package " << *options.package
696 << ".\n";
697 }
698
699 for (unsigned int packageIndex = startPackage; packageIndex <= endPackage;
700 ++packageIndex)
701 {
702 std::cout << "Checking Package " << packageIndex << "...\n";
703
704 // For each channel from 0..7, we:
705 // 1) Send Clear Initial State.
706 // 2) If that succeeds, send Get Capabilities.
707 // 3) If we get a valid response, we parse and stop.
708 bool foundChannel = false;
709 for (unsigned int channelIndex = 0; channelIndex < maxChannelCount;
710 ++channelIndex)
711 {
712 std::cout << " Clearing Initial State on Channel " << channelIndex
713 << "...\n";
714 {
715 std::vector<unsigned char> clearPayload; // No payload
716 NCSICommand clearCmd(ncsiClearInitialState,
717 static_cast<uint8_t>(packageIndex),
718 static_cast<uint8_t>(channelIndex),
719 std::span<unsigned char>(clearPayload));
720
721 auto clearResp = options.interface->sendCommand(clearCmd);
722 if (!clearResp || clearResp->response != 0x0000)
723 {
724 std::cout << " Clear Initial State failed on Channel "
725 << channelIndex << ". Trying next channel.\n";
726 continue; // Try next channel
727 }
728 }
729
730 // Now that Clear Initial State succeeded, try Get Capabilities
731 std::vector<unsigned char> payload; // No payload
732 NCSICommand getCapCmd(ncsiGetCapabilities,
733 static_cast<uint8_t>(packageIndex),
734 static_cast<uint8_t>(channelIndex),
735 std::span<unsigned char>(payload));
736
737 auto resp = options.interface->sendCommand(getCapCmd);
738 if (resp && resp->response == 0x0000 &&
739 resp->full_payload.size() >= 52)
740 {
741 uint8_t channelCount = resp->full_payload[47];
742 std::cout << " Package " << packageIndex << " supports "
743 << static_cast<int>(channelCount) << " channels.\n";
744 std::cout << " Found available Package " << packageIndex
745 << ", Channel " << channelIndex
746 << ". Stopping discovery.\n";
747 foundChannel = true;
748 break;
749 }
750 else
751 {
752 std::cout << " Channel " << channelIndex
753 << " not responding. Trying next channel.\n";
754 }
755 } // end channel loop
756
757 if (foundChannel)
758 {
759 // We found a channel on this package, so we stop discovery
760 return 0;
761 }
762 else
763 {
764 std::cout << " No valid channels found for Package "
765 << packageIndex << ". Moving to next package.\n";
766 }
767 } // end package loop
768
769 std::cout
770 << "No available NC-SI packages or channels found. Discovery complete.\n";
771 return -1;
772}
773
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800774/* A note on log output:
775 * For output that relates to command-line usage, we just output directly to
776 * stderr. Once we have a properly parsed command line invocation, we use lg2
777 * for log output, as we want that to use the standard log facilities to
778 * catch runtime error scenarios
779 */
780int main(int argc, char** argv)
781{
782 const char* progname = argv[0];
783
784 auto opts = parseGlobalOptions(argc, argv);
785
786 if (!opts.has_value())
787 {
788 return EXIT_FAILURE;
789 }
790
791 auto [globalOptions, consumed] = std::move(*opts);
792
793 if (consumed >= argc)
794 {
795 std::cerr << "Missing subcommand command type\n";
796 return EXIT_FAILURE;
797 }
798
799 /* We have parsed the global options, advance argv & argc to allow the
800 * subcommand handlers to consume their own options
801 */
802 argc -= consumed;
803 argv += consumed;
804
805 std::string subcommand = argv[0];
eddylu36658cc2025-02-08 14:39:36 +0800806
807 // For non-discovery commands, package must be provided.
808 if (subcommand != "discover" && !globalOptions.package.has_value())
809 {
810 std::cerr << "Missing package, add a --package argument\n";
811 return EXIT_FAILURE;
812 }
813
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800814 int ret = -1;
815
816 if (subcommand == "raw")
817 {
818 ret = ncsiCommandRaw(globalOptions, argc, argv);
819 }
820 else if (subcommand == "oem")
821 {
822 ret = ncsiCommandOEM(globalOptions, argc, argv);
823 }
eddyluef2be3c2024-12-13 11:11:05 +0800824 else if (subcommand == "core-dump" || subcommand == "crash-dump")
825 {
826 ret = ncsiCommandReceiveDump(globalOptions, subcommand, argc, argv);
827 }
eddylu36658cc2025-02-08 14:39:36 +0800828 else if (subcommand == "discover")
829 {
830 return ncsiDiscover(globalOptions);
831 }
Jeremy Kerrbad17c02024-11-21 16:44:39 +0800832 else
833 {
834 std::cerr << "Unknown subcommand '" << subcommand << "'\n";
835 print_usage(progname);
836 }
837
838 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
839}