summaryrefslogtreecommitdiffstats
path: root/tools/rmtool/src/rm_api.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
commitf909a5f822c5c4a4e9526b861d4171f8925f5113 (patch)
treeed362021c83569f49b48079a774f05775e84b0bd /tools/rmtool/src/rm_api.c
parent09f241601423b1ae98e97846ee451768ff8a0a9e (diff)
downloadopen-iov-f909a5f822c5c4a4e9526b861d4171f8925f5113.tar.gz
open-iov-f909a5f822c5c4a4e9526b861d4171f8925f5113.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/rm_api.c')
-rw-r--r--tools/rmtool/src/rm_api.c291
1 files changed, 291 insertions, 0 deletions
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;
+}