/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright (c) 2026 Open-IOV contributors */ /* panel.c - rmtop sampling (read-only RM + /proc) and plain rendering. */ #include #include #include #include "rmtop.h" static int pick_gpu(struct rm_ctx *ctx, struct rmtop_state *st) { struct rm_gpu_desc gpus[RM_MAX_GPUS]; const struct rm_gpu_desc *g = NULL; int n, i; n = rm_scan_gpus(gpus, RM_MAX_GPUS); if (n <= 0) { st->gpu_minor = ctx->gpu_minor; return n; } for (i = 0; i < n; i++) { if (gpus[i].minor == ctx->gpu_minor) g = &gpus[i]; } if (g == NULL) g = &gpus[0]; st->gpu_minor = g->minor; snprintf(st->model, sizeof(st->model), "%s", g->model); snprintf(st->bdf, sizeof(st->bdf), "%s", g->bdf); snprintf(st->uuid, sizeof(st->uuid), "%s", g->uuid); return n; } static void read_driver_version(struct rmtop_state *st) { FILE *fh = fopen("/proc/driver/nvidia/version", "r"); st->driver[0] = '\0'; if (fh == NULL) return; if (fgets(st->driver, sizeof(st->driver), fh) != NULL) st->driver[strcspn(st->driver, "\r\n")] = '\0'; fclose(fh); } int rmtop_collect(struct rm_ctx *ctx, struct rmtop_state *st) { NV2080_CTRL_MC_ARCH_INFO_PARAMS arch; NV2080_CTRL_GPU_GET_ENGINES_V2_PARAMS engines; NV2080_CTRL_TIMER_GET_TIME_PARAMS timer; NV2080_FIFO_DEVICE_ENTRY *fifo; NvU32 fb_indices[6] = { 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_BAR1_SIZE, NV2080_CTRL_FB_INFO_INDEX_BAR1_AVAIL_SIZE, }; NvU32 fb_values[6] = { 0 }; NvU32 n_fifo = 0; unsigned int i; memset(st, 0, sizeof(*st)); st->gpu_minor = ctx->gpu_minor; st->sampled_at = time(NULL); read_driver_version(st); pick_gpu(ctx, st); st->rm_rc = rm_ctx_open(ctx); if (st->rm_rc != 0) return 0; st->have_rm = 1; st->gpu_minor = ctx->rm.gpu_minor; if (rm_query_name(ctx, st->rm_name, sizeof(st->rm_name)) != 0) snprintf(st->rm_name, sizeof(st->rm_name), "%s", "-"); memset(&arch, 0, sizeof(arch)); if (rm_query_mc_arch(ctx, &arch) == 0) { st->mc_arch = arch.architecture; st->mc_impl = arch.implementation; st->mc_rev = arch.revision; st->mc_subrev = arch.subRevision; } if (rm_query_fb(ctx, fb_indices, fb_values, 6) == 0) { st->vram_total_kib = fb_values[0]; st->vram_usable_kib = fb_values[1]; st->heap_size_kib = fb_values[2]; st->heap_free_kib = fb_values[3]; st->bar1_size_kib = fb_values[4]; st->bar1_avail_kib = fb_values[5]; } memset(&timer, 0, sizeof(timer)); { int ioctl_ret; if (rm_control(&ctx->rm, ctx->rm.hSubdevice, NV2080_CTRL_CMD_TIMER_GET_TIME, &timer, sizeof(timer), &ioctl_ret) == 0 && ioctl_ret == 0) { st->timer_nsec = timer.time_nsec; st->have_timer = 1; } } memset(&engines, 0, sizeof(engines)); if (rm_query_engines(ctx, &engines) == 0) { st->engine_count = engines.engineCount; if (st->engine_count > RMTOP_MAX_ENGINES) st->engine_count = RMTOP_MAX_ENGINES; for (i = 0; i < st->engine_count; i++) st->engine_list[i] = engines.engineList[i]; } fifo = calloc(1, sizeof(*fifo) * 512); if (fifo != NULL) { rm_query_fifo_table(ctx, fifo, 512, &n_fifo); for (i = 0; i < st->engine_count; i++) { snprintf(st->engine_names[i], sizeof(st->engine_names[i]), "%s", rm_engine_name(fifo, n_fifo, st->engine_list[i])); } st->engine_names_ok = st->engine_count > 0; free(fifo); } return 0; } void rmtop_render_plain(const struct rmtop_state *st, enum rm_format format) { struct rm_kv kv[16]; char v[16][160]; char human[32], human2[32]; char when[32]; struct tm tm; int n = 0; if (format != RM_FMT_TABLE) goto build; printf("rmtop %s - read-only GPU dashboard (one-shot)\n", RMTOP_VERSION); printf("sampled %s", ctime(&st->sampled_at)); build: kv[n].key = "gpu_minor"; snprintf(v[n], sizeof(v[n]), "%d", st->gpu_minor); kv[n].value = v[n]; n++; kv[n].key = "model"; kv[n].value = st->model[0] ? st->model : "-"; n++; kv[n].key = "driver"; kv[n].value = st->driver[0] ? st->driver : "-"; n++; kv[n].key = "bus"; kv[n].value = st->bdf[0] ? st->bdf : "-"; n++; kv[n].key = "uuid"; kv[n].value = st->uuid[0] ? st->uuid : "-"; n++; kv[n].key = "rm_name"; kv[n].value = st->have_rm && st->rm_name[0] ? st->rm_name : "-"; n++; kv[n].key = "rm_client"; snprintf(v[n], sizeof(v[n]), "%s", st->have_rm ? "ok" : "unavailable"); kv[n].value = v[n]; n++; if (st->have_rm) { snprintf(v[n], sizeof(v[n]), "%s (0x%x/0x%x rev %u)", rm_arch_str(st->mc_arch), st->mc_arch, st->mc_impl, st->mc_rev); kv[n].key = "arch"; kv[n].value = v[n]; n++; } rm_human_kib(st->vram_total_kib, human, sizeof(human)); rm_human_kib(st->vram_usable_kib, human2, sizeof(human2)); snprintf(v[n], sizeof(v[n]), "%u KiB (%s) / usable %u KiB (%s)", st->vram_total_kib, human, st->vram_usable_kib, human2); kv[n].key = "vram"; kv[n].value = v[n]; n++; rm_human_kib(st->heap_free_kib, human, sizeof(human)); rm_human_kib(st->heap_size_kib, human2, sizeof(human2)); snprintf(v[n], sizeof(v[n]), "free %u KiB (%s) / heap %u KiB (%s)", st->heap_free_kib, human, st->heap_size_kib, human2); kv[n].key = "vram_free"; kv[n].value = v[n]; n++; rm_human_kib(st->bar1_size_kib, human, sizeof(human)); rm_human_kib(st->bar1_avail_kib, human2, sizeof(human2)); snprintf(v[n], sizeof(v[n]), "size %u KiB (%s), avail %u KiB (%s)", st->bar1_size_kib, human, st->bar1_avail_kib, human2); kv[n].key = "bar1"; kv[n].value = v[n]; n++; if (st->have_timer) { time_t secs = (time_t)(st->timer_nsec / 1000000000ULL); gmtime_r(&secs, &tm); strftime(when, sizeof(when), "%Y-%m-%dT%H:%M:%S", &tm); snprintf(v[n], sizeof(v[n]), "%llu ns (%s.%09lluZ)", (unsigned long long)st->timer_nsec, when, (unsigned long long)(st->timer_nsec % 1000000000ULL)); kv[n].key = "gpu_timer"; kv[n].value = v[n]; n++; } { size_t off = 0; unsigned int i; v[n][0] = '\0'; for (i = 0; i < st->engine_count && off < sizeof(v[n]) - 2; i++) { const char *name = st->engine_names_ok ? st->engine_names[i] : "engine"; off += (size_t)snprintf(v[n] + off, sizeof(v[n]) - off, "%s%s", i ? " " : "", name); } kv[n].key = "engines"; kv[n].value = v[n][0] ? v[n] : "-"; n++; } kv[n].key = "live_util_power_thermal"; kv[n].value = "n/a (read-only minimal build; see docs/17)"; n++; rm_print_kv("rmtop", kv, n, format); }