diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
| commit | 002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch) | |
| tree | 6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /tools/rmtool/src/nv2080.c | |
| parent | 2f249817df327f5dcccf0464ad517fa60d6071d3 (diff) | |
| download | open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.tar.gz open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.zip | |
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
Diffstat (limited to 'tools/rmtool/src/nv2080.c')
| -rw-r--r-- | tools/rmtool/src/nv2080.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/tools/rmtool/src/nv2080.c b/tools/rmtool/src/nv2080.c new file mode 100644 index 0000000..5dd808e --- /dev/null +++ b/tools/rmtool/src/nv2080.c @@ -0,0 +1,398 @@ +/* nv2080.c - rmtool nv2080 class list / method call and sample. + * + * The generic control entry point is restricted to a read-only allowlist so + * the tools can never mutate the live GPU or driver state. Anything else is + * refused before the ioctl is made. + */ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "rmtool.h" + +static const NvU32 readonly_cmds[] = { + NV2080_CTRL_CMD_GPU_GET_INFO_V2, + NV2080_CTRL_CMD_GPU_GET_NAME_STRING, + NV2080_CTRL_CMD_GPU_QUERY_MODE, + NV2080_CTRL_CMD_GPU_GET_ID, + NV2080_CTRL_CMD_GPU_GET_ENGINES_V2, + NV2080_CTRL_CMD_MC_GET_ARCH_INFO, + NV2080_CTRL_CMD_FB_GET_INFO_V2, + NV2080_CTRL_CMD_BUS_GET_PCI_INFO, + NV2080_CTRL_CMD_TIMER_GET_TIME, + NV2080_CTRL_CMD_FIFO_GET_DEVICE_INFO_TABLE, + NV0080_CTRL_CMD_GPU_GET_CLASSLIST_V2, +}; + +int rm_control_is_readonly(NvU32 cmd) +{ + unsigned int i; + + for (i = 0; i < sizeof(readonly_cmds) / sizeof(readonly_cmds[0]); i++) { + if (readonly_cmds[i] == cmd) + return 1; + } + return 0; +} + +static const char *class_name(NvU32 klass) +{ + switch (klass) { + case NV01_DEVICE_0: + return "NV01_DEVICE_0"; + case NV20_SUBDEVICE_0: + return "NV20_SUBDEVICE_0"; + default: + return "-"; + } +} + +static void usage(void) +{ + printf("usage: rmtool nv2080 class list [-d N|BDF] [--format=...]\n" + " rmtool nv2080 method call -m CMD [--object=OBJ] " + "[--params=HEX]\n" + "OBJ is subdevice (default), device or client. CMD must be on " + "the\nread-only allowlist; write controls are refused.\n" + " rmtool sample timer [-d N|BDF] [--interval=100ms] " + "[--count=N]\n"); +} + +static int class_list(struct rm_ctx *ctx) +{ + NV0080_CTRL_GPU_GET_CLASSLIST_V2_PARAMS classes; + const char *headers[] = { "IDX", "CLASS", "NAME" }; + char cells[NV0080_CTRL_GPU_CLASSLIST_MAX_SIZE * 3][32]; + unsigned int i; + int rc, ioctl_ret; + NvU32 status; + + rc = rm_ctx_open(ctx); + if (rc != 0) { + fprintf(stderr, "rmtool: cannot open RM client: rc=%d\n", rc); + return 2; + } + memset(&classes, 0, sizeof(classes)); + status = rm_control(&ctx->rm, ctx->rm.hDevice, + NV0080_CTRL_CMD_GPU_GET_CLASSLIST_V2, &classes, + sizeof(classes), &ioctl_ret); + if (status != 0) { + fprintf(stderr, "rmtool: GPU_GET_CLASSLIST_V2: %s\n", + rm_status_str(status)); + return 2; + } + if (classes.numClasses > NV0080_CTRL_GPU_CLASSLIST_MAX_SIZE) + classes.numClasses = NV0080_CTRL_GPU_CLASSLIST_MAX_SIZE; + for (i = 0; i < classes.numClasses; i++) { + snprintf(cells[i * 3 + 0], sizeof(cells[0]), "%u", i); + snprintf(cells[i * 3 + 1], sizeof(cells[0]), "0x%x", + classes.classList[i]); + snprintf(cells[i * 3 + 2], sizeof(cells[0]), "%s", + class_name(classes.classList[i])); + } + rm_print_table("device classes (NV0080_CTRL_GPU_GET_CLASSLIST_V2)", + headers, &cells[0][0], sizeof(cells[0]), + (int)classes.numClasses, 3, ctx->format); + return 0; +} + +static int parse_hex(const char *hex, unsigned char **out, size_t *len) +{ + size_t n, i; + unsigned char *buf; + + if (hex == NULL) { + *out = NULL; + *len = 0; + return 0; + } + if (hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) + hex += 2; + n = strlen(hex); + if (n % 2 != 0) { + fprintf(stderr, "rmtool: --params must be whole hex bytes\n"); + return -1; + } + buf = malloc(n / 2 + 1); + if (buf == NULL) + return -1; + for (i = 0; i < n / 2; i++) { + unsigned int byte; + + if (sscanf(hex + i * 2, "%2x", &byte) != 1) { + fprintf(stderr, "rmtool: bad hex byte '%c%c'\n", + hex[i * 2], hex[i * 2 + 1]); + free(buf); + return -1; + } + buf[i] = (unsigned char)byte; + } + *out = buf; + *len = n / 2; + return 0; +} + +static int method_call(struct rm_ctx *ctx, NvU32 cmd, const char *object, + const char *hex) +{ + unsigned char *params; + size_t len; + NvU32 hObject; + int rc, ioctl_ret; + NvU32 status; + + if (!rm_control_is_readonly(cmd)) { + fprintf(stderr, "rmtool: control 0x%x is not on the read-only " + "allowlist; refused (see docs/17-RM-HARNESS.md)\n", + cmd); + return 4; + } + if (parse_hex(hex, ¶ms, &len) < 0) + return 2; + rc = rm_ctx_open(ctx); + if (rc != 0) { + fprintf(stderr, "rmtool: cannot open RM client: rc=%d\n", rc); + free(params); + return 2; + } + if (object == NULL || strcmp(object, "subdevice") == 0) + hObject = ctx->rm.hSubdevice; + else if (strcmp(object, "device") == 0) + hObject = ctx->rm.hDevice; + else if (strcmp(object, "client") == 0) + hObject = ctx->rm.hClient; + else { + fprintf(stderr, "rmtool: unknown --object '%s'\n", object); + free(params); + return 2; + } + status = rm_control(&ctx->rm, hObject, cmd, params, (NvU32)len, + &ioctl_ret); + printf("cmd=0x%x object=0x%x params=%zu bytes\n", cmd, hObject, len); + if (ioctl_ret != 0) { + fprintf(stderr, "rmtool: control ioctl failed: %s\n", + strerror(-ioctl_ret)); + free(params); + return 2; + } + printf("status=%s\n", rm_status_str(status)); + if (status == 0 && len > 0) + rm_hexdump(params, len); + free(params); + return status == 0 ? 0 : 2; +} + +static double parse_interval_ms(const char *s) +{ + char *end; + double v = strtod(s, &end); + + if (end != NULL && strcmp(end, "ms") == 0) + return v; + if (end != NULL && strcmp(end, "s") == 0) + return v * 1000.0; + if (end != NULL && strcmp(end, "us") == 0) + return v / 1000.0; + return v; +} + +static int sample_timer(struct rm_ctx *ctx, double interval_ms, int count) +{ + int rc, i; + + rc = rm_ctx_open(ctx); + if (rc != 0) { + fprintf(stderr, "rmtool: cannot open RM client: rc=%d\n", rc); + return 2; + } + for (i = 0; i < count; i++) { + NV2080_CTRL_TIMER_GET_TIME_PARAMS p; + struct tm tm; + char when[32]; + time_t secs; + int ioctl_ret; + NvU32 status; + + memset(&p, 0, sizeof(p)); + status = rm_control(&ctx->rm, ctx->rm.hSubdevice, + NV2080_CTRL_CMD_TIMER_GET_TIME, &p, + sizeof(p), &ioctl_ret); + if (status != 0 || ioctl_ret != 0) { + fprintf(stderr, "rmtool: TIMER_GET_TIME: %s\n", + rm_status_str(status)); + return 2; + } + secs = (time_t)(p.time_nsec / 1000000000ULL); + gmtime_r(&secs, &tm); + strftime(when, sizeof(when), "%Y-%m-%dT%H:%M:%S", &tm); + printf("gpu_time_ns=%llu (%s.%09lluZ)\n", + (unsigned long long)p.time_nsec, when, + (unsigned long long)(p.time_nsec % 1000000000ULL)); + fflush(stdout); + if (i + 1 < count) { + struct timespec req; + + req.tv_sec = (time_t)(interval_ms / 1000.0); + req.tv_nsec = (long)((interval_ms - + (double)req.tv_sec * 1000.0) * + 1000000.0); + nanosleep(&req, NULL); + } + } + return 0; +} + +static int unsupported_sample(const char *kind) +{ + fprintf(stderr, + "rmtool: 'sample %s' is not implemented in this minimal build: " + "it needs NVML-class perfmon/thermal/power controls whose " + "structs are not exposed in the in-tree SDK. Use nvidia-smi " + "for now; see debug/risc-v-gsp-rm-vm/docs/17-RM-HARNESS.md.\n", + kind); + return 3; +} + +int cmd_nv2080(int argc, char **argv, struct rm_ctx *ctx) +{ + int opt; + + if (argc < 3) { + usage(); + return 2; + } + if (strcmp(argv[1], "class") == 0 && strcmp(argv[2], "list") == 0) { + 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 }, + }; + + optind = 1; + opterr = 0; + while ((opt = getopt_long(argc - 2, argv + 2, "d:f:h", + long_opts, NULL)) != -1) { + if (opt == 'h') { + usage(); + return 0; + } + if (rm_apply_common_opt(ctx, opt, optarg) < 0) + return 2; + } + return class_list(ctx); + } + if (strcmp(argv[1], "method") == 0 && strcmp(argv[2], "call") == 0) { + static const struct option long_opts[] = { + { "cmd", required_argument, NULL, 'm' }, + { "method", required_argument, NULL, 'm' }, + { "class", required_argument, NULL, 'c' }, + { "object", required_argument, NULL, 'o' }, + { "params", required_argument, NULL, 'p' }, + { "format", required_argument, NULL, 'f' }, + { "device-spec", required_argument, NULL, 'd' }, + { "device", required_argument, NULL, 1000 }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + const char *object = NULL; + const char *params = NULL; + const char *klass = NULL; + NvU32 cmd = 0; + int have_cmd = 0; + + optind = 1; + opterr = 0; + while ((opt = getopt_long(argc - 2, argv + 2, "m:c:o:p:f:d:h", + long_opts, NULL)) != -1) { + if (opt == 'h') { + usage(); + return 0; + } + if (opt == 'm') { + cmd = (NvU32)strtoul(optarg, NULL, 0); + have_cmd = 1; + continue; + } + if (opt == 'o') { + object = optarg; + continue; + } + if (opt == 'p') { + params = optarg; + continue; + } + if (opt == 'c') { + klass = optarg; + continue; + } + if (rm_apply_common_opt(ctx, opt, optarg) < 0) + return 2; + } + if (!have_cmd) { + usage(); + return 2; + } + if (klass != NULL) + fprintf(stderr, "rmtool: note: --class %s is accepted " + "for documentation parity; the RM control " + "command is sent as given with -m.\n", klass); + return method_call(ctx, cmd, object, params); + } + if (strcmp(argv[1], "event") == 0) { + fprintf(stderr, "rmtool: nv2080 event subscribe/wait is not " + "implemented (event handling is a write path).\n"); + return 3; + } + usage(); + return 2; +} + +int cmd_sample(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' }, + { "interval", required_argument, NULL, 'i' }, + { "count", required_argument, NULL, 'n' }, + { "device", required_argument, NULL, 1000 }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + double interval_ms = 1000.0; + int count = 1; + int opt; + + if (argc < 2) { + usage(); + return 2; + } + if (strcmp(argv[1], "timer") != 0) + return unsupported_sample(argv[1]); + optind = 1; + opterr = 0; + while ((opt = getopt_long(argc - 1, argv + 1, "d:f:i:n:h", + long_opts, NULL)) != -1) { + if (opt == 'h') { + usage(); + return 0; + } + if (opt == 'i') { + interval_ms = parse_interval_ms(optarg); + continue; + } + if (opt == 'n') { + count = atoi(optarg); + continue; + } + if (rm_apply_common_opt(ctx, opt, optarg) < 0) + return 2; + } + if (count <= 0) + count = 1; + return sample_timer(ctx, interval_ms, count); +} |
