xref: /openbmc/phosphor-networkd/src/ncsi_cmd.cpp (revision 306542d05c0255e8709ad0bba6bbe92d1551ad58)
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 };
39 
40 struct MCTPAddress
41 {
42     int network;
43     uint8_t eid;
44 };
45 
46 /* MCTP EIDs below 8 are invalid, 255 is broadcast */
47 static constexpr uint8_t MCTP_EID_MIN = 8;
48 static constexpr uint8_t MCTP_EID_MAX = 254;
49 
50 const struct option options[] = {
51     {"package", required_argument, NULL, 'p'},
52     {"channel", required_argument, NULL, 'c'},
53     {"interface", required_argument, NULL, 'i'},
54     {"mctp", required_argument, NULL, 'm'},
55     {"help", no_argument, NULL, 'h'},
56     {0, 0, 0, 0},
57 };
58 
print_usage(const char * progname)59 static 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"
66         "  " << progname << " <options> discover\n"
67         "  " << progname << " <options> core-dump FILE\n"
68         "  " << progname << " <options> crash-dump FILE\n"
69         "\n"
70         "Global options:\n"
71         "    --interface IFACE, -i  Specify net device by ifindex.\n"
72         "    --mctp [NET,]EID, -m   Specify MCTP network device.\n"
73         "    --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"
75         "    --channel CHANNEL, -c  Specify a channel.\n"
76         "\n"
77         "A --package/-p argument is required, as well as interface type "
78         "(--interface/-i or --mctp/-m)\n"
79         "\n"
80         "Subcommands:\n"
81         "\n"
82         "discover\n"
83         "    Scan for available NC-SI packages and channels.\n"
84         "\n"
85         "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"
92         "        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";
99     // clang-format on
100 }
101 
parseUnsigned(const char * str,const char * label)102 static std::optional<unsigned int> parseUnsigned(const char* str,
103                                                  const char* label)
104 {
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 
parseMCTPAddress(const std::string & str)117 static 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 
parsePayload(int argc,const char * const argv[])162 static std::optional<std::vector<unsigned char>> parsePayload(
163     int argc, const char* const argv[])
164 {
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 
parseGlobalOptions(int argc,char * const * argv)234 static std::optional<std::tuple<GlobalOptions, int>> parseGlobalOptions(
235     int argc, char* const* argv)
236 {
237     std::optional<unsigned int> chan, package, interface;
238     std::optional<MCTPAddress> mctp;
239     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          */
247         int opt = getopt_long(argc, argv, "+p:c:i:m:h", options, NULL);
248         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 
271             case 'm':
272                 mctp = parseMCTPAddress(optarg);
273                 if (!mctp.has_value())
274                 {
275                     return {};
276                 }
277                 break;
278 
279             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 
295     if (interface.has_value() && mctp.has_value())
296     {
297         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";
313         return {};
314     }
315 
316     // For non-discovery commands, package is required.
317     // If the subcommand is "discover", leave opts.package as nullopt.
318     if (!package.has_value())
319     {
320         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         }
329     }
330     else
331     {
332         opts.package = static_cast<uint8_t>(*package);
333     }
334 
335     return std::make_tuple(std::move(opts), optind);
336 }
337 
toHexStr(std::span<const uint8_t> c)338 static 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  */
ncsiCommand(GlobalOptions & options,uint8_t type,std::vector<unsigned char> payload)363 static int ncsiCommand(GlobalOptions& options, uint8_t type,
364                        std::vector<unsigned char> payload)
365 {
366     uint8_t pkg = options.package.value_or(0);
367     auto ch = options.channel;
368 
369     NCSICommand cmd(type, pkg, ch,
370                     std::span<unsigned char>(payload.data(), payload.size()));
371     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 
ncsiCommandRaw(GlobalOptions & options,int argc,const char * const * argv)387 static 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 
ncsiCommandOEM(GlobalOptions & options,int argc,const char * const * argv)424 static 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 
generateDumpCmdPayload(uint32_t chunkNum,uint32_t dataHandle,bool isAbort)444 static std::array<unsigned char, 12> generateDumpCmdPayload(
445     uint32_t chunkNum, uint32_t dataHandle, bool isAbort)
446 {
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 
getDescForResponse(uint16_t response)473 std::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 
getDescForReason(uint16_t reason)492 std::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 
ncsiDump(GlobalOptions & options,uint32_t handle,const std::string & fileName)527 static 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());
560         uint8_t pkg = options.package.value_or(0);
561         auto ch = options.channel;
562         NCSICommand cmd(ncsiCmdDump, pkg, ch, payload);
563         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());
641         uint8_t pkg = options.package.value_or(0);
642         auto ch = options.channel;
643         NCSICommand abortCmd(ncsiCmdDump, pkg, ch, abortPayload);
644         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 
ncsiCommandReceiveDump(GlobalOptions & options,const std::string & subcommand,int argc,const char * const * argv)665 static 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 
ncsiDiscover(GlobalOptions & options)681 static 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 
774 /* 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  */
main(int argc,char ** argv)780 int 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];
806 
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 
814     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     }
824     else if (subcommand == "core-dump" || subcommand == "crash-dump")
825     {
826         ret = ncsiCommandReceiveDump(globalOptions, subcommand, argc, argv);
827     }
828     else if (subcommand == "discover")
829     {
830         return ncsiDiscover(globalOptions);
831     }
832     else
833     {
834         std::cerr << "Unknown subcommand '" << subcommand << "'\n";
835         print_usage(progname);
836     }
837 
838     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
839 }
840