libxcoder 5.8.0
Loading...
Searching...
No Matches
ni_quadraprobe.c
Go to the documentation of this file.
1/*******************************************************************************
2 *
3 * Copyright (C) 2022 NETINT Technologies
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18 * SOFTWARE.
19 *
20 ******************************************************************************/
21
22/*!*****************************************************************************
23 * \file ni_quadraprobe.c
24 *
25 * \brief Read the firmware logs from BAR4 space
26 ******************************************************************************/
27
28#include <stdio.h>
29#include <stdint.h>
30#include <stddef.h>
31#include <stdlib.h>
32#include <string.h>
33#include <sys/stat.h>
34#include <unistd.h>
35#include <fcntl.h>
36#include <errno.h>
37#include <limits.h>
38#include <dirent.h>
39#include <sys/mman.h>
40#include <stdbool.h>
41#include <pwd.h>
42#include <time.h>
43#include <ctype.h>
44
45#include "ni_quadraprobe.h"
46#include "ni_util.h"
47
48/* These defines are private to this translation unit */
49#define PCI_SYSFS_PATH "/sys/bus/pci/devices"
50#define QUADRA_VENDOR_ID 0x1d82
51#define QUADRA_DEVICE_ID 0x0401
52
53/* artifacts BAR4 offset : 0x84F920000 (artifacts base) - 0x840000000 (window base) = 0xF920000 */
54#define ARTIFACTS_BAR4_OFFSET 0xF920000 /* BAR4 offset to artifacts buffer in DDR */
55
56#define BAR4_MIN_ADDRESS 0x840000000ULL
57#define NOR_FLASH_CMD_ADDRESS 0x84F8F5020ULL
58#define NOR_FLASH_CMD_OFFSET (NOR_FLASH_CMD_ADDRESS - BAR4_MIN_ADDRESS)
59#define NOR_READ_WAIT_SECONDS 100
60
61#define CORE_LOG_PTR_TABLE_OFFSET 0xF8F5000
62#define CORE_RESET_LOG_PTR_TABLE_OFFSET 0xF8FD0C0
63#define CORE_LOG_SIZE ((size_t)1024 * 1024) /* 1 MB per-core normal log buffer */
64#define CORE_RESET_LOG_SIZE ((size_t)64 * 1024) /* 64 KB per-core reset log buffer */
65
66#define FL_LOG_ENTRY_SIZE 128 /* bytes per FL log record */
67#define FL_CORE_IDX 5
68
69#if defined(__linux__)
70
71const char *const core_names[NUM_CORES] = {"np", "dp", "ep", "tp", "fp", "fl"};
72
73/*!*****************************************************************************
74 * \brief Restore file ownership to the invoking user.
75 *
76 * \return None
77 ******************************************************************************/
78static void set_log_file_ownership(const char *fname)
79{
80 chmod(fname, 0664);
81 const char *sudo_user = getenv("SUDO_USER"); // NOLINT(concurrency-mt-unsafe) - no thread-safe alternative
82 if (!sudo_user || sudo_user[0] == '\0')
83 return;
84 struct passwd pw_buf, *pw = NULL;
85 char pwbuf[1024];
86 (void)getpwnam_r(sudo_user, &pw_buf, pwbuf, sizeof(pwbuf), &pw);
87 // print the error message if chown fails
88 if (pw && chown(fname, pw->pw_uid, pw->pw_gid) != 0)
89 {
90 char errbuf[NI_ERRNO_LEN];
91 (void)ni_strerror(errbuf, sizeof(errbuf), errno);
92 (void)fprintf(stderr, "[WARN] chown(%s) failed: %s\n",
93 fname, errbuf);
94 }
95}
96
97/*!*****************************************************************************
98 * \brief Read the NVMe serial number for a Quadra device from sysfs.
99 *
100 * \param[in] qd QuadraDevice whose sysfs_path to search
101 * \param[out] sn Buffer to receive the serial number
102 * \param[in] sn_len Size of sn buffer
103 *
104 * \return 0 on success, -1 if not found or error
105 ******************************************************************************/
106static int read_nvme_sn(const QuadraDevice *qd, char *sn, size_t sn_len)
107{
108 char nvme_dir[PATH_MAX];
109 if (snprintf(nvme_dir, sizeof(nvme_dir), "%s/nvme", qd->sysfs_path) < 0)
110 return -1;
111
112 DIR *dir = opendir(nvme_dir);
113 if (!dir)
114 return -1;
115
116 int found = 0;
117 struct dirent *entry;
118 while ((entry = readdir(dir))) // NOLINT(concurrency-mt-unsafe) - single-threaded CLI tool
119 {
120 if (strncmp(entry->d_name, "nvme", 4) != 0)
121 continue;
122
123 char serial_path[PATH_MAX];
124 if (snprintf(serial_path, sizeof(serial_path), "%s/%s/serial",
125 nvme_dir, entry->d_name) < 0)
126 continue;
127
128 FILE *fp = fopen(serial_path, "r");
129 if (!fp)
130 continue;
131
132 char raw_sn[21] = {0};
133 if (fgets(raw_sn, sizeof(raw_sn), fp))
134 {
135 raw_sn[strcspn(raw_sn, " \t\r\n")] = '\0';
136 if (raw_sn[0])
137 {
138 for (char *p = raw_sn; *p; ++p)
139 {
140 if (!isalnum((unsigned char)*p) && *p != '-')
141 *p = '_';
142 }
143 (void)snprintf(sn, sn_len, "%s", raw_sn);
144 found = 1;
145 }
146 }
147 (void)fclose(fp);
148 if (found)
149 break;
150 }
151 closedir(dir);
152 return found ? 0 : -1;
153}
154
155int bdf_to_sysfs(const char* bdf, char* sysfs_path, size_t pathsz, int *domain, int *bus, int *slot, int *func)
156{
157 unsigned int d=0, b=0, s=0, f=0;
158 if (sscanf(bdf, "%x:%x:%x.%x", &d, &b, &s, &f) != 4) // NOLINT(cert-err34-c) - return value checked
159 return -1;
160 if (snprintf(sysfs_path, pathsz, "/sys/bus/pci/devices/%04x:%02x:%02x.%x", d, b, s, f) < 0)
161 return -1;
162 if (domain) *domain = (int)d;
163 if (bus) *bus = (int)b;
164 if (slot) *slot = (int)s;
165 if (func) *func = (int)f;
166 return 0;
167}
168
169static int safe_realloc_devices(QuadraDevice **p_devs, int new_cap)
170{
171 QuadraDevice *new_devs = realloc(*p_devs, new_cap * sizeof(**p_devs));
172 if (!new_devs)
173 {
174 free(*p_devs);
175 *p_devs = NULL;
176 return -1;
177 }
178 *p_devs = new_devs;
179 return 0;
180}
181
182int quadra_device_find(QuadraDevice **out_devs)
183{
184 DIR *dir = opendir(PCI_SYSFS_PATH);
185 if (!dir)
186 {
187 perror("opendir(sysfs)");
188 return -1;
189 }
190
191 struct dirent *entry;
192 QuadraDevice *devs = calloc(8, sizeof(*devs));
193 int dev_count = 0, capacity = 8;
194 if (!devs)
195 {
196 closedir(dir);
197 return -1;
198 }
199
200 char path[PATH_MAX];
201 while ((entry = readdir(dir))) // NOLINT(concurrency-mt-unsafe) - single-threaded CLI tool
202 {
203 if (entry->d_name[0] == '.')
204 continue;
205
206 if (snprintf(path, sizeof(path), "%s/%s", PCI_SYSFS_PATH, entry->d_name) < 0)
207 continue;
208
209 struct stat st;
210 if (stat(path, &st) < 0 || !S_ISDIR(st.st_mode))
211 continue;
212
213 char ven_path[PATH_MAX], dev_path[PATH_MAX];
214 if (snprintf(ven_path, sizeof(ven_path), "%s/vendor", path) < 0)
215 continue;
216 if (snprintf(dev_path, sizeof(dev_path), "%s/device", path) < 0)
217 continue;
218
219 FILE *fv = fopen(ven_path, "r");
220 if (!fv)
221 continue;
222
223 char buf[32];
224 if (!fgets(buf, sizeof(buf), fv))
225 {
226 (void)fclose(fv);
227 continue;
228 }
229 (void)fclose(fv);
230
231 unsigned long ven = strtoul(buf, NULL, 0);
232 if (ven != QUADRA_VENDOR_ID)
233 continue;
234
235 /* Read device */
236 FILE *fd = fopen(dev_path, "r");
237 if (!fd)
238 continue;
239
240 if (!fgets(buf, sizeof(buf), fd))
241 {
242 (void)fclose(fd);
243 continue;
244 }
245 (void)fclose(fd);
246
247 unsigned long dev = strtoul(buf, NULL, 0);
248 if (dev != QUADRA_DEVICE_ID)
249 continue;
250
251 if (dev_count >= capacity)
252 {
253 capacity *= 2;
254 if (safe_realloc_devices(&devs, capacity) < 0)
255 {
256 closedir(dir);
257 return -1;
258 }
259 }
260
261 if (bdf_to_sysfs(entry->d_name,
262 devs[dev_count].sysfs_path, sizeof(devs[dev_count].sysfs_path),
263 &devs[dev_count].domain, &devs[dev_count].bus, &devs[dev_count].slot,
264 &devs[dev_count].func) == 0)
265 {
266 dev_count++;
267 }
268 }
269
270 closedir(dir);
271
272 if (dev_count == 0)
273 {
274 free(devs);
275 return 0;
276 }
277
278 *out_devs = devs;
279 return dev_count;
280}
281
282int quadra_init_mmap(QuadraDevice *qd)
283{
284 char bar4path[PATH_MAX];
285 struct stat bar4stat;
286 if (snprintf(bar4path, sizeof(bar4path), "%s/resource4", qd->sysfs_path) < 0)
287 return -1;
288 if (geteuid() != 0)
289 {
290 (void)fprintf(stderr, "Permission error: You must run as root (sudo)\n");
291 return -1;
292 }
293 /*
294 * O_RDWR here causes EROFS during card loss - BAR4 becomes read-only (QDFW-5627)
295 * temporarily.
296 */
297 qd->bar4_fd = open(bar4path, O_RDONLY | O_SYNC);
298 if (qd->bar4_fd == -1)
299 {
300 perror("open(bar4)");
301 return -1;
302 }
303 if (fstat(qd->bar4_fd, &bar4stat) == 0)
304 {
305 qd->barsize = bar4stat.st_size;
306 }
307 /* if fstat reports size 0, fallback to lseek. */
308 if (qd->barsize == 0)
309 {
310 off_t sz = lseek(qd->bar4_fd, 0, SEEK_END);
311 if (sz > 0)
312 {
313 qd->barsize = (off_t)sz;
314 (void)lseek(qd->bar4_fd, 0, SEEK_SET);
315 }
316 }
317 if (qd->barsize == 0)
318 {
319 (void)fprintf(stderr, "Cannot determine BAR4 size for %s\n", bar4path);
320 close(qd->bar4_fd);
321 qd->bar4_fd = -1;
322 return -1;
323 }
324 qd->bar4_base = mmap(NULL, qd->barsize, PROT_READ, MAP_SHARED, qd->bar4_fd, 0);
325 if (qd->bar4_base == MAP_FAILED || qd->bar4_base == NULL)
326 {
327 if (qd->bar4_base != NULL && errno == EACCES)
328 (void)fprintf(stderr, "Permission error: Cannot mmap %s. Run as root (sudo).\n", bar4path);
329 else if (qd->bar4_base != NULL && errno == EINVAL)
330 (void)fprintf(stderr, "EINVAL: mmap() on BAR4 failed. Try kernel flag 'iomem=relaxed'.\n");
331 else
332 (void)fprintf(stderr, "Failed to mmap BAR4 at %s: %s\n", bar4path, strerror(errno)); // NOLINT(concurrency-mt-unsafe) - single-threaded CLI tool
333 close(qd->bar4_fd);
334 qd->bar4_fd = -1;
335 qd->bar4_base = NULL;
336 return -1;
337 }
338 return 0;
339}
340
341void quadra_cleanup(QuadraDevice *qd)
342{
343 if (qd->bar4_base && qd->bar4_base != MAP_FAILED)
344 {
345 munmap(qd->bar4_base, qd->barsize);
346 qd->bar4_base = NULL;
347 }
348 if (qd->bar4_fd >= 0)
349 {
350 close(qd->bar4_fd);
351 qd->bar4_fd = -1;
352 }
353}
354
355/*!*****************************************************************************
356 * \brief Write a NOR command to the BAR4 register.
357 *
358 * Uses a temporary O_RDWR mapping just for NOR command write, so the
359 * quadra_init_mmap stays read-only (QDFW-5627).
360 *
361 * \return 0 - for success
362 * -1 - if bar4 is not mapped
363 ******************************************************************************/
364static int quadra_nor_dispatch(QuadraDevice *qd, uint32_t command)
365{
366 char bar4path[PATH_MAX];
367 if (snprintf(bar4path, sizeof(bar4path), "%s/resource4", qd->sysfs_path) < 0)
368 return -1;
369
370 char errbuf[NI_ERRNO_LEN];
371 int fd = open(bar4path, O_RDWR | O_SYNC);
372 if (fd == -1)
373 {
374 (void)ni_strerror(errbuf, sizeof(errbuf), errno);
375 (void)fprintf(stderr, "Failed to open BAR4 (O_RDWR) at %s for NOR command: %s\n",
376 bar4path, errbuf);
377 return -1;
378 }
379
380 void *map = mmap(NULL, qd->barsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
381 if (map == MAP_FAILED)
382 {
383 (void)ni_strerror(errbuf, sizeof(errbuf), errno);
384 (void)fprintf(stderr, "Failed to mmap BAR4 (RW) at %s for NOR command: %s\n",
385 bar4path, errbuf);
386 close(fd);
387 return -1;
388 }
389
390 *(volatile uint32_t *)((uint8_t *)map + NOR_FLASH_CMD_OFFSET) = command;
391
392 munmap(map, qd->barsize);
393 close(fd);
394 return 0;
395}
396
397/*!*****************************************************************************
398 * \brief Block the host while firmware loads NOR logs to DDR.(~100 sec)
399 *
400 * \return None
401 ******************************************************************************/
402static void quadra_nor_read_wait(void)
403{
404 for (int elapsed = 0; elapsed < NOR_READ_WAIT_SECONDS; elapsed += 10)
405 {
406 (void)fprintf(stderr, "\r[INFO] NOR flash operation in progress... %d%%",
407 ((elapsed + 1) * 100) / NOR_READ_WAIT_SECONDS);
408 ni_usleep(10 * 1000000LL);
409 }
410 (void)fprintf(stderr, "\r[INFO] NOR flash operation in progress... 100%%\n");
411}
412
413/*!*****************************************************************************
414 * \brief Dispatch a NOR command and wait for completion - read command.
415 *
416 * Nor read blocks ~100 s while firmware copies NOR to DDR;
417 * write/erase/exception return immediately (fire-and-forget).
418 *
419 * \return 0 - on success
420 * -1 - if BAR4 is not mapped
421 ******************************************************************************/
422int quadra_nor_command(QuadraDevice *qd, uint32_t command)
423{
424 if (quadra_nor_dispatch(qd, command) != 0)
425 return -1;
426
427 switch (command)
428 {
429 case NOR_CMD_READ:
430 quadra_nor_read_wait();
431 (void)fprintf(stderr, "[INFO] NOR flash command complete.\n");
432 break;
433 case NOR_CMD_ERASE:
434 (void)fprintf(stderr,
435 "[INFO] NOR flash erase command dispatched.\n"
436 "[INFO] Erasing NOR flash logs may take up to 100 seconds to complete.\n");
437 break;
438 case NOR_CMD_WRITE:
439 (void)fprintf(stderr,
440 "[INFO] NOR flash write command dispatched.\n"
441 "[INFO] Writing DDR logs to NOR flash may take up to 100 seconds to complete.\n");
442 break;
443 case NOR_CMD_EXCEPTION:
444 (void)fprintf(stderr, "[INFO] NOR flash exception command dispatched.\n");
445 break;
446 default:
447 (void)fprintf(stderr, "[INFO] Unsupported NOR flash command: %u\n", command);
448 break;
449 }
450
451 return 0;
452}
453
454void fill_core_log_offsets(QuadraDevice *qd)
455{
456 uint8_t *base = qd->bar4_base;
457 off_t offset = CORE_LOG_PTR_TABLE_OFFSET;
458 for (int i = 0; i < NUM_CORES; ++i)
459 {
460 uint32_t log_offset = *(uint32_t *)(base + offset + 4 * (size_t)i) & 0x0FFFFFFF;
461 if ((size_t)log_offset + CORE_LOG_SIZE > (size_t)qd->barsize)
462 {
463 (void)fprintf(stderr,
464 "[WARN] Core %s log offset 0x%x + 0x%zx exceeds BAR4 size 0x%lx, skipping\n",
465 core_names[i], log_offset, CORE_LOG_SIZE, (unsigned long)qd->barsize);
466 log_offset = 0;
467 }
468 qd->core_log_offsets[i] = log_offset;
469 }
470 qd->log_size = CORE_LOG_SIZE;
471 qd->log_artifacts_offset = ARTIFACTS_BAR4_OFFSET;
472}
473
474void fill_core_reset_log_offsets(QuadraDevice *qd)
475{
476 uint8_t *base = qd->bar4_base;
477 off_t offset = CORE_RESET_LOG_PTR_TABLE_OFFSET;
478 for (int i = 0; i < NUM_CORES_NO_FL; ++i)
479 {
480 uint32_t log_offset = *(uint32_t *)(base + offset + 4 * (size_t)i) & 0x0FFFFFFF;
481 if ((size_t)log_offset + CORE_RESET_LOG_SIZE > (size_t)qd->barsize)
482 {
483 (void)fprintf(stderr,
484 "[WARN] Core %s reset-log offset 0x%x + 0x%zx exceeds BAR4 size 0x%lx, skipping\n",
485 core_names[i], log_offset, CORE_RESET_LOG_SIZE, (unsigned long)qd->barsize);
486 log_offset = 0;
487 }
488 qd->core_log_offsets[i] = log_offset;
489 }
490 qd->log_size = CORE_RESET_LOG_SIZE;
491 qd->log_artifacts_offset = ARTIFACTS_BAR4_OFFSET;
492}
493
494/*!*****************************************************************************
495 * \brief Dump Firmware Loader logs
496 *
497 * Reads the FL signature at the core's BAR4 offset; if it does not
498 * match FL_SIGNATURE the core is skipped, otherwise read from BAR4,
499 * FL logs never read from NOR flash.
500 *
501 * \param[in] qd Initialized QuadraDevice - BAR4 mapped.
502 * \param[in] outdir Destination directory.
503 *
504 * \return 0 - on success,
505 * -1 - on file-open failure.
506 ******************************************************************************/
507static int dump_fl_core(QuadraDevice *qd, const char *outdir)
508{
509 uint32_t fl_offset = qd->core_log_offsets[FL_CORE_IDX];
510 if ((size_t)fl_offset + 8 > (size_t)qd->barsize)
511 {
512 (void)fprintf(stderr, "[WARN] FL offset 0x%x out of BAR4 range, skipping\n", fl_offset);
513 return -1;
514 }
515 uint8_t *base = qd->bar4_base + fl_offset;
516
517 uint32_t signature;
518 memcpy(&signature, base, sizeof(signature));
519 (void)fprintf(stderr, "[INFO] FL Signature 0x%08x\n", signature);
520
521 if (signature != FL_SIGNATURE)
522 {
523 (void)fprintf(stderr, "[WARN] FL log: invalid signature, skipping\n");
524 return 0;
525 }
526
527 uint32_t len_field;
528 memcpy(&len_field, base + 4, sizeof(len_field));
529 uint32_t log_len = len_field & 0xFFFFFF;
530 size_t payload_size = (size_t)FL_LOG_ENTRY_SIZE * log_len;
531 if ((size_t)fl_offset + 8 + payload_size > (size_t)qd->barsize)
532 {
533 (void)fprintf(stderr,
534 "[ERR] FL log payload_size %zu exceeds BAR4 bounds (%lu)\n",
535 payload_size, (unsigned long)qd->barsize);
536 return -1;
537 }
538
539 char fname[PATH_MAX];
540 if (snprintf(fname, sizeof(fname), "%s/raw_fl_slot_%02x_%04x.bin",
541 outdir, qd->bus, qd->domain) < 0)
542 return -1;
543
544 uint8_t *raw = base + 8;
545 FILE *fp = fopen(fname, "wb");
546 if (!fp)
547 {
548 perror("fopen");
549 return -1;
550 }
551
552 for (size_t i = 0; i < payload_size; ++i)
553 {
554 if (raw[i] != 0x00)
555 (void)fputc(raw[i], fp);
556 }
557
558 (void)fclose(fp);
559 set_log_file_ownership(fname);
560 (void)fprintf(stderr, "[INFO] Dumped log: %s\n", fname);
561 return 0;
562}
563
564/*!*****************************************************************************
565 * \brief Dump per-core firmware logs from BAR4 to files.
566 *
567 * \param[in] qd Initialized QuadraDevice - bar4 mapped.
568 * \param[in] outdir Destination directory for the log files.
569 * \param[in] core_reset_log true -> dump core-reset logs
570 * false -> dump running firmware logs (including FL)
571 *
572 * \return 0 - on success,
573 * -1 - one or more cores failed
574 ******************************************************************************/
575int dump_raw_logs(QuadraDevice *qd, const char *outdir, bool core_reset_log)
576{
577 int num_cores;
578 if (core_reset_log)
579 {
580 fill_core_reset_log_offsets(qd);
581 num_cores = NUM_CORES_NO_FL;
582 }
583 else
584 {
585 fill_core_log_offsets(qd);
586 num_cores = NUM_CORES;
587 }
588
589 char sn[21] = {0};
590 (void)read_nvme_sn(qd, sn, sizeof(sn));
591
592 char ts[16] = {0};
593 time_t now = time(NULL);
594 struct tm tm_buf;
595 struct tm *tm_info = localtime_r(&now, &tm_buf);
596 if (tm_info)
597 (void)strftime(ts, sizeof(ts), "%Y%m%d-%H%M%S", tm_info);
598
599 int rc = 0;
600 for (int core_idx = 0; core_idx < num_cores; ++core_idx)
601 {
602 const char *core = core_names[core_idx];
603
604 if (core_idx == FL_CORE_IDX)
605 {
606 rc |= (dump_fl_core(qd, outdir) != 0) ? -1 : 0;
607 continue;
608 }
609
610 uint32_t log_offset = qd->core_log_offsets[core_idx];
611 if ((size_t)log_offset + (size_t)qd->log_size > (size_t)qd->barsize)
612 {
613 (void)fprintf(stderr,
614 "[ERROR] %s log offset 0x%x out of BAR4 range, skipping\n",
615 core, log_offset);
616 rc = -1;
617 continue;
618 }
619 char fname[PATH_MAX];
620 int n;
621 if (sn[0] && ts[0])
622 n = snprintf(fname, sizeof(fname), "%s/raw_%s_%04x_%02x_%02x_%x_%s_%s.bin",
623 outdir, core, (unsigned)qd->domain, (unsigned)qd->bus,
624 (unsigned)qd->slot, (unsigned)qd->func, sn, ts);
625 else if (sn[0])
626 n = snprintf(fname, sizeof(fname), "%s/raw_%s_%04x_%02x_%02x_%x_%s.bin",
627 outdir, core, (unsigned)qd->domain, (unsigned)qd->bus,
628 (unsigned)qd->slot, (unsigned)qd->func, sn);
629 else if (ts[0])
630 n = snprintf(fname, sizeof(fname), "%s/raw_%s_%04x_%02x_%02x_%x_%s.bin",
631 outdir, core, (unsigned)qd->domain, (unsigned)qd->bus,
632 (unsigned)qd->slot, (unsigned)qd->func, ts);
633 else
634 n = snprintf(fname, sizeof(fname), "%s/raw_%s_%04x_%02x_%02x_%x.bin",
635 outdir, core, (unsigned)qd->domain, (unsigned)qd->bus,
636 (unsigned)qd->slot, (unsigned)qd->func);
637 if (n < 0 || (size_t)n >= sizeof(fname))
638 {
639 (void)fprintf(stderr,
640 "[ERROR] %s log filename truncated or encoding error, skipping\n", core);
641 rc = -1;
642 continue;
643 }
644 FILE *fp = fopen(fname, "wb");
645 if (!fp)
646 {
647 perror("fopen");
648 rc = -1;
649 continue;
650 }
651 if (fwrite(qd->bar4_base + log_offset, 1, (size_t)qd->log_size, fp) != (size_t)qd->log_size)
652 {
653 (void)fprintf(stderr, "[ERROR] Short write on %s\n", fname);
654 (void)fclose(fp);
655 set_log_file_ownership(fname);
656 rc = -1;
657 continue;
658 }
659 (void)fclose(fp);
660 set_log_file_ownership(fname);
661 if (memcmp(qd->bar4_base + log_offset, "hashid", 6) == 0)
662 {
663 char hstr[17] = {0};
664 memcpy(hstr, qd->bar4_base + log_offset, 16);
665 (void)fprintf(stderr, "%s: %s\n", core, hstr);
666 }
667 (void)fprintf(stderr, "[INFO] Dumped log: %s\n", fname);
668 }
669 return rc;
670}
671
672/*!*****************************************************************************
673 * \brief Read nor-persistent logs to BAR4 and write them to files.
674 * NOR read command triggers core log read from nor flash to BAR4.
675 *
676 * \param[in] qd Initialized QuadraDevice
677 * \param[in] outdir Destination directory for the log files.
678 *
679 * \return 0 - log dump successful
680 * -1 - one or more cores failed
681 ******************************************************************************/
682int dump_nor_logs(QuadraDevice *qd, const char *outdir)
683{
684 int rc = 0;
685 for (int core_idx = 0; core_idx < NUM_CORES_NO_FL; ++core_idx)
686 {
687 const char *core = core_names[core_idx];
688
689 uint32_t log_offset = qd->core_log_offsets[core_idx];
690 if ((size_t)log_offset + (size_t)qd->log_size > (size_t)qd->barsize)
691 {
692 (void)fprintf(stderr,
693 "[ERROR] %s log offset 0x%x out of BAR4 range, skipping\n",
694 core, log_offset);
695 rc = -1;
696 continue;
697 }
698 char fname[PATH_MAX];
699 if (snprintf(fname, sizeof(fname), "%s/raw_%s_slot_%02x_%04x.bin",
700 outdir, core, qd->bus, qd->domain) < 0) continue;
701 FILE *fp = fopen(fname, "wb");
702 if (!fp)
703 {
704 perror("fopen");
705 rc = -1;
706 continue;
707 }
708 if (fwrite(qd->bar4_base + log_offset, 1, (size_t)qd->log_size, fp) != (size_t)qd->log_size)
709 {
710 (void)fprintf(stderr, "[ERROR] Short write on %s\n", fname);
711 (void)fclose(fp);
712 set_log_file_ownership(fname);
713 rc = -1;
714 continue;
715 }
716 (void)fclose(fp);
717 set_log_file_ownership(fname);
718 if (memcmp(qd->bar4_base + log_offset, "hashid", 6) == 0)
719 {
720 char hstr[17] = {0};
721 memcpy(hstr, qd->bar4_base + log_offset, 16);
722 (void)fprintf(stderr, "%s: %s\n", core, hstr);
723 }
724 (void)fprintf(stderr, "[INFO] Dumped persistent log: %s\n", fname);
725 }
726 return rc;
727}
728
729/*!*****************************************************************************
730 * \brief Check whether a PCI sysfs path points to a Quadra device.
731 *
732 * Compare sysfs vendor and device IDs against the NETINT
733 * vendor ID (0x1d82) and Quadra device ID (0x0401).
734 *
735 * \param[in] sysfs_path Device sysfs path, ex:/sys/bus/pci/devices/D:B:D:F).
736 *
737 * \return 0 - the path is a Quadra device,
738 * -1 - missing path / wrong vendor/device ID
739 ******************************************************************************/
740static int validate_quadra_sysfs(const char *sysfs_path)
741{
742 char vpath[PATH_MAX], dpath[PATH_MAX], buf[32];
743
744 if (snprintf(vpath, sizeof(vpath), "%s/vendor", sysfs_path) < 0 ||
745 snprintf(dpath, sizeof(dpath), "%s/device", sysfs_path) < 0)
746 return -1;
747
748 FILE *f = fopen(vpath, "r");
749 if (!f)
750 {
751 (void)fprintf(stderr, "[ERROR] No PCI device at %s\n", sysfs_path);
752 return -1;
753 }
754 if (!fgets(buf, sizeof(buf), f)) { (void)fclose(f); return -1; }
755 (void)fclose(f);
756 if (strtoul(buf, NULL, 0) != QUADRA_VENDOR_ID)
757 {
758 (void)fprintf(stderr, "[ERROR] Device at %s is not a NETINT device.\n",
759 sysfs_path);
760 return -1;
761 }
762
763 f = fopen(dpath, "r");
764 if (!f) return -1;
765 if (!fgets(buf, sizeof(buf), f)) { (void)fclose(f); return -1; }
766 (void)fclose(f);
767 if (strtoul(buf, NULL, 0) != QUADRA_DEVICE_ID)
768 {
769 (void)fprintf(stderr, "[ERROR] Device at %s is not a Quadra device.\n",
770 sysfs_path);
771 return -1;
772 }
773 return 0;
774}
775
776/*!*****************************************************************************
777 * \brief Find a single Quadra device by PCI slot (and domain)
778 *
779 * \param[in] domain_str domain, default "0000".
780 * \param[in] slot_str slot e.g. "01"
781 * \param[out] out allocated QuadraDevice structure
782 *
783 * \return 0 - device found
784 * -1 - failure
785 ******************************************************************************/
786int quadra_device_find_by_bdf(const char *domain_str, const char *slot_str,
787 QuadraDevice **out)
788{
789 if (!slot_str || !out)
790 return -1;
791
792 unsigned int domain = domain_str ? (unsigned int)strtoul(domain_str, NULL, 16) : 0;
793 unsigned int bus = (unsigned int)strtoul(slot_str, NULL, 16);
794
795 if (domain > 0xFFFF || bus > 0xFF)
796 {
797 (void)fprintf(stderr, "[ERROR] --domain or -s value out of range.\n");
798 return -1;
799 }
800
801 char bdf[32];
802 (void)snprintf(bdf, sizeof(bdf), "%04x:%02x:00.0", domain, bus);
803
804 char sysfs_path[PATH_MAX];
805 (void)snprintf(sysfs_path, sizeof(sysfs_path), "%s/%s", PCI_SYSFS_PATH, bdf);
806
807 if (validate_quadra_sysfs(sysfs_path) != 0)
808 return -1;
809
810 QuadraDevice *qd = calloc(1, sizeof(*qd));
811 if (!qd) return -1;
812
813 if (bdf_to_sysfs(bdf, qd->sysfs_path, sizeof(qd->sysfs_path),
814 &qd->domain, &qd->bus, &qd->slot, &qd->func) != 0)
815 {
816 free(qd);
817 return -1;
818 }
819 qd->bar4_fd = -1;
820 *out = qd;
821 return 0;
822}
823
824/*!*****************************************************************************
825 * \brief Find a single Quadra device by its NVMe block-device name.
826 *
827 * \param[in] blkdev NVMe block-device name
828 * \param[out] out allocated QuadraDevice struct
829 *
830 * \return 0 - device found,
831 * -1 - not a valid Quadra card
832 ******************************************************************************/
833int quadra_device_find_by_block_dev(const char *blkdev, QuadraDevice **out)
834{
835 if (!blkdev || !out)
836 return -1;
837
838 /* use readlink system call */
839 char link_path[PATH_MAX];
840 if (snprintf(link_path, sizeof(link_path),
841 "/sys/block/%s/device/device", blkdev) < 0)
842 return -1;
843
844 char link_target[PATH_MAX];
845 ssize_t len = readlink(link_path, link_target, sizeof(link_target) - 1);
846 if (len < 0)
847 {
848 (void)fprintf(stderr, "[ERROR] Block device '%s' not found.\n", blkdev);
849 return -1;
850 }
851 link_target[len] = '\0';
852
853 /* Extract the last path component (the BDF). */
854 char *last_slash = strrchr(link_target, '/');
855 const char *bdf = last_slash ? last_slash + 1 : link_target;
856
857 char sysfs_path[PATH_MAX];
858 if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/%s", PCI_SYSFS_PATH, bdf) < 0)
859 return -1;
860
861 if (validate_quadra_sysfs(sysfs_path) != 0)
862 {
863 (void)fprintf(stderr, "[ERROR] '%s' does not correspond to a Quadra device.\n",
864 blkdev);
865 return -1;
866 }
867
868 QuadraDevice *qd = calloc(1, sizeof(*qd));
869 if (!qd) return -1;
870
871 if (bdf_to_sysfs(bdf, qd->sysfs_path, sizeof(qd->sysfs_path),
872 &qd->domain, &qd->bus, &qd->slot, &qd->func) != 0)
873 {
874 free(qd);
875 return -1;
876 }
877 qd->bar4_fd = -1;
878 *out = qd;
879 return 0;
880}
881
882/*!*****************************************************************************
883 * \brief Read log artifacts tar.gz from DDR via BAR4 and write to disk.
884 *
885 * [0 .. 3] - uint32_t artifacts data size
886 * [4 .. 4+size-1] - tar.gz payload
887 *
888 * Returns 0 when:
889 * - size header is 0 (firmware too old / artifacts not loaded)
890 * - size exceeds maximum (stale DDR data)
891 * - gzip magic mismatch (stale DDR data / unsupported firmware)
892 * - success
893 * Returns -1 on I/O error.
894 ******************************************************************************/
895static int dump_artifacts(QuadraDevice *qd, const char *outdir)
896{
897 const uint8_t *artifacts_base = qd->bar4_base + qd->log_artifacts_offset;
898
899 uint32_t artifacts_size;
900 memcpy(&artifacts_size, artifacts_base, sizeof(artifacts_size));
901
902 if (artifacts_size == 0)
903 {
904 (void)fprintf(stderr, "[INFO] Artifacts: size header is 0 - artifacts were not loaded into DDR.\n");
905 return 0;
906 }
907
908 uint32_t max_data_size = LOG_ARTIFACTS_MAX_SIZE - CPU_LOG_ARTIFACTS_HEADER_SIZE;
909 if (artifacts_size > max_data_size)
910 {
911 (void)fprintf(stderr, "[WARN] Artifacts: size %u exceeds max %u size - stale DDR data or artifacts not loaded.\n",
912 artifacts_size, max_data_size);
913 return 0;
914 }
915
916 const uint8_t *data = artifacts_base + CPU_LOG_ARTIFACTS_HEADER_SIZE;
917 if (data[0] != 0x1f || data[1] != 0x8b)
918 {
919 (void)fprintf(stderr, "[WARN] Artifacts: invalid gzip magic 0x%02x 0x%02x - stale DDR data or artifacts not loaded\n",
920 data[0], data[1]);
921 return 0;
922 }
923
924 char fname[PATH_MAX];
925 if (snprintf(fname, sizeof(fname), "%s/artifacts_slot_%02x_%04x.tar.gz",
926 outdir, qd->bus, qd->domain) < 0)
927 return -1;
928
929 FILE *fp = fopen(fname, "wb");
930 if (!fp)
931 {
932 perror("fopen(artifacts)");
933 return -1;
934 }
935 if (fwrite(data, 1, artifacts_size, fp) != artifacts_size)
936 {
937 (void)fprintf(stderr, "[ERROR] artifacts write failed on %s\n", fname);
938 (void)fclose(fp);
939 return -1;
940 }
941 (void)fclose(fp);
942 set_log_file_ownership(fname);
943 (void)fprintf(stderr, "[INFO] Artifacts saved: %s (%u bytes)\n", fname, artifacts_size);
944 return 0;
945}
946
947int ni_rsrc_log_dump(const char *outdir, bool core_reset_log)
948{
949 QuadraDevice *devs = NULL;
950 int ndev = quadra_device_find(&devs);
951 if (ndev <= 0)
952 {
953 free(devs);
954 (void)fprintf(stderr, "[WARN] No Quadra devices found in sysfs (vendor 1d82).\n");
955 return 1;
956 }
957 int fail_count = 0;
958 for (int i = 0; i < ndev; ++i)
959 {
960 if (quadra_init_mmap(&devs[i]) == 0)
961 {
962 int rc = dump_raw_logs(&devs[i], outdir ? outdir : ".", core_reset_log);
963 fail_count += (rc != 0);
964 dump_artifacts(&devs[i], outdir ? outdir : ".");
965 quadra_cleanup(&devs[i]);
966 }
967 else
968 {
969 (void)fprintf(stderr, "[ERROR] Failed to mmap device %d\n", i);
970 fail_count++;
971 }
972 }
973 free(devs);
974 return (fail_count == 0) ? 0 : 2;
975}
976
977/*!*****************************************************************************
978 * \brief Convert a user nor command string to NOR_CMD_* command.
979 *
980 * \param[in] nor_cmd "w", "r", "e", or "ex"
981 * \param[out] out_cmd Receives the NOR_CMD_*
982 *
983 * \return 0 - on success,
984 * -1 - on unknown command.
985 ******************************************************************************/
986int nor_cmd_decode(const char *nor_cmd, uint32_t *out_cmd)
987{
988 if (!nor_cmd || !out_cmd)
989 return -1;
990
991 if (strcmp(nor_cmd, "w") == 0)
992 {
993 *out_cmd = NOR_CMD_WRITE;
994 return 0;
995 }
996 if (strcmp(nor_cmd, "r") == 0)
997 {
998 *out_cmd = NOR_CMD_READ;
999 return 0;
1000 }
1001 if (strcmp(nor_cmd, "e") == 0)
1002 {
1003 *out_cmd = NOR_CMD_ERASE;
1004 return 0;
1005 }
1006 if (strcmp(nor_cmd, "ex") == 0)
1007 {
1008 *out_cmd = NOR_CMD_EXCEPTION;
1009 return 0;
1010 }
1011
1012 (void)fprintf(stderr,
1013 "[ERROR] Unknown NOR command '%s'. Use w, r, e, or ex.\n", nor_cmd);
1014 return -1;
1015}
1016
1017/*!*****************************************************************************
1018 * \brief Run a NOR command across an identified list of Quadra devices.
1019 *
1020 * NOR_CMD_READ - waits for 100s, single wait for all cards
1021 * NOR_CMD_WRITE / ERASE / EXCEPTION - returns immediately
1022 *
1023 * \param[in] devs Identified Quadra device list
1024 * \param[in] ndev Number of devices
1025 * \param[in] cmd NOR_CMD_*
1026 * \param[in] outdir Destination directory
1027 *
1028 * \return 0 - command succeeded on every device,
1029 * 1 - invalid args,
1030 * 2 - one or more devices failed,
1031 * -1 - failure.
1032 ******************************************************************************/
1033int ni_rsrc_nor_cmd_run(QuadraDevice *devs, int ndev,
1034 uint32_t cmd, const char *outdir)
1035{
1036 if (!devs || ndev <= 0)
1037 return 1;
1038
1039 const char *odir = outdir ? outdir : ".";
1040 int fail_count = 0;
1041
1042 if (cmd == NOR_CMD_READ)
1043 {
1044 /* NOR read: 100s wait so nor read & dump from BAR4 happens fully */
1045 bool *ready = (bool *)calloc((size_t)ndev, sizeof(bool));
1046 if (!ready)
1047 return -1;
1048
1049 /* mmap all devices and dispatch NOR_CMD_READ to each. */
1050 for (int i = 0; i < ndev; ++i)
1051 {
1052 if (quadra_init_mmap(&devs[i]) != 0)
1053 {
1054 (void)fprintf(stderr,
1055 "[ERROR] Failed to mmap device %d\n", i);
1056 fail_count++;
1057 continue;
1058 }
1059 if (quadra_nor_dispatch(&devs[i], NOR_CMD_READ) != 0)
1060 {
1061 (void)fprintf(stderr,
1062 "[ERROR] NOR read dispatch failed on device %d\n",
1063 i);
1064 quadra_cleanup(&devs[i]);
1065 fail_count++;
1066 continue;
1067 }
1068 (void)fprintf(stderr,
1069 "[INFO] NOR read dispatched to device %d\n", i);
1070 ready[i] = true;
1071 }
1072
1073 /* Single wait — covers all dispatched cards in parallel */
1074 quadra_nor_read_wait();
1075
1076 /* Dump logs from every device that dispatched successfully */
1077 for (int i = 0; i < ndev; ++i)
1078 {
1079 if (!ready[i])
1080 continue;
1081 fill_core_log_offsets(&devs[i]);
1082 int rc = dump_nor_logs(&devs[i], odir);
1083 fail_count += (rc != 0);
1084 (void)fprintf(stderr, "[INFO] NOR flash command complete.\n");
1085 quadra_cleanup(&devs[i]);
1086 }
1087
1088 free(ready);
1089 }
1090 else
1091 {
1092 /* w / e / ex: each call returns immediately */
1093 for (int i = 0; i < ndev; ++i)
1094 {
1095 if (quadra_init_mmap(&devs[i]) != 0)
1096 {
1097 (void)fprintf(stderr,
1098 "[ERROR] Failed to mmap device %d\n", i);
1099 fail_count++;
1100 continue;
1101 }
1102 if (quadra_nor_command(&devs[i], cmd) != 0)
1103 {
1104 (void)fprintf(stderr,
1105 "[ERROR] NOR command failed on device %d\n", i);
1106 quadra_cleanup(&devs[i]);
1107 fail_count++;
1108 continue;
1109 }
1110 quadra_cleanup(&devs[i]);
1111 }
1112 }
1113
1114 return (fail_count == 0) ? 0 : 2;
1115}
1116
1117/*!*****************************************************************************
1118 * \brief Execute a NOR flash persistent log command on all detected devices.
1119 * Enumerate every Quadra card on the host, decodes nor_commands (read,
1120 * write, erase, exception) and dispatches them.
1121 *
1122 * \param[in] nor_cmd "w", "r", "e", or "ex"
1123 * \param[in] outdir Output directory
1124 *
1125 * \return 0 - all succeeded,
1126 * 1 - no devices,
1127 * 2 - partial failure,
1128 * -1 - invalid args.
1129 ******************************************************************************/
1130int ni_rsrc_nor_log_command(const char *nor_cmd, const char *outdir)
1131{
1132 uint32_t cmd;
1133 if (nor_cmd_decode(nor_cmd, &cmd) != 0)
1134 return -1;
1135
1136 QuadraDevice *devs = NULL;
1137 int ndev = quadra_device_find(&devs);
1138 if (ndev <= 0)
1139 {
1140 free(devs);
1141 (void)fprintf(stderr,
1142 "[WARN] No Quadra devices found in sysfs (vendor 1d82).\n");
1143 return 1;
1144 }
1145
1146 int rc = ni_rsrc_nor_cmd_run(devs, ndev, cmd, outdir);
1147 free(devs);
1148 return rc;
1149}
1150#endif
#define LOG_ARTIFACTS_MAX_SIZE
Definition ni_defs.h:378
#define FL_SIGNATURE
Definition ni_defs.h:381
#define CPU_LOG_ARTIFACTS_HEADER_SIZE
Definition ni_defs.h:377
#define NI_ERRNO_LEN
Definition ni_log.h:51
#define CORE_RESET_LOG_SIZE
#define CORE_LOG_SIZE
#define QUADRA_VENDOR_ID
#define FL_CORE_IDX
#define CORE_LOG_PTR_TABLE_OFFSET
#define ARTIFACTS_BAR4_OFFSET
#define FL_LOG_ENTRY_SIZE
#define NOR_FLASH_CMD_OFFSET
#define CORE_RESET_LOG_PTR_TABLE_OFFSET
#define QUADRA_DEVICE_ID
#define NOR_READ_WAIT_SECONDS
#define PCI_SYSFS_PATH
Quadraprobe definitions.
ni_retcode_t ni_strerror(char *dest, size_t dmax, int errnum)
Definition ni_util.c:663
void ni_usleep(int64_t usec)
Definition ni_util.c:366
Utility definitions.