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