Foundry/barswap/gpu_owner_util.c

341 lines
10 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file gpu_owner_util.c
* @brief Implementation of the shared multi-die GPU resource-owner helpers:
* GK210 sysfs enumeration + driver_override blocking, nvidia load-once, the
* dlopen'd CUDA driver-API table, and the slow_add die-pattern helpers.
*
* Helpers were extracted verbatim (renamed + de-static'd) from
* barswap/tests/worker_switch.c and barswap/tests/compute_persist.c so the
* owner and its tests share one copy. The gk210_bdf[]/n_gk210 cache stays
* file-private here and is exposed only through gou_gk210_bdf()/gou_num_gk210().
*
* @status None
* @issues None
* @todo None
*/
/* 1. This module's own header */
#include "barswap/gpu_owner_util.h"
/* 2. Standard library */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 3. System / platform headers */
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#define CUDA_SUCCESS 0
/* ================================================================
* GK210 enumeration + driver_override protocol (ported from
* worker_switch.c — verbatim logic, renamed + de-static'd).
* ================================================================ */
/* BDF strings for all GK210 devices found in sysfs (file-private cache). */
static char gk210_bdf[GOU_MAX_DIES][16];
static int n_gk210;
int gou_discover_gk210s(void)
{
FILE *fp;
char path[512], buf[16];
DIR *dir;
struct dirent *ent;
char *bdfs[GOU_MAX_DIES];
int count = 0;
dir = opendir("/sys/bus/pci/devices");
if (!dir)
return 0;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == '.')
continue;
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/vendor", ent->d_name);
fp = fopen(path, "r");
if (!fp)
continue;
if (!fgets(buf, sizeof(buf), fp)) { fclose(fp); continue; }
fclose(fp);
if (strncmp(buf, "0x10de", 6) != 0)
continue;
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/device", ent->d_name);
fp = fopen(path, "r");
if (!fp)
continue;
if (!fgets(buf, sizeof(buf), fp)) { fclose(fp); continue; }
fclose(fp);
if (strncmp(buf, "0x102d", 6) != 0)
continue;
if (count < GOU_MAX_DIES) {
bdfs[count] = strdup(ent->d_name);
count++;
}
}
closedir(dir);
/* Sort by BDF name */
for (int i = 0; i < count - 1; i++)
for (int j = i + 1; j < count; j++)
if (strcmp(bdfs[i], bdfs[j]) > 0) {
char *swap_tmp = bdfs[i]; bdfs[i] = bdfs[j]; bdfs[j] = swap_tmp;
}
for (int i = 0; i < count; i++) {
snprintf(gk210_bdf[i], sizeof(gk210_bdf[i]), "%s", bdfs[i]);
free(bdfs[i]);
}
n_gk210 = count;
return count;
}
int gou_num_gk210(void)
{
return n_gk210;
}
const char *gou_gk210_bdf(int index)
{
if (index < 0 || index >= n_gk210)
return NULL;
return gk210_bdf[index];
}
uint64_t gou_read_bar0(const char *bdf)
{
char path[512], buf[256];
uint64_t start = 0;
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/resource", bdf);
FILE *fp = fopen(path, "r");
if (!fp)
return 0;
/* First line of 'resource' file is BAR 0: start end flags */
if (fgets(buf, sizeof(buf), fp))
start = strtoull(buf, NULL, 16);
fclose(fp);
return start;
}
int gou_find_acquired_bdf(void)
{
for (int i = 0; i < n_gk210; i++) {
if (gou_read_bar0(gk210_bdf[i]) != 0)
return i;
}
return -1;
}
int gou_sysfs_write(const char *path, const char *str)
{
int fd = open(path, O_WRONLY);
if (fd < 0) {
fprintf(stderr, " sysfs_write: open(%s) failed\n", path);
return -1;
}
ssize_t want = (ssize_t)strlen(str);
ssize_t got = write(fd, str, (size_t)want);
close(fd);
if (got != want) {
fprintf(stderr, " sysfs_write: write(%s, \"%s\") failed (%zd/%zd)\n",
path, str, got, want);
return -1;
}
return 0;
}
void gou_set_driver_override(const char *bdf, const char *value)
{
char path[512];
int fd;
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/driver_override", bdf);
fd = open(path, O_WRONLY);
if (fd < 0)
return;
if (*value == '\0') {
/* Write newline to clear override — 0-byte write is ignored by VFS */
if (write(fd, "\n", 1) < 0) { /* best-effort */ }
} else {
if (write(fd, value, strlen(value)) < 0) { /* best-effort */ }
}
close(fd);
}
void gou_block_all_except(int bdf_index)
{
for (int i = 0; i < n_gk210; i++) {
if (i == bdf_index)
gou_set_driver_override(gk210_bdf[i], ""); /* clear — allow nvidia */
else
gou_set_driver_override(gk210_bdf[i], "none"); /* block */
}
}
void gou_unblock_all(void)
{
for (int i = 0; i < n_gk210; i++)
gou_set_driver_override(gk210_bdf[i], "");
}
int gou_load_nvidia_once(void)
{
int ret;
/* Each module loaded separately with --ignore-install: "install nvidia
* /bin/false" in modprobe.d otherwise blocks dependency resolution. */
ret = system("modprobe --ignore-install nvidia 2>/dev/null");
if (ret != 0)
return -1;
sleep(1);
/* nvidia_uvm is required for cuInit to succeed */
ret = system("modprobe --ignore-install nvidia_uvm 2>/dev/null");
if (ret != 0)
return -1;
sleep(1);
return 0;
}
/* ================================================================
* CUDA driver API via dlopen (ported from compute_persist.c — the loose
* p_cu* globals reshaped into a single gou_cuda table).
* ================================================================ */
static void *libcuda = NULL;
static gou_cuda cuda;
static int cuda_loaded;
const gou_cuda *gou_cuda_load_symbols(void)
{
if (cuda_loaded)
return &cuda;
libcuda = dlopen("libcuda.so.1", RTLD_LAZY);
if (!libcuda)
libcuda = dlopen("libcuda.so", RTLD_LAZY);
if (!libcuda)
return NULL; /* SKIP path: not on a K80 host (e.g. the Mac) */
/* _v2 symbols where the originals do (legacy v1 ctx is ABI-incompatible). */
#define LOAD(member, sym, type) do { \
cuda.member = (type)dlsym(libcuda, sym); \
if (!cuda.member) { \
fprintf(stderr, " dlsym(%s) failed: %s\n", sym, dlerror()); \
dlclose(libcuda); \
libcuda = NULL; \
return NULL; \
} \
} while (0)
LOAD(cuInit, "cuInit", CUresult (*)(unsigned));
LOAD(cuDeviceGetCount, "cuDeviceGetCount", CUresult (*)(int*));
LOAD(cuDeviceGet, "cuDeviceGet", CUresult (*)(CUdevice*,int));
LOAD(cuCtxCreate, "cuCtxCreate_v2", CUresult (*)(CUcontext*,unsigned,CUdevice));
LOAD(cuCtxDestroy, "cuCtxDestroy_v2", CUresult (*)(CUcontext));
LOAD(cuCtxSetCurrent, "cuCtxSetCurrent", CUresult (*)(CUcontext));
LOAD(cuCtxGetCurrent, "cuCtxGetCurrent", CUresult (*)(CUcontext*));
LOAD(cuCtxSynchronize, "cuCtxSynchronize", CUresult (*)(void));
LOAD(cuMemAlloc, "cuMemAlloc_v2", CUresult (*)(CUdeviceptr*,size_t));
LOAD(cuMemFree, "cuMemFree_v2", CUresult (*)(CUdeviceptr));
LOAD(cuMemcpyHtoD, "cuMemcpyHtoD_v2", CUresult (*)(CUdeviceptr,const void*,size_t));
LOAD(cuMemcpyDtoH, "cuMemcpyDtoH_v2", CUresult (*)(void*,CUdeviceptr,size_t));
LOAD(cuMemGetInfo, "cuMemGetInfo_v2", CUresult (*)(size_t*,size_t*));
LOAD(cuModuleLoadData, "cuModuleLoadData", CUresult (*)(CUmodule*,const void*));
LOAD(cuModuleGetFunction, "cuModuleGetFunction", CUresult (*)(CUfunction*,CUmodule,const char*));
LOAD(cuLaunchKernel, "cuLaunchKernel",
CUresult (*)(CUfunction,unsigned,unsigned,unsigned,unsigned,unsigned,unsigned,unsigned,CUstream,void**,void**));
#undef LOAD
cuda_loaded = 1;
return &cuda;
}
/* ================================================================
* Compute payload — slow_add PTX + die-pattern helpers (ported from
* worker_switch.c: slow_add_ptx, fill_die_pattern, expected_compute_at).
* ================================================================ */
/*
* Embedded PTX kernel — adds 1.0 to each element `iters` times.
* out[i] = in[i] + iters, exact in float32 while values stay under 2^24.
*/
const char *const gou_slow_add_ptx =
".version 5.0\n"
".target sm_37\n"
".address_size 64\n"
"\n"
".visible .entry slow_add(\n"
" .param .u64 p_out,\n"
" .param .u64 p_in,\n"
" .param .u32 p_n,\n"
" .param .u32 p_iters\n"
") {\n"
" .reg .u32 %r<10>;\n"
" .reg .u64 %rd<10>;\n"
" .reg .f32 %f<4>;\n"
" .reg .pred %p1;\n"
"\n"
" mov.u32 %r0, %tid.x;\n"
" mov.u32 %r1, %ctaid.x;\n"
" mov.u32 %r2, %ntid.x;\n"
" mad.lo.u32 %r0, %r1, %r2, %r0;\n"
"\n"
" ld.param.u32 %r3, [p_n];\n"
" setp.ge.u32 %p1, %r0, %r3;\n"
" @%p1 bra $L_done;\n"
"\n"
" ld.param.u64 %rd0, [p_in];\n"
" cvt.u64.u32 %rd1, %r0;\n"
" shl.b64 %rd1, %rd1, 2;\n"
" add.u64 %rd0, %rd0, %rd1;\n"
" ld.global.f32 %f0, [%rd0];\n"
"\n"
" ld.param.u32 %r4, [p_iters];\n"
" mov.u32 %r5, 0;\n"
" mov.f32 %f1, 0f3F800000;\n" /* 1.0f */
"\n"
"$L_loop:\n"
" add.f32 %f0, %f0, %f1;\n"
" add.u32 %r5, %r5, 1;\n"
" setp.lt.u32 %p1, %r5, %r4;\n"
" @%p1 bra $L_loop;\n"
"\n"
" ld.param.u64 %rd2, [p_out];\n"
" cvt.u64.u32 %rd3, %r0;\n"
" shl.b64 %rd3, %rd3, 2;\n"
" add.u64 %rd2, %rd2, %rd3;\n"
" st.global.f32 [%rd2], %f0;\n"
"\n"
"$L_done:\n"
" ret;\n"
"}\n";
void gou_fill_die_pattern(float *buf, int n, uint32_t seed)
{
for (int i = 0; i < n; i++) {
uint32_t v = (seed + (uint32_t)i * 2654435761u) & 0xFFFu;
buf[i] = (float)v;
}
}
int gou_verify_die_pattern(const float *buf, int n, uint32_t seed, int iters)
{
for (int i = 0; i < n; i++) {
uint32_t v = (seed + (uint32_t)i * 2654435761u) & 0xFFFu;
float expected = (float)v + (float)iters;
if (buf[i] != expected)
return 0;
}
return 1;
}