summaryrefslogtreecommitdiffstats
path: root/tools/rmtool/src/nv2080.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/rmtool/src/nv2080.c')
-rw-r--r--tools/rmtool/src/nv2080.c398
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, &params, &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);
+}