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