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 | |
| 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')
| -rw-r--r-- | tools/rmtool/Makefile | 35 | ||||
| -rw-r--r-- | tools/rmtool/include/rm_api.h | 297 | ||||
| -rw-r--r-- | tools/rmtool/include/rmtool.h | 71 | ||||
| -rwxr-xr-x | tools/rmtool/rmtool | bin | 0 -> 57040 bytes | |||
| -rw-r--r-- | tools/rmtool/src/engine.c | 252 | ||||
| -rw-r--r-- | tools/rmtool/src/gpu.c | 283 | ||||
| -rw-r--r-- | tools/rmtool/src/main.c | 95 | ||||
| -rw-r--r-- | tools/rmtool/src/mem.c | 146 | ||||
| -rw-r--r-- | tools/rmtool/src/nv2080.c | 398 | ||||
| -rw-r--r-- | tools/rmtool/src/output.c | 251 | ||||
| -rw-r--r-- | tools/rmtool/src/query.c | 256 | ||||
| -rw-r--r-- | tools/rmtool/src/rm_api.c | 291 |
12 files changed, 2375 insertions, 0 deletions
diff --git a/tools/rmtool/Makefile b/tools/rmtool/Makefile new file mode 100644 index 0000000..a66b7de --- /dev/null +++ b/tools/rmtool/Makefile @@ -0,0 +1,35 @@ +# rmtool - read-only NVIDIA RM host oracle (Open-IOV). +# +# Builds src/*.c into ./rmtool and a reusable lib/librmapi.a that rmtop links +# against. No external SDK or library dependency. +CC ?= gcc +CFLAGS ?= -O2 -Wall -Wextra +CPPFLAGS += -Iinclude +PREFIX ?= /usr/local + +OBJS = src/main.o src/rm_api.o src/query.o src/output.o src/gpu.o \ + src/engine.o src/mem.o src/nv2080.o +LIBOBJS = src/rm_api.o src/query.o src/output.o +LIB = lib/librmapi.a + +all: rmtool $(LIB) + +rmtool: $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) + +$(LIB): $(LIBOBJS) + @mkdir -p lib + ar rcs $@ $(LIBOBJS) + +src/%.o: src/%.c include/rm_api.h include/rmtool.h + $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< + +install: rmtool + install -d $(DESTDIR)$(PREFIX)/bin + install -m 0755 rmtool $(DESTDIR)$(PREFIX)/bin/rmtool + +clean: + rm -f $(OBJS) rmtool $(LIB) + rmdir lib 2>/dev/null || true + +.PHONY: all install clean diff --git a/tools/rmtool/include/rm_api.h b/tools/rmtool/include/rm_api.h new file mode 100644 index 0000000..5d7f8cf --- /dev/null +++ b/tools/rmtool/include/rm_api.h @@ -0,0 +1,297 @@ +/* rm_api.h - minimal self-contained NVIDIA RM userspace client. + * + * ABI and control IDs mirror the NV570 SDK (nvos.h, ctrl2080*.h) but the + * definitions live here so the tools build with no external SDK dependency. + * This is the shared layer used by rmtool and rmtop; it is read-only by + * design: it only allocates an RM client/device/subdevice and issues + * read-only NV2080/NV0080 control queries. + */ +#ifndef RM_API_H +#define RM_API_H + +#include <stdint.h> + +typedef uint8_t NvU8; +typedef uint16_t NvU16; +typedef uint32_t NvU32; +typedef uint64_t NvU64; +typedef uint32_t NvHandle; +typedef uint64_t NvP64; +typedef uint32_t NvV32; + +/* NVIDIA escape encoding: _IOWR('F', nr, type) */ +#define NV_IOCTL_MAGIC 'F' +#define NV_IOCTL_BASE 200 +#define NV_ESC_RM_ALLOC 0x2B +#define NV_ESC_RM_CONTROL 0x2A +#define NV_ESC_CHECK_VERSION_STR (NV_IOCTL_BASE + 10) +#define NV_ESC_WAIT_OPEN_COMPLETE (NV_IOCTL_BASE + 18) + +/* RM classes used by the client ladder */ +#define NV01_ROOT 0x00000000U +#define NV01_ROOT_CLIENT 0x00000041U +#define NV01_DEVICE_0 0x00000080U +#define NV20_SUBDEVICE_0 0x00002080U + +/* NVOS21/NVOS64 match the running driver ABI (NV570 kernel nvos.h): both + * carry paramsSize. The userspace SDK snapshot in-tree has an older layout; + * using it results in the kernel rejecting the ioctl with EINVAL. + */ +typedef struct { + NvHandle hRoot; + NvHandle hObjectParent; + NvHandle hObjectNew; + NvV32 hClass; + NvP64 pAllocParms __attribute__((aligned(8))); + NvU32 paramsSize; + NvV32 status; +} NVOS21_PARAMETERS; + +#define NVOS64_FLAGS_NONE 0x00000000U +#define NVOS64_FLAGS_FINN_SERIALIZED 0x00000001U + +typedef struct { + NvHandle hRoot; + NvHandle hObjectParent; + NvHandle hObjectNew; + NvV32 hClass; + NvP64 pAllocParms __attribute__((aligned(8))); + NvP64 pRightsRequested __attribute__((aligned(8))); + NvU32 paramsSize; + NvU32 flags; + NvV32 status; +} NVOS64_PARAMETERS; + +/* NVOS54 (NV04_CONTROL), standard nvos.h layout: sizeof = 32 */ +typedef struct { + NvHandle hClient; + NvHandle hObject; + NvV32 cmd; + NvU32 flags; + NvP64 params __attribute__((aligned(8))); + NvU32 paramsSize; + NvV32 status; +} NVOS54_PARAMETERS; + +#define NV_RM_API_VERSION_STRING_LENGTH 64 +#define NV_RM_API_VERSION_CMD_QUERY '2' +#define NV_RM_API_VERSION_REPLY_RECOGNIZED 1U + +typedef struct { + NvU32 cmd; + NvU32 reply; + char versionString[NV_RM_API_VERSION_STRING_LENGTH]; +} nv_ioctl_rm_api_version_t; + +typedef struct { + int rc; + NvU32 adapterStatus; +} nv_ioctl_wait_open_complete_t; + +typedef struct { + NvU32 deviceId; + NvHandle hClientShare; + NvHandle hTargetClient; + NvHandle hTargetDevice; + NvV32 flags; + NvU64 vaSpaceSize __attribute__((aligned(8))); + NvU64 vaStartInternal __attribute__((aligned(8))); + NvU64 vaLimitInternal __attribute__((aligned(8))); + NvV32 vaMode; +} NV0080_ALLOC_PARAMETERS; + +typedef struct { + NvU32 subDeviceId; +} NV2080_ALLOC_PARAMETERS; + +#define NV_IOCTL_RM_ALLOC21 \ + _IOWR(NV_IOCTL_MAGIC, NV_ESC_RM_ALLOC, NVOS21_PARAMETERS) +#define NV_IOCTL_RM_ALLOC64 \ + _IOWR(NV_IOCTL_MAGIC, NV_ESC_RM_ALLOC, NVOS64_PARAMETERS) +#define NV_IOCTL_RM_CONTROL \ + _IOWR(NV_IOCTL_MAGIC, NV_ESC_RM_CONTROL, NVOS54_PARAMETERS) +#define NV_IOCTL_CHECK_VERSION \ + _IOWR(NV_IOCTL_MAGIC, NV_ESC_CHECK_VERSION_STR, \ + nv_ioctl_rm_api_version_t) +#define NV_IOCTL_WAIT_OPEN_COMPLETE \ + _IOWR(NV_IOCTL_MAGIC, NV_ESC_WAIT_OPEN_COMPLETE, \ + nv_ioctl_wait_open_complete_t) + +/* ------------------------------------------------------------------ */ +/* Read-only NV2080/NV0080 controls used by the tools. */ +/* ------------------------------------------------------------------ */ + +/* NV2080_CTRL_CMD_GPU_GET_INFO_V2 (subdevice, no embedded pointers) */ +#define NV2080_CTRL_CMD_GPU_GET_INFO_V2 0x20800102U +#define NV2080_CTRL_CMD_GPU_GET_NAME_STRING 0x20800110U +#define NV2080_CTRL_CMD_GPU_QUERY_MODE 0x20800128U +#define NV2080_CTRL_CMD_GPU_GET_ID 0x20800142U +#define NV2080_CTRL_CMD_GPU_GET_ENGINES_V2 0x20800170U +#define NV2080_CTRL_CMD_MC_GET_ARCH_INFO 0x20801701U +#define NV2080_CTRL_CMD_FB_GET_INFO_V2 0x20801303U +#define NV2080_CTRL_CMD_BUS_GET_PCI_INFO 0x20801801U +#define NV2080_CTRL_CMD_TIMER_GET_TIME 0x20800403U +#define NV2080_CTRL_CMD_FIFO_GET_DEVICE_INFO_TABLE 0x20801112U +#define NV0080_CTRL_CMD_GPU_GET_CLASSLIST_V2 0x00000292U + +#define NV2080_CTRL_GPU_INFO_MAX_LIST_SIZE 0x41U +#define NV2080_CTRL_GPU_INFO_INDEX_SYSMEM_ACCESS 0x1fU +#define NV2080_CTRL_GPU_INFO_INDEX_GPU_SM_VERSION 0x2cU +#define NV2080_CTRL_GPU_INFO_INDEX_GPU_FLA_CAPABILITY 0x2dU +#define NV2080_CTRL_GPU_INFO_INDEX_4K_PAGE_ISOLATION_REQUIRED 0x33U +#define NV2080_CTRL_GPU_INFO_INDEX_DISPLAY_ENABLED 0x34U +#define NV2080_CTRL_GPU_INFO_INDEX_GPU_PROFILING_CAPABILITY 0x36U +#define NV2080_CTRL_GPU_INFO_INDEX_GPU_DEBUGGING_CAPABILITY 0x37U +#define NV2080_CTRL_GPU_INFO_INDEX_CMP_SKU 0x3cU +#define NV2080_CTRL_GPU_INFO_INDEX_DMABUF_CAPABILITY 0x3dU + +typedef struct { + NvU32 index; + NvU32 data; +} NV2080_CTRL_GPU_INFO; + +typedef struct { + NvU32 gpuInfoListSize; + NV2080_CTRL_GPU_INFO gpuInfoList[NV2080_CTRL_GPU_INFO_MAX_LIST_SIZE]; +} NV2080_CTRL_GPU_GET_INFO_V2_PARAMS; + +#define NV2080_GPU_MAX_NAME_STRING_LENGTH 0x40U + +typedef struct { + NvU32 gpuNameStringFlags; + union { + NvU8 ascii[NV2080_GPU_MAX_NAME_STRING_LENGTH]; + NvU16 unicode[NV2080_GPU_MAX_NAME_STRING_LENGTH]; + } gpuNameString; +} NV2080_CTRL_GPU_GET_NAME_STRING_PARAMS; + +typedef struct { + NvU32 gpuId; +} NV2080_CTRL_GPU_GET_ID_PARAMS; + +typedef struct { + NvU32 mode; +} NV2080_CTRL_GPU_QUERY_MODE_PARAMS; + +#define NV2080_GPU_MAX_ENGINES_LIST_SIZE 0x54U + +typedef struct { + NvU32 engineCount; + NvU32 engineList[NV2080_GPU_MAX_ENGINES_LIST_SIZE]; +} NV2080_CTRL_GPU_GET_ENGINES_V2_PARAMS; + +typedef struct { + NvU32 architecture; + NvU32 implementation; + NvU32 revision; + NvU8 subRevision; +} NV2080_CTRL_MC_ARCH_INFO_PARAMS; + +#define NV2080_CTRL_FB_INFO_MAX_LIST_SIZE 0x39U +#define NV2080_CTRL_FB_INFO_INDEX_TOTAL_RAM_SIZE 0x08U +#define NV2080_CTRL_FB_INFO_INDEX_HEAP_SIZE 0x09U +#define NV2080_CTRL_FB_INFO_INDEX_LARGEST_FREE_REGION_SIZE_KB 0x12U +#define NV2080_CTRL_FB_INFO_INDEX_HEAP_FREE 0x16U +#define NV2080_CTRL_FB_INFO_INDEX_BAR1_SIZE 0x05U +#define NV2080_CTRL_FB_INFO_INDEX_BAR1_AVAIL_SIZE 0x1dU +#define NV2080_CTRL_FB_INFO_INDEX_USABLE_RAM_SIZE 0x20U + +typedef struct { + NvU32 index; + NvU32 data; +} NV2080_CTRL_FB_INFO; + +typedef struct { + NvU32 fbInfoListSize; + NV2080_CTRL_FB_INFO fbInfoList[NV2080_CTRL_FB_INFO_MAX_LIST_SIZE]; +} NV2080_CTRL_FB_GET_INFO_V2_PARAMS; + +typedef struct { + NvU32 pciDeviceId; + NvU32 pciSubSystemId; + NvU32 pciRevisionId; + NvU32 pciExtDeviceId; +} NV2080_CTRL_BUS_GET_PCI_INFO_PARAMS; + +typedef struct { + NvU64 time_nsec __attribute__((aligned(8))); +} NV2080_CTRL_TIMER_GET_TIME_PARAMS; + +/* NV2080_CTRL_FIFO_GET_DEVICE_INFO_TABLE: static engine table with names. + * Sizes match tools/rmq/rmq.c, which has produced ground-truth on TU104. + */ +typedef struct { + NvU32 engineData[16]; + NvU32 pbdmaIds[2]; + NvU32 pbdmaFaultIds[2]; + NvU32 numPbdmas; + char engineName[16]; +} NV2080_FIFO_DEVICE_ENTRY; + +typedef struct { + NvU32 baseIndex; + NvU32 numEntries; + NvU32 bMore; + NV2080_FIFO_DEVICE_ENTRY entries[32]; +} NV2080_CTRL_FIFO_GET_DEVICE_INFO_TABLE_PARAMS; + +#define NV0080_CTRL_GPU_CLASSLIST_MAX_SIZE 100U + +typedef struct { + NvU32 numClasses; + NvU32 classList[NV0080_CTRL_GPU_CLASSLIST_MAX_SIZE]; +} NV0080_CTRL_GPU_GET_CLASSLIST_V2_PARAMS; + +/* ------------------------------------------------------------------ */ +/* Client + GPU discovery */ +/* ------------------------------------------------------------------ */ + +struct rm_client { + int fd; /* /dev/nvidiactl control fd */ + int gpu_fd; /* /dev/nvidiaN device fd (open-complete) */ + NvU32 hClient; + NvU32 hDevice; + NvU32 hSubdevice; + int gpu_minor; +}; + +#define RM_MAX_GPUS 32 +#define RM_BDF_LEN 32 +#define RM_NAME_LEN 128 +#define RM_UUID_LEN 96 +#define RM_VBIOS_LEN 64 + +struct rm_gpu_desc { + int minor; + char bdf[RM_BDF_LEN]; + char model[RM_NAME_LEN]; + char uuid[RM_UUID_LEN]; + char vbios[RM_VBIOS_LEN]; + int irq; +}; + +/* Open /dev/nvidiactl, bind a GPU (gpu_minor; <0 means 0), alloc the + * root-client -> device -> subdevice ladder, and keep the matching + * /dev/nvidiaN device fd open for the lifetime of the client. + * dev_path (may be NULL) overrides the GPU device node. + * Returns 0 on success, negative errno, or an RM status. + */ +int rm_client_open(struct rm_client *c, int gpu_minor, const char *dev_path); +void rm_client_close(struct rm_client *c); + +/* Issue a control on hObject; returns the raw NV_STATUS (0 = NV_OK) and + * stores -1 in *ioctl_ret on transport failure (NULL allowed). */ +NvU32 rm_control(struct rm_client *c, NvU32 hObject, NvU32 cmd, + void *params, NvU32 size, int *ioctl_ret); + +/* Scan the per-GPU information files under /proc/driver/nvidia (read-only, + * no device access). + */ +int rm_scan_gpus(struct rm_gpu_desc *out, int max); + +/* True if cmd is on the read-only allowlist (see nv2080.c). */ +int rm_control_is_readonly(NvU32 cmd); + +const char *rm_status_str(NvU32 status); + +#endif /* RM_API_H */ diff --git a/tools/rmtool/include/rmtool.h b/tools/rmtool/include/rmtool.h new file mode 100644 index 0000000..55166f8 --- /dev/null +++ b/tools/rmtool/include/rmtool.h @@ -0,0 +1,71 @@ +/* rmtool.h - internal shared declarations for rmtool commands and output. */ +#ifndef RMTOOL_H +#define RMTOOL_H + +#include <stdio.h> +#include <stddef.h> +#include "rm_api.h" + +#define RMTOOL_VERSION "0.1.0" + +enum rm_format { + RM_FMT_TABLE = 0, + RM_FMT_JSON, + RM_FMT_CSV, +}; + +struct rm_kv { + const char *key; + const char *value; +}; + +struct rm_ctx { + int gpu_minor; /* -1 = first/default GPU */ + const char *dev_path; /* NULL = /dev/nvidiactl */ + enum rm_format format; + struct rm_client rm; + int have_rm; +}; + +/* output.c */ +enum rm_format rm_format_parse(const char *name); +const char *rm_human_kib(NvU64 kib, char *buf, size_t len); +void rm_print_kv(const char *title, const struct rm_kv *kv, int n, + enum rm_format format); +void rm_print_table(const char *title, const char *const *headers, + const char *cells, size_t stride, int rows, int cols, + enum rm_format format); +void rm_hexdump(const void *data, size_t len); + +/* query.c */ +int rm_ctx_open(struct rm_ctx *ctx); +int rm_apply_common_opt(struct rm_ctx *ctx, int opt, const char *arg); +NvU32 rm_query_name(struct rm_ctx *ctx, char *buf, size_t len); +NvU32 rm_query_mc_arch(struct rm_ctx *ctx, NV2080_CTRL_MC_ARCH_INFO_PARAMS *p); +NvU32 rm_query_pci(struct rm_ctx *ctx, NV2080_CTRL_BUS_GET_PCI_INFO_PARAMS *p); +NvU32 rm_query_fb(struct rm_ctx *ctx, const NvU32 *indices, NvU32 *values, + NvU32 n); +NvU32 rm_query_engines(struct rm_ctx *ctx, + NV2080_CTRL_GPU_GET_ENGINES_V2_PARAMS *p); +NvU32 rm_query_fifo_table(struct rm_ctx *ctx, + NV2080_FIFO_DEVICE_ENTRY *entries, + NvU32 max_entries, NvU32 *n_entries); +const char *rm_arch_str(NvU32 architecture); +const char *rm_engine_type_name(NvU32 engine_type); +const char *rm_engine_name(const NV2080_FIFO_DEVICE_ENTRY *entries, + NvU32 n_entries, NvU32 engine_type); + +/* gpu.c */ +int cmd_gpu(int argc, char **argv, struct rm_ctx *ctx); + +/* engine.c */ +int cmd_engine(int argc, char **argv, struct rm_ctx *ctx); + +/* mem.c */ +int cmd_mem(int argc, char **argv, struct rm_ctx *ctx); + +/* nv2080.c (includes sample) */ +int cmd_nv2080(int argc, char **argv, struct rm_ctx *ctx); +int cmd_sample(int argc, char **argv, struct rm_ctx *ctx); + +#endif /* RMTOOL_H */ diff --git a/tools/rmtool/rmtool b/tools/rmtool/rmtool Binary files differnew file mode 100755 index 0000000..a0c6bea --- /dev/null +++ b/tools/rmtool/rmtool diff --git a/tools/rmtool/src/engine.c b/tools/rmtool/src/engine.c new file mode 100644 index 0000000..c247e47 --- /dev/null +++ b/tools/rmtool/src/engine.c @@ -0,0 +1,252 @@ +/* engine.c - rmtool engine list/info via read-only GPU/engine controls. */ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> + +#include "rmtool.h" + +static void usage(void) +{ + printf("usage: rmtool engine list [-d N|BDF] [--format=...]\n" + " rmtool engine info [-d N|BDF] -e ENGINE\n" + "ENGINE is a FIFO table name (gr, ce0, nvdec0, ...).\n"); +} + +static int parse_list(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 (optind < argc) { + fprintf(stderr, "rmtool: unexpected argument '%s'\n", + argv[optind]); + return -1; + } + return 0; +} + +static int engine_list(struct rm_ctx *ctx) +{ + NV2080_CTRL_GPU_GET_ENGINES_V2_PARAMS engines; + NV2080_FIFO_DEVICE_ENTRY *fifo; + const char *headers[] = { "IDX", "TYPE", "NAME", "PBDMAS", + "PBDMA IDS" }; + char (*cells)[64]; + int rc; + NvU32 status, n_fifo = 0; + unsigned int i; + + fifo = calloc(1, sizeof(*fifo) * 512); + cells = calloc(1, sizeof(*cells) * (NV2080_GPU_MAX_ENGINES_LIST_SIZE * + 5)); + if (fifo == NULL || cells == NULL) { + free(fifo); + free(cells); + return 2; + } + rc = rm_ctx_open(ctx); + if (rc != 0) { + fprintf(stderr, "rmtool: cannot open RM client: rc=%d\n", rc); + free(fifo); + free(cells); + return 2; + } + status = rm_query_engines(ctx, &engines); + if (status != 0) { + fprintf(stderr, "rmtool: GPU_GET_ENGINES_V2: %s\n", + rm_status_str(status)); + free(fifo); + free(cells); + return 2; + } + status = rm_query_fifo_table(ctx, fifo, 512, &n_fifo); + + for (i = 0; i < engines.engineCount; i++) { + char type[24]; + + snprintf(type, sizeof(type), "0x%x", engines.engineList[i]); + snprintf(cells[i * 5 + 0], sizeof(cells[0]), "%u", i); + snprintf(cells[i * 5 + 1], sizeof(cells[0]), "%s", type); + snprintf(cells[i * 5 + 2], sizeof(cells[0]), "%s", + rm_engine_name(fifo, n_fifo, engines.engineList[i])); + snprintf(cells[i * 5 + 3], sizeof(cells[0]), "-"); + snprintf(cells[i * 5 + 4], sizeof(cells[0]), "-"); + } + if (status == 0) { + NvU32 j; + + for (j = 0; j < n_fifo && j < engines.engineCount; j++) { + snprintf(cells[j * 5 + 2], sizeof(cells[0]), "%s", + fifo[j].engineName); + snprintf(cells[j * 5 + 3], sizeof(cells[0]), "%u", + fifo[j].numPbdmas); + snprintf(cells[j * 5 + 4], sizeof(cells[0]), + "%u,%u", fifo[j].pbdmaIds[0], + fifo[j].pbdmaIds[1]); + } + } + if (ctx->format == RM_FMT_TABLE) + printf("%u engine(s); FIFO table %s\n", engines.engineCount, + status == 0 ? "read" : "unavailable"); + rm_print_table("engines", headers, &cells[0][0], sizeof(cells[0]), + (int)engines.engineCount, 5, ctx->format); + free(fifo); + free(cells); + return 0; +} + +static int engine_info(struct rm_ctx *ctx, const char *want) +{ + NV2080_FIFO_DEVICE_ENTRY *fifo; + struct rm_kv kv[8]; + char v[8][256]; + NvU32 n_fifo = 0, i; + int rc, n = 0; + NvU32 status; + + fifo = calloc(1, sizeof(*fifo) * 512); + if (fifo == NULL) + return 2; + rc = rm_ctx_open(ctx); + if (rc != 0) { + fprintf(stderr, "rmtool: cannot open RM client: rc=%d\n", rc); + free(fifo); + return 2; + } + status = rm_query_fifo_table(ctx, fifo, 512, &n_fifo); + if (status != 0) { + fprintf(stderr, "rmtool: FIFO_GET_DEVICE_INFO_TABLE: %s\n", + rm_status_str(status)); + free(fifo); + return 2; + } + for (i = 0; i < n_fifo; i++) { + if (strcasecmp(fifo[i].engineName, want) != 0) + continue; + snprintf(v[0], sizeof(v[0]), "%s", fifo[i].engineName); + kv[n].key = "Engine"; + kv[n++].value = v[0]; + snprintf(v[1], sizeof(v[1]), "%u", i); + kv[n].key = "Table index"; + kv[n++].value = v[1]; + snprintf(v[2], sizeof(v[2]), "%u", fifo[i].numPbdmas); + kv[n].key = "PBDMA count"; + kv[n++].value = v[2]; + snprintf(v[3], sizeof(v[3]), "%u,%u", fifo[i].pbdmaIds[0], + fifo[i].pbdmaIds[1]); + kv[n].key = "PBDMA ids"; + kv[n++].value = v[3]; + snprintf(v[4], sizeof(v[4]), "%u,%u", + fifo[i].pbdmaFaultIds[0], fifo[i].pbdmaFaultIds[1]); + kv[n].key = "PBDMA fault ids"; + kv[n++].value = v[4]; + snprintf(v[5], sizeof(v[5]),"%u", fifo[i].engineData[0]); + kv[n].key = "engineData[0] (type)"; + kv[n++].value = v[5]; + { + size_t off = 0; + unsigned int j; + + for (j = 0; j < 16 && off < sizeof(v[6]); j++) + off += (size_t)snprintf(v[6] + off, + sizeof(v[6]) - off, + "%s%u", j ? "," : "", + fifo[i].engineData[j]); + } + kv[n].key = "engineData[0..15]"; + kv[n++].value = v[6]; + rm_print_kv("engine info (FIFO device table)", kv, n, + ctx->format); + free(fifo); + return 0; + } + fprintf(stderr, "rmtool: engine '%s' not found in FIFO table " + "(%u entries)\n", want, n_fifo); + free(fifo); + return 2; +} + +int cmd_engine(int argc, char **argv, struct rm_ctx *ctx) +{ + const char *want = NULL; + int rc; + + if (argc < 2) { + usage(); + return 2; + } + if (strcmp(argv[1], "list") == 0) { + rc = parse_list(argc - 1, argv + 1, ctx); + if (rc < 0) + return 2; + if (rc == 1) { + usage(); + return 0; + } + return engine_list(ctx); + } + if (strcmp(argv[1], "info") == 0) { + int opt; + static const struct option long_opts[] = { + { "device-spec", required_argument, NULL, 'd' }, + { "format", required_argument, NULL, 'f' }, + { "engine", required_argument, NULL, 'e' }, + { "device", required_argument, NULL, 1000 }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + optind = 1; + opterr = 0; + while ((opt = getopt_long(argc - 1, argv + 1, "d:f:e:h", + long_opts, NULL)) != -1) { + if (opt == 'h') { + usage(); + return 0; + } + if (opt == 'e') { + want = optarg; + continue; + } + if (rm_apply_common_opt(ctx, opt, optarg) < 0) + return 2; + } + if (want == NULL) { + usage(); + return 2; + } + return engine_info(ctx, want); + } + if (strcmp(argv[1], "allocate") == 0 || + strcmp(argv[1], "start") == 0 || + strcmp(argv[1], "stop") == 0 || + strcmp(argv[1], "config") == 0) { + fprintf(stderr, + "rmtool: 'engine %s' is a write/ownership operation " + "and is refused by policy (read-only host oracle). " + "See debug/risc-v-gsp-rm-vm/docs/17-RM-HARNESS.md.\n", + argv[1]); + return 4; + } + usage(); + return 2; +} 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 <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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; +} diff --git a/tools/rmtool/src/main.c b/tools/rmtool/src/main.c new file mode 100644 index 0000000..4e4b440 --- /dev/null +++ b/tools/rmtool/src/main.c @@ -0,0 +1,95 @@ +/* main.c - rmtool entry point: command dispatch and global help/version. + * + * rmtool is a read-only host RM oracle for the Open-IOV accuracy loop. It + * never allocates engine/channel/memory objects and never touches VFIO, + * mdevs or kernel modules; write commands are refused by policy. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "rmtool.h" + +static void usage(void) +{ + printf("rmtool %s - read-only NVIDIA RM host oracle\n\n", + RMTOOL_VERSION); + printf("usage: rmtool <command> [subcommand] [options]\n\n" + "commands:\n" + " gpu list|topology|info enumeration/identity\n" + " engine list|info engine inventory (read-only)\n" + " mem stats VRAM/heap/BAR1 totals\n" + " nv2080 class list device class list\n" + " nv2080 method call allowlisted read-only controls\n" + " sample timer GPU timer sampling\n" + " version print version\n\n" + "options:\n" + " -d N|BDF select GPU (minor or bus id)\n" + " -f FMT output format table|json|csv\n" + " --device PATH use an explicit GPU device node\n\n" + "safety: read-only queries only. alloc/map/pin, engine/channel\n" + "control and any write control are refused. Never touches\n" + "modules, VFIO, mdevs, sysfs bindings or /sys/fs/pstore.\n\n" + "Not implemented (documented commands): channel, context,\n" + "resserve, cudasw, resman, gsp. See docs/17-RM-HARNESS.md.\n"); +} + +int main(int argc, char **argv) +{ + struct rm_ctx ctx; + const char *command; + int rc; + + ctx.gpu_minor = -1; + ctx.dev_path = NULL; + ctx.format = RM_FMT_TABLE; + ctx.have_rm = 0; + ctx.rm.fd = -1; + + if (argc < 2) { + usage(); + return 2; + } + command = argv[1]; + if (strcmp(command, "--help") == 0 || strcmp(command, "-h") == 0 || + strcmp(command, "help") == 0) { + usage(); + return 0; + } + if (strcmp(command, "--version") == 0 || + strcmp(command, "version") == 0) { + printf("rmtool %s (read-only; Open-IOV RM oracle)\n", + RMTOOL_VERSION); + return 0; + } + if (strcmp(command, "gpu") == 0) { + rc = cmd_gpu(argc - 1, argv + 1, &ctx); + } else if (strcmp(command, "engine") == 0) { + rc = cmd_engine(argc - 1, argv + 1, &ctx); + } else if (strcmp(command, "mem") == 0) { + rc = cmd_mem(argc - 1, argv + 1, &ctx); + } else if (strcmp(command, "nv2080") == 0) { + rc = cmd_nv2080(argc - 1, argv + 1, &ctx); + } else if (strcmp(command, "sample") == 0) { + rc = cmd_sample(argc - 1, argv + 1, &ctx); + } else if (strcmp(command, "channel") == 0 || + strcmp(command, "context") == 0 || + strcmp(command, "resserve") == 0 || + strcmp(command, "cudasw") == 0 || + strcmp(command, "resman") == 0 || + strcmp(command, "gsp") == 0) { + fprintf(stderr, "rmtool: '%s' is a documented but not-yet-" + "implemented surface in this minimal build; it is a " + "write/ownership path and is refused. See " + "docs/17-RM-HARNESS.md.\n", command); + rc = 3; + } else { + fprintf(stderr, "rmtool: unknown command '%s'\n", command); + usage(); + rc = 2; + } + + if (ctx.have_rm) + rm_client_close(&ctx.rm); + return rc; +} diff --git a/tools/rmtool/src/mem.c b/tools/rmtool/src/mem.c new file mode 100644 index 0000000..a85bd70 --- /dev/null +++ b/tools/rmtool/src/mem.c @@ -0,0 +1,146 @@ +/* mem.c - rmtool mem stats: read-only FB/heap/BAR1 information. + * + * Allocation, mapping, pinning and arbitrary-handle inspection are + * deliberately not implemented: they are write operations against the live + * driver and are out of scope for this read-only oracle. + */ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "rmtool.h" + +static const NvU32 fb_indices[] = { + NV2080_CTRL_FB_INFO_INDEX_TOTAL_RAM_SIZE, + NV2080_CTRL_FB_INFO_INDEX_USABLE_RAM_SIZE, + NV2080_CTRL_FB_INFO_INDEX_HEAP_SIZE, + NV2080_CTRL_FB_INFO_INDEX_HEAP_FREE, + NV2080_CTRL_FB_INFO_INDEX_LARGEST_FREE_REGION_SIZE_KB, + NV2080_CTRL_FB_INFO_INDEX_BAR1_SIZE, + NV2080_CTRL_FB_INFO_INDEX_BAR1_AVAIL_SIZE, +}; + +static const char *fb_names[] = { + "vram_total_kib", + "vram_usable_kib", + "heap_size_kib", + "heap_free_kib", + "largest_free_region_kib", + "bar1_size_kib", + "bar1_avail_kib", +}; + +#define FB_N (sizeof(fb_indices) / sizeof(fb_indices[0])) + +static void usage(void) +{ + printf("usage: rmtool mem stats [-d N|BDF] [--format=...]\n" + " rmtool mem info -h HANDLE (not implemented: write " + "path)\n" + "rmtool is read-only; alloc/map/unmap/pin are refused.\n"); +} + +static int mem_stats(struct rm_ctx *ctx) +{ + NvU32 values[FB_N]; + struct rm_kv kv[FB_N + 4]; + char v[FB_N + 4][128]; + NvU32 status; + int rc, n = 0; + unsigned int i; + + rc = rm_ctx_open(ctx); + if (rc != 0) { + fprintf(stderr, "rmtool: cannot open RM client: rc=%d\n", rc); + return 2; + } + memset(values, 0, sizeof(values)); + status = rm_query_fb(ctx, fb_indices, values, FB_N); + if (status != 0) { + fprintf(stderr, "rmtool: FB_GET_INFO_V2: %s\n", + rm_status_str(status)); + return 2; + } + for (i = 0; i < FB_N; i++) { + char human[32]; + + rm_human_kib(values[i], human, sizeof(human)); + snprintf(v[n], sizeof(v[n]), "%u (%s)", values[i], human); + kv[n].key = fb_names[i]; + kv[n].value = v[n]; + n++; + } + if (values[2] >= values[3]) { + unsigned int used = values[2] - values[3]; + char human[32]; + + rm_human_kib(used, human, sizeof(human)); + snprintf(v[n], sizeof(v[n]), "%u (%s)", used, human); + kv[n].key = "heap_used_kib (derived)"; + kv[n].value = v[n]; + n++; + } + rm_print_kv("VRAM / heap (FB_GET_INFO_V2, KiB units)", kv, n, + ctx->format); + 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; + } + return 0; +} + +int cmd_mem(int argc, char **argv, struct rm_ctx *ctx) +{ + int rc; + + if (argc < 2) { + usage(); + return 2; + } + if (strcmp(argv[1], "stats") == 0) { + rc = parse_common(argc - 1, argv + 1, ctx); + if (rc < 0) + return 2; + if (rc == 1) { + usage(); + return 0; + } + return mem_stats(ctx); + } + if (strcmp(argv[1], "alloc") == 0 || + strcmp(argv[1], "map") == 0 || + strcmp(argv[1], "unmap") == 0 || + strcmp(argv[1], "pin") == 0 || + strcmp(argv[1], "info") == 0) { + fprintf(stderr, + "rmtool: 'mem %s' is a write/ownership operation and " + "is refused by policy (read-only host oracle). " + "See debug/risc-v-gsp-rm-vm/docs/17-RM-HARNESS.md.\n", + argv[1]); + return 4; + } + usage(); + return 2; +} 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); +} diff --git a/tools/rmtool/src/output.c b/tools/rmtool/src/output.c new file mode 100644 index 0000000..6de5d02 --- /dev/null +++ b/tools/rmtool/src/output.c @@ -0,0 +1,251 @@ +/* output.c - table/JSON/CSV formatting helpers shared by rmtool commands. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "rmtool.h" + +enum rm_format rm_format_parse(const char *name) +{ + if (name == NULL || strcmp(name, "table") == 0) + return RM_FMT_TABLE; + if (strcmp(name, "json") == 0) + return RM_FMT_JSON; + if (strcmp(name, "csv") == 0) + return RM_FMT_CSV; + return (enum rm_format)-1; +} + +const char *rm_human_kib(NvU64 kib, char *buf, size_t len) +{ + if (kib >= 1024ULL * 1024ULL) + snprintf(buf, len, "%.2f GiB", (double)kib / (1024.0 * 1024.0)); + else if (kib >= 1024ULL) + snprintf(buf, len, "%.1f MiB", (double)kib / 1024.0); + else + snprintf(buf, len, "%llu KiB", (unsigned long long)kib); + return buf; +} + +static int json_plain_number(const char *value) +{ + char *end; + double d; + + if (value == NULL || *value == '\0') + return 0; + d = strtod(value, &end); + if (end == value || *end != '\0') + return 0; + (void)d; + return 1; +} + +static void json_string(FILE *out, const char *value) +{ + const unsigned char *p = (const unsigned char *)value; + + fputc('"', out); + for (; p != NULL && *p != '\0'; p++) { + switch (*p) { + case '"': + fputs("\\\"", out); + break; + case '\\': + fputs("\\\\", out); + break; + case '\n': + fputs("\\n", out); + break; + case '\r': + fputs("\\r", out); + break; + case '\t': + fputs("\\t", out); + break; + default: + if (*p < 0x20) + fprintf(out, "\\u%04x", *p); + else + fputc(*p, out); + break; + } + } + fputc('"', out); +} + +static void json_value(FILE *out, const char *value) +{ + if (value == NULL) { + fputs("null", out); + } else if (json_plain_number(value)) { + fputs(value, out); + } else { + json_string(out, value); + } +} + +static void csv_value(FILE *out, const char *value) +{ + const char *p; + int needs_quote = 0; + + if (value == NULL) + value = ""; + for (p = value; *p != '\0'; p++) { + if (*p == ',' || *p == '"' || *p == '\n' || *p == '\r') { + needs_quote = 1; + break; + } + } + if (!needs_quote) { + fputs(value, out); + return; + } + fputc('"', out); + for (p = value; *p != '\0'; p++) { + if (*p == '"') + fputc('"', out); + fputc(*p, out); + } + fputc('"', out); +} + +void rm_print_kv(const char *title, const struct rm_kv *kv, int n, + enum rm_format format) +{ + int i; + + if (format == RM_FMT_JSON) { + printf("{\"title\": "); + json_string(stdout, title); + printf(", \"values\": {"); + for (i = 0; i < n; i++) { + if (i) + fputs(", ", stdout); + json_string(stdout, kv[i].key); + fputs(": ", stdout); + json_value(stdout, kv[i].value); + } + printf("}}\n"); + return; + } + if (format == RM_FMT_CSV) { + for (i = 0; i < n; i++) { + csv_value(stdout, kv[i].key); + fputc(',', stdout); + csv_value(stdout, kv[i].value); + fputc('\n', stdout); + } + return; + } + printf("%s\n", title); + for (i = 0; i < n; i++) + printf(" %-28s %s\n", kv[i].key, + kv[i].value ? kv[i].value : "-"); +} + +static const char *cell_at(const char *cells, size_t stride, int row, + int col, int cols) +{ + return cells + (size_t)(row * cols + col) * stride; +} + +void rm_print_table(const char *title, const char *const *headers, + const char *cells, size_t stride, int rows, int cols, + enum rm_format format) +{ + int *width; + int r, c; + + if (format == RM_FMT_JSON) { + printf("["); + for (r = 0; r < rows; r++) { + if (r) + fputs(", ", stdout); + fputc('{', stdout); + for (c = 0; c < cols; c++) { + if (c) + fputs(", ", stdout); + json_string(stdout, headers[c]); + fputs(": ", stdout); + json_value(stdout, cell_at(cells, stride, r, + c, cols)); + } + fputc('}', stdout); + } + printf("]\n"); + return; + } + if (format == RM_FMT_CSV) { + for (c = 0; c < cols; c++) { + if (c) + fputc(',', stdout); + csv_value(stdout, headers[c]); + } + fputc('\n', stdout); + for (r = 0; r < rows; r++) { + for (c = 0; c < cols; c++) { + if (c) + fputc(',', stdout); + csv_value(stdout, cell_at(cells, stride, r, + c, cols)); + } + fputc('\n', stdout); + } + return; + } + width = calloc((size_t)cols, sizeof(*width)); + if (width == NULL) + return; + for (c = 0; c < cols; c++) + width[c] = (int)strlen(headers[c]); + for (r = 0; r < rows; r++) { + for (c = 0; c < cols; c++) { + const char *v = cell_at(cells, stride, r, c, cols); + int len = v ? (int)strlen(v) : 0; + + if (len > width[c]) + width[c] = len; + } + } + printf("%s\n", title); + for (c = 0; c < cols; c++) + printf("%-*s%s", width[c], headers[c], + c + 1 < cols ? " " : "\n"); + for (r = 0; r < rows; r++) { + for (c = 0; c < cols; c++) + printf("%-*s%s", width[c], + cell_at(cells, stride, r, c, cols), + c + 1 < cols ? " " : "\n"); + } + free(width); +} + +void rm_hexdump(const void *data, size_t len) +{ + const unsigned char *p = data; + size_t off; + + for (off = 0; off < len; off += 16) { + size_t i; + + printf(" %04zx ", off); + for (i = 0; i < 16; i++) { + if (off + i < len) + printf("%02x ", p[off + i]); + else + fputs(" ", stdout); + if (i == 7) + fputc(' ', stdout); + } + fputs(" |", stdout); + for (i = 0; i < 16 && off + i < len; i++) { + unsigned char ch = p[off + i]; + + fputc(isprint(ch) ? ch : '.', stdout); + } + puts("|"); + } +} diff --git a/tools/rmtool/src/query.c b/tools/rmtool/src/query.c new file mode 100644 index 0000000..c9a7460 --- /dev/null +++ b/tools/rmtool/src/query.c @@ -0,0 +1,256 @@ +/* query.c - shared read-only RM queries used by rmtool and rmtop. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "rmtool.h" + +int rm_ctx_open(struct rm_ctx *ctx) +{ + int rc; + + if (ctx->have_rm) + return 0; + rc = rm_client_open(&ctx->rm, ctx->gpu_minor, ctx->dev_path); + if (rc != 0) + return rc; + ctx->have_rm = 1; + return 0; +} + +NvU32 rm_query_name(struct rm_ctx *ctx, char *buf, size_t len) +{ + NV2080_CTRL_GPU_GET_NAME_STRING_PARAMS p; + NvU32 status; + int ioctl_ret; + + memset(&p, 0, sizeof(p)); + p.gpuNameStringFlags = 0; + status = rm_control(&ctx->rm, ctx->rm.hSubdevice, + NV2080_CTRL_CMD_GPU_GET_NAME_STRING, &p, + sizeof(p), &ioctl_ret); + if (status != 0) + return status; + p.gpuNameString.ascii[sizeof(p.gpuNameString.ascii) - 1] = '\0'; + snprintf(buf, len, "%s", p.gpuNameString.ascii); + return 0; +} + +NvU32 rm_query_mc_arch(struct rm_ctx *ctx, NV2080_CTRL_MC_ARCH_INFO_PARAMS *p) +{ + int ioctl_ret; + + memset(p, 0, sizeof(*p)); + return rm_control(&ctx->rm, ctx->rm.hSubdevice, + NV2080_CTRL_CMD_MC_GET_ARCH_INFO, p, sizeof(*p), + &ioctl_ret); +} + +NvU32 rm_query_pci(struct rm_ctx *ctx, NV2080_CTRL_BUS_GET_PCI_INFO_PARAMS *p) +{ + int ioctl_ret; + + memset(p, 0, sizeof(*p)); + return rm_control(&ctx->rm, ctx->rm.hSubdevice, + NV2080_CTRL_CMD_BUS_GET_PCI_INFO, p, sizeof(*p), + &ioctl_ret); +} + +NvU32 rm_query_fb(struct rm_ctx *ctx, const NvU32 *indices, NvU32 *values, + NvU32 n) +{ + NV2080_CTRL_FB_GET_INFO_V2_PARAMS p; + NvU32 status; + NvU32 i; + int ioctl_ret; + + if (n > NV2080_CTRL_FB_INFO_MAX_LIST_SIZE) + return (NvU32)-1; + memset(&p, 0, sizeof(p)); + p.fbInfoListSize = n; + for (i = 0; i < n; i++) { + p.fbInfoList[i].index = indices[i]; + values[i] = 0; + } + status = rm_control(&ctx->rm, ctx->rm.hSubdevice, + NV2080_CTRL_CMD_FB_GET_INFO_V2, &p, sizeof(p), + &ioctl_ret); + if (status != 0) + return status; + for (i = 0; i < n; i++) { + NvU32 j; + + for (j = 0; j < p.fbInfoListSize && j < n; j++) { + if (p.fbInfoList[j].index == indices[i]) { + values[i] = p.fbInfoList[j].data; + break; + } + } + } + return 0; +} + +NvU32 rm_query_engines(struct rm_ctx *ctx, + NV2080_CTRL_GPU_GET_ENGINES_V2_PARAMS *p) +{ + int ioctl_ret; + + memset(p, 0, sizeof(*p)); + return rm_control(&ctx->rm, ctx->rm.hSubdevice, + NV2080_CTRL_CMD_GPU_GET_ENGINES_V2, p, sizeof(*p), + &ioctl_ret); +} + +NvU32 rm_query_fifo_table(struct rm_ctx *ctx, + NV2080_FIFO_DEVICE_ENTRY *entries, + NvU32 max_entries, NvU32 *n_entries) +{ + NV2080_CTRL_FIFO_GET_DEVICE_INFO_TABLE_PARAMS t; + NvU32 base = 0; + NvU32 total = 0; + + *n_entries = 0; + do { + NvU32 status; + NvU32 i; + int ioctl_ret; + + memset(&t, 0, sizeof(t)); + t.baseIndex = base; + status = rm_control(&ctx->rm, ctx->rm.hSubdevice, + NV2080_CTRL_CMD_FIFO_GET_DEVICE_INFO_TABLE, + &t, sizeof(t), &ioctl_ret); + if (status != 0) + return status; + for (i = 0; i < t.numEntries && total < max_entries; i++) + entries[total++] = t.entries[i]; + base += t.numEntries; + if (!t.bMore || t.numEntries == 0) + break; + } while (1); + *n_entries = total; + return 0; +} + +const char *rm_arch_str(NvU32 architecture) +{ + switch (architecture) { + case 0xE0000023U: + return "T23X"; + case 0x00000160U: + return "TU100"; + case 0x00000170U: + return "GA100"; + case 0x00000180U: + return "GH100"; + case 0x00000190U: + return "AD100"; + case 0x000001A0U: + return "GB100"; + case 0x000001B0U: + return "GB200"; + default: + return "unknown"; + } +} + +const char *rm_engine_type_name(NvU32 engine_type) +{ + static const struct { + NvU32 type; + const char *name; + } table[] = { + { 0x01, "gr" }, { 0x09, "ce0" }, { 0x0a, "ce1" }, + { 0x0b, "ce2" }, { 0x0c, "ce3" }, { 0x0d, "ce4" }, + { 0x0e, "ce5" }, { 0x0f, "ce6" }, { 0x10, "ce7" }, + { 0x11, "ce8" }, { 0x12, "ce9" }, { 0x34, "ce10" }, + { 0x35, "ce11" }, { 0x36, "ce12" }, { 0x37, "ce13" }, + { 0x38, "ce14" }, { 0x39, "ce15" }, + { 0x13, "nvdec0" }, { 0x14, "nvdec1" }, { 0x15, "nvdec2" }, + { 0x16, "nvdec3" }, { 0x17, "nvdec4" }, { 0x18, "nvdec5" }, + { 0x19, "nvdec6" }, { 0x1a, "nvdec7" }, + { 0x1b, "nvenc0" }, { 0x1c, "nvenc1" }, { 0x1d, "nvenc2" }, + { 0x3f, "nvenc3" }, + { 0x2b, "nvjpg0" }, { 0x2c, "nvjpg1" }, { 0x2d, "nvjpg2" }, + { 0x2e, "nvjpg3" }, { 0x2f, "nvjpg4" }, { 0x30, "nvjpg5" }, + { 0x31, "nvjpg6" }, { 0x32, "nvjpg7" }, + { 0x33, "ofa0" }, { 0x3e, "ofa1" }, + { 0x24, "vic" }, { 0x26, "sec2" }, { 0x22, "sw" }, + { 0x27, "host" }, { 0x29, "pmu" }, { 0x2a, "fbflcn" }, + { 0x1e, "vp" }, { 0x1f, "me" }, { 0x20, "ppp" }, + { 0x21, "mpeg" }, { 0x23, "cipher" }, { 0x25, "mp" }, + { 0x28, "dpu" }, + }; + unsigned int i; + + for (i = 0; i < sizeof(table) / sizeof(table[0]); i++) { + if (table[i].type == engine_type) + return table[i].name; + } + return NULL; +} + +const char *rm_engine_name(const NV2080_FIFO_DEVICE_ENTRY *entries, + NvU32 n_entries, NvU32 engine_type) +{ + static char fallback[24]; + const char *name; + NvU32 i; + + for (i = 0; i < n_entries; i++) { + if (entries[i].engineName[0] == '\0') + continue; + if (entries[i].engineData[0] == engine_type) + return entries[i].engineName; + } + name = rm_engine_type_name(engine_type); + if (name != NULL) + return name; + snprintf(fallback, sizeof(fallback), "engine[0x%x]", engine_type); + return fallback; +} + +int rm_apply_common_opt(struct rm_ctx *ctx, int opt, const char *arg) +{ + if (opt == 'd') { + char *end; + long minor = strtol(arg, &end, 0); + + if (arg[0] != '\0' && *end == '\0') { + ctx->gpu_minor = (int)minor; + return 1; + } + if (strchr(arg, ':') != NULL) { + struct rm_gpu_desc gpus[RM_MAX_GPUS]; + int n = rm_scan_gpus(gpus, RM_MAX_GPUS); + int i; + + for (i = 0; i < n; i++) { + if (strcmp(gpus[i].bdf, arg) == 0) { + ctx->gpu_minor = gpus[i].minor; + return 1; + } + } + fprintf(stderr, "rmtool: no GPU with bus id %s\n", arg); + return -1; + } + fprintf(stderr, "rmtool: bad GPU selector '%s'\n", arg); + return -1; + } + if (opt == 'f') { + enum rm_format fmt = rm_format_parse(arg); + + if ((int)fmt < 0) { + fprintf(stderr, "rmtool: bad format '%s' " + "(table|json|csv)\n", arg); + return -1; + } + ctx->format = fmt; + return 1; + } + if (opt == 1000) { + ctx->dev_path = arg; + return 1; + } + return 0; +} diff --git a/tools/rmtool/src/rm_api.c b/tools/rmtool/src/rm_api.c new file mode 100644 index 0000000..17fb48b --- /dev/null +++ b/tools/rmtool/src/rm_api.c @@ -0,0 +1,291 @@ +/* rm_api.c - RM ioctl client, GPU discovery and control plumbing. + * + * The ioctl ladder (root -> device -> subdevice) and NV_ESC_RM_CONTROL + * pattern follow tools/rmq/rmq.c and tools/guest-diag/guest_engines.c. + * All operations here are control-path only; no kernel module, VFIO or + * device state is modified. + */ +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "rm_api.h" + +#define CONTROL_NODE "/dev/nvidiactl" + +static NvU32 rm_alloc_root(int fd, NvHandle *out) +{ + NVOS21_PARAMETERS p = { 0 }; + + p.hRoot = 0; + p.hObjectParent = 0; + p.hClass = NV01_ROOT_CLIENT; + if (ioctl(fd, NV_IOCTL_RM_ALLOC21, &p) < 0) + return (NvU32)-1; + if (p.status != 0) + return p.status; + *out = p.hObjectNew; + return 0; +} + +static NvU32 rm_alloc64(int fd, NvHandle hRoot, NvHandle hParent, + NvU32 hClass, void *params, NvU32 params_size, + NvHandle *out) +{ + NVOS64_PARAMETERS p = { 0 }; + + p.hRoot = hRoot; + p.hObjectParent = hParent; + p.hClass = hClass; + p.pAllocParms = (NvP64)params; + p.paramsSize = params_size; + p.flags = NVOS64_FLAGS_NONE; + if (ioctl(fd, NV_IOCTL_RM_ALLOC64, &p) < 0) + return (NvU32)-1; + if (p.status != 0) + return p.status; + *out = p.hObjectNew; + return 0; +} + +static NvU32 rm_version_check(int fd) +{ + nv_ioctl_rm_api_version_t v = { 0 }; + + v.cmd = NV_RM_API_VERSION_CMD_QUERY; + if (ioctl(fd, NV_IOCTL_CHECK_VERSION, &v) < 0) + return (NvU32)-1; + if (v.reply != NV_RM_API_VERSION_REPLY_RECOGNIZED) + return (NvU32)-2; + return 0; +} + +/* The open module registers a GPU once its device node open completes; a + * NV01_DEVICE_0 alloc issued before that fails with NV_ERR_INVALID_STATE. + */ +static int rm_wait_gpu_open(int gpu_minor, const char *dev_path, int *gpu_fd) +{ + nv_ioctl_wait_open_complete_t w = { 0 }; + char path[64]; + int fd; + + if (dev_path != NULL) { + snprintf(path, sizeof(path), "%s", dev_path); + } else if (gpu_minor >= 0) { + snprintf(path, sizeof(path), "/dev/nvidia%d", gpu_minor); + } else { + snprintf(path, sizeof(path), "/dev/nvidia0"); + } + fd = open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) + return -errno; + if (ioctl(fd, NV_IOCTL_WAIT_OPEN_COMPLETE, &w) < 0) { + int err = -errno; + + close(fd); + return err; + } + if (w.rc != 0) { + close(fd); + return w.rc; + } + *gpu_fd = fd; + return 0; +} + +int rm_client_open(struct rm_client *c, int gpu_minor, const char *dev_path) +{ + NV0080_ALLOC_PARAMETERS dev_params; + NV2080_ALLOC_PARAMETERS sub_params; + NvU32 status; + int rc; + + memset(c, 0, sizeof(*c)); + c->fd = -1; + c->gpu_fd = -1; + c->gpu_minor = gpu_minor >= 0 ? gpu_minor : 0; + c->fd = open(CONTROL_NODE, O_RDWR | O_CLOEXEC); + if (c->fd < 0) + return -errno; + + status = rm_version_check(c->fd); + if (status != 0) { + fprintf(stderr, "rmtool: RM version check failed: %s\n", + rm_status_str(status)); + rm_client_close(c); + return (int)status; + } + + rc = rm_wait_gpu_open(gpu_minor, dev_path, &c->gpu_fd); + if (rc != 0) { + fprintf(stderr, "rmtool: GPU open/complete failed: %s\n", + strerror(-rc)); + rm_client_close(c); + return rc; + } + + status = rm_alloc_root(c->fd, &c->hClient); + if (status != 0) { + fprintf(stderr, "rmtool: NV01_ROOT_CLIENT alloc failed: " + "0x%x\n", status); + rm_client_close(c); + return (int)status; + } + + memset(&dev_params, 0, sizeof(dev_params)); + dev_params.deviceId = (NvU32)c->gpu_minor; + status = rm_alloc64(c->fd, c->hClient, c->hClient, NV01_DEVICE_0, + &dev_params, sizeof(dev_params), &c->hDevice); + if (status != 0) { + fprintf(stderr, "rmtool: NV01_DEVICE_0 alloc failed: 0x%x\n", + status); + rm_client_close(c); + return (int)status; + } + + memset(&sub_params, 0, sizeof(sub_params)); + sub_params.subDeviceId = 0; + status = rm_alloc64(c->fd, c->hClient, c->hDevice, NV20_SUBDEVICE_0, + &sub_params, sizeof(sub_params), &c->hSubdevice); + if (status != 0) { + fprintf(stderr, "rmtool: NV20_SUBDEVICE_0 alloc failed: " + "0x%x\n", status); + rm_client_close(c); + return (int)status; + } + return 0; +} + +void rm_client_close(struct rm_client *c) +{ + if (c->gpu_fd >= 0) + close(c->gpu_fd); + if (c->fd >= 0) + close(c->fd); + c->fd = -1; + c->gpu_fd = -1; + c->hClient = 0; + c->hDevice = 0; + c->hSubdevice = 0; +} + +NvU32 rm_control(struct rm_client *c, NvU32 hObject, NvU32 cmd, + void *params, NvU32 size, int *ioctl_ret) +{ + NVOS54_PARAMETERS p = { 0 }; + + p.hClient = c->hClient; + p.hObject = hObject; + p.cmd = cmd; + p.params = (NvP64)params; + p.paramsSize = size; + if (ioctl(c->fd, NV_IOCTL_RM_CONTROL, &p) < 0) { + if (ioctl_ret) + *ioctl_ret = -errno; + return (NvU32)-1; + } + if (ioctl_ret) + *ioctl_ret = 0; + return p.status; +} + +const char *rm_status_str(NvU32 status) +{ + static char buf[32]; + + if (status == 0) + return "NV_OK"; + snprintf(buf, sizeof(buf), "NV_STATUS(0x%x)", status); + return buf; +} + +/* ------------------------------------------------------------------ */ +/* /proc/driver/nvidia/gpus discovery */ +/* ------------------------------------------------------------------ */ + +static void copy_trim(char *dst, size_t len, const char *src) +{ + size_t n; + + while (*src == ' ' || *src == '\t') + src++; + n = strcspn(src, "\r\n"); + if (n >= len) + n = len - 1; + memcpy(dst, src, n); + dst[n] = '\0'; + while (n > 0 && (dst[n - 1] == ' ' || dst[n - 1] == '\t')) + dst[--n] = '\0'; +} + +static void parse_information(const char *path, struct rm_gpu_desc *gpu) +{ + FILE *fh; + char line[512]; + + fh = fopen(path, "r"); + if (fh == NULL) + return; + while (fgets(line, sizeof(line), fh) != NULL) { + char *colon = strchr(line, ':'); + + if (colon == NULL) + continue; + *colon = '\0'; + if (strcmp(line, "Model") == 0) + copy_trim(gpu->model, sizeof(gpu->model), colon + 1); + else if (strcmp(line, "GPU UUID") == 0) + copy_trim(gpu->uuid, sizeof(gpu->uuid), colon + 1); + else if (strcmp(line, "Video BIOS") == 0) + copy_trim(gpu->vbios, sizeof(gpu->vbios), colon + 1); + else if (strcmp(line, "Bus Location") == 0) + copy_trim(gpu->bdf, sizeof(gpu->bdf), colon + 1); + else if (strcmp(line, "IRQ") == 0) + gpu->irq = atoi(colon + 1); + else if (strcmp(line, "Device Minor") == 0) + gpu->minor = atoi(colon + 1); + } + fclose(fh); +} + +static int gpu_compare(const void *a, const void *b) +{ + const struct rm_gpu_desc *ga = a; + const struct rm_gpu_desc *gb = b; + + return ga->minor - gb->minor; +} + +int rm_scan_gpus(struct rm_gpu_desc *out, int max) +{ + const char *root = "/proc/driver/nvidia/gpus"; + DIR *dir; + struct dirent *ent; + int n = 0; + + dir = opendir(root); + if (dir == NULL) + return -errno; + while ((ent = readdir(dir)) != NULL && n < max) { + char info[512]; + struct rm_gpu_desc *gpu = &out[n]; + + if (ent->d_name[0] == '.') + continue; + memset(gpu, 0, sizeof(*gpu)); + gpu->minor = -1; + copy_trim(gpu->bdf, sizeof(gpu->bdf), ent->d_name); + snprintf(info, sizeof(info), "%s/%s/information", root, + ent->d_name); + parse_information(info, gpu); + n++; + } + closedir(dir); + qsort(out, n, sizeof(*out), gpu_compare); + return n; +} |
