From f909a5f822c5c4a4e9526b861d4171f8925f5113 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Fri, 2 Oct 2026 10:58:21 -0400 Subject: debug: green-eye wave 3 - RM harness, local simulator, device map, MCP plugin, value-validation demos, CI pipeline, toolchain docs; add host RM tools rmtool/rmtop --- tools/rmtool/src/gpu.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 tools/rmtool/src/gpu.c (limited to 'tools/rmtool/src/gpu.c') diff --git a/tools/rmtool/src/gpu.c b/tools/rmtool/src/gpu.c new file mode 100644 index 0000000..ff9b26c --- /dev/null +++ b/tools/rmtool/src/gpu.c @@ -0,0 +1,283 @@ +/* gpu.c - rmtool gpu list/topology/info: enumeration and identity queries. */ +#include +#include +#include +#include + +#include "rmtool.h" + +static int read_sysfs(const char *bdf, const char *file, char *buf, size_t len) +{ + char path[512]; + FILE *fh; + + snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/%s", bdf, file); + fh = fopen(path, "r"); + if (fh == NULL) { + buf[0] = '\0'; + return -1; + } + if (fgets(buf, (int)len, fh) == NULL) + buf[0] = '\0'; + fclose(fh); + buf[strcspn(buf, "\r\n")] = '\0'; + return 0; +} + +static int parse_common(int argc, char **argv, struct rm_ctx *ctx) +{ + static const struct option long_opts[] = { + { "device-spec", required_argument, NULL, 'd' }, + { "format", required_argument, NULL, 'f' }, + { "device", required_argument, NULL, 1000 }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + int opt; + + optind = 1; + opterr = 0; + while ((opt = getopt_long(argc, argv, "d:f:h", long_opts, NULL)) != -1) { + int rc; + + if (opt == 'h') + return 1; + rc = rm_apply_common_opt(ctx, opt, optarg); + if (rc < 0) + return -1; + if (rc == 0) { + fprintf(stderr, "rmtool: unknown option\n"); + return -1; + } + } + if (optind < argc) { + fprintf(stderr, "rmtool: unexpected argument '%s'\n", + argv[optind]); + return -1; + } + return 0; +} + +static void list_usage(void) +{ + printf("usage: rmtool gpu list [--format=table|json|csv]\n" + " rmtool gpu topology [--format=...]\n" + " rmtool gpu info [-d N|BDF] [--format=...]\n"); +} + +static int gpu_list(struct rm_ctx *ctx) +{ + struct rm_gpu_desc gpus[RM_MAX_GPUS]; + const char *headers[] = { "MINOR", "BUS", "MODEL", "UUID", "VBIOS" }; + char cells[RM_MAX_GPUS * 5][RM_UUID_LEN + 8]; + int n; + int i; + + n = rm_scan_gpus(gpus, RM_MAX_GPUS); + if (n < 0) { + fprintf(stderr, "rmtool: cannot read /proc/driver/nvidia/gpus: " + "%s\n", strerror(-n)); + return 2; + } + for (i = 0; i < n; i++) { + snprintf(cells[i * 5 + 0], sizeof(cells[0]), "%d", + gpus[i].minor); + snprintf(cells[i * 5 + 1], sizeof(cells[0]), "%s", + gpus[i].bdf); + snprintf(cells[i * 5 + 2], sizeof(cells[0]), "%s", + gpus[i].model); + snprintf(cells[i * 5 + 3], sizeof(cells[0]), "%s", + gpus[i].uuid); + snprintf(cells[i * 5 + 4], sizeof(cells[0]), "%s", + gpus[i].vbios); + } + if (ctx->format == RM_FMT_TABLE) + printf("%d GPU(s) on this host\n", n); + rm_print_table("gpus", headers, &cells[0][0], sizeof(cells[0]), n, 5, + ctx->format); + return 0; +} + +static int gpu_topology(struct rm_ctx *ctx) +{ + struct rm_gpu_desc gpus[RM_MAX_GPUS]; + const char *headers[] = { "MINOR", "BUS", "LINK", "MAX LINK", "NUMA", + "VENDOR:DEVICE" }; + char cells[RM_MAX_GPUS * 6][RM_UUID_LEN]; + int n; + int i; + + n = rm_scan_gpus(gpus, RM_MAX_GPUS); + if (n < 0) { + fprintf(stderr, "rmtool: cannot read /proc/driver/nvidia/gpus: " + "%s\n", strerror(-n)); + return 2; + } + for (i = 0; i < n; i++) { + char speed[32], width[32], max_speed[32], max_width[32]; + char numa[16], vendor[32], device[32]; + + read_sysfs(gpus[i].bdf, "current_link_speed", speed, + sizeof(speed)); + read_sysfs(gpus[i].bdf, "current_link_width", width, + sizeof(width)); + read_sysfs(gpus[i].bdf, "max_link_speed", max_speed, + sizeof(max_speed)); + read_sysfs(gpus[i].bdf, "max_link_width", max_width, + sizeof(max_width)); + read_sysfs(gpus[i].bdf, "numa_node", numa, sizeof(numa)); + read_sysfs(gpus[i].bdf, "vendor", vendor, sizeof(vendor)); + read_sysfs(gpus[i].bdf, "device", device, sizeof(device)); + snprintf(cells[i * 6 + 0], sizeof(cells[0]), "%d", + gpus[i].minor); + snprintf(cells[i * 6 + 1], sizeof(cells[0]), "%s", + gpus[i].bdf); + snprintf(cells[i * 6 + 2], sizeof(cells[0]), "%s x%s", + speed, width); + snprintf(cells[i * 6 + 3], sizeof(cells[0]), "%s x%s", + max_speed, max_width); + snprintf(cells[i * 6 + 4], sizeof(cells[0]), "%s", numa); + snprintf(cells[i * 6 + 5], sizeof(cells[0]), "%s:%s", vendor, + device); + } + rm_print_table("gpu topology (sysfs, read-only)", headers, + &cells[0][0], sizeof(cells[0]), n, 6, ctx->format); + return 0; +} + +static int gpu_info(struct rm_ctx *ctx) +{ + struct rm_gpu_desc gpus[RM_MAX_GPUS]; + NV2080_CTRL_MC_ARCH_INFO_PARAMS arch; + NV2080_CTRL_BUS_GET_PCI_INFO_PARAMS pci; + NV2080_CTRL_GPU_GET_ENGINES_V2_PARAMS engines; + struct rm_kv kv[20]; + char v[20][96]; + int n = 0; + int i, n_gpus, rc; + NvU32 status; + + n_gpus = rm_scan_gpus(gpus, RM_MAX_GPUS); + rc = rm_ctx_open(ctx); + if (rc != 0) { + fprintf(stderr, "rmtool: cannot open RM client " + "(need the driver bound and read access): rc=%d\n", rc); + return 2; + } + + status = rm_query_name(ctx, v[0], sizeof(v[0])); + kv[n].key = "Name"; + kv[n++].value = status == 0 ? v[0] : "query failed"; + + memset(v[1], 0, sizeof(v[1])); + snprintf(v[1], sizeof(v[1]), "%d", ctx->rm.gpu_minor); + kv[n].key = "GPU minor"; + kv[n++].value = v[1]; + + if (n_gpus > 0) { + const struct rm_gpu_desc *g = NULL; + + for (i = 0; i < n_gpus; i++) { + if (gpus[i].minor == ctx->rm.gpu_minor) + g = &gpus[i]; + } + if (g == NULL) + g = &gpus[0]; + kv[n].key = "Model (proc)"; + kv[n++].value = g->model; + kv[n].key = "Bus location"; + kv[n++].value = g->bdf; + kv[n].key = "GPU UUID"; + kv[n++].value = g->uuid; + kv[n].key = "Video BIOS"; + kv[n++].value = g->vbios; + } + + status = rm_query_mc_arch(ctx, &arch); + if (status == 0) { + snprintf(v[2], sizeof(v[2]), "%s (0x%x/0x%x rev %u)", + rm_arch_str(arch.architecture), arch.architecture, + arch.implementation, arch.revision); + kv[n].key = "Architecture"; + kv[n++].value = v[2]; + } else { + snprintf(v[2], sizeof(v[2]), "MC_GET_ARCH_INFO: %s", + rm_status_str(status)); + kv[n].key = "Architecture"; + kv[n++].value = v[2]; + } + + status = rm_query_pci(ctx, &pci); + if (status == 0) { + snprintf(v[3], sizeof(v[3]), "%04x:%04x (subsystem %04x)", + pci.pciDeviceId & 0xffff, + (pci.pciDeviceId >> 16) & 0xffff, + pci.pciSubSystemId); + kv[n].key = "PCI device"; + kv[n++].value = v[3]; + snprintf(v[4], sizeof(v[4]), "%u", pci.pciRevisionId); + kv[n].key = "PCI revision"; + kv[n++].value = v[4]; + } + + status = rm_query_engines(ctx, &engines); + snprintf(v[5], sizeof(v[5]), "%s", + status == 0 ? "ok" : rm_status_str(status)); + kv[n].key = "Engine inventory"; + kv[n++].value = v[5]; + if (status == 0) { + snprintf(v[6], sizeof(v[6]), "%u", engines.engineCount); + kv[n].key = "Engine count"; + kv[n++].value = v[6]; + } + + rm_print_kv("rmtool gpu info (read-only RM queries)", kv, n, + ctx->format); + return 0; +} + +int cmd_gpu(int argc, char **argv, struct rm_ctx *ctx) +{ + const char *sub; + + if (argc < 2) { + list_usage(); + return 2; + } + sub = argv[1]; + if (strcmp(sub, "list") == 0) { + int rc = parse_common(argc - 1, argv + 1, ctx); + + if (rc < 0) + return 2; + if (rc == 1) { + list_usage(); + return 0; + } + return gpu_list(ctx); + } + if (strcmp(sub, "topology") == 0) { + int rc = parse_common(argc - 1, argv + 1, ctx); + + if (rc < 0) + return 2; + if (rc == 1) { + list_usage(); + return 0; + } + return gpu_topology(ctx); + } + if (strcmp(sub, "info") == 0) { + int rc = parse_common(argc - 1, argv + 1, ctx); + + if (rc < 0) + return 2; + if (rc == 1) { + list_usage(); + return 0; + } + return gpu_info(ctx); + } + list_usage(); + return 2; +} -- cgit v1.2.3