/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright (c) 2026 Open-IOV contributors */ /* 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 #include #include #include #include #include #include #include #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; }