/* 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 #include #include #include #include #include #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); }