329 lines
8.7 KiB
C
329 lines
8.7 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/*
|
|
* test_barswap.c — Unit tests for BAR Swap userspace library v2
|
|
*
|
|
* Tests gracefully skip (pass) if /dev/bar_swap is not available,
|
|
* since we can't test the kernel module on the development machine.
|
|
*
|
|
* Phase 1 tests (6): init/free, null safety, bounds, num_dies, num_slots, mapped
|
|
* Phase 2 tests (8): acquire/release, active_die tracking, implicit release, VRAM
|
|
*/
|
|
|
|
#include "../../lib/test.h"
|
|
#include "../barswap.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
/* Track whether device is available for conditional tests */
|
|
static bs_context *g_ctx = NULL;
|
|
static int g_device_available = 0;
|
|
|
|
static void setup(void)
|
|
{
|
|
g_ctx = bs_init();
|
|
g_device_available = (g_ctx != NULL);
|
|
if (!g_device_available) {
|
|
printf(" (note: /dev/bar_swap not available — testing offline mode)\n");
|
|
}
|
|
}
|
|
|
|
static void teardown(void)
|
|
{
|
|
if (g_ctx) {
|
|
bs_free(g_ctx);
|
|
g_ctx = NULL;
|
|
}
|
|
}
|
|
|
|
/* ---- Phase 1 tests ---- */
|
|
|
|
/* Test: init/free lifecycle — should not crash even without device */
|
|
static void test_init_free(void)
|
|
{
|
|
bs_context *ctx = bs_init();
|
|
/* May be NULL if no device — that's fine */
|
|
bs_free(ctx);
|
|
/* Also safe to free NULL */
|
|
bs_free(NULL);
|
|
ASSERT_TRUE(1);
|
|
}
|
|
|
|
/* Test: NULL context safety */
|
|
static void test_null_safety(void)
|
|
{
|
|
ASSERT_INT_EQ(bs_num_dies(NULL), 0);
|
|
ASSERT_INT_EQ(bs_num_slots(NULL), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(NULL, 0), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(NULL, -1), 0);
|
|
ASSERT_INT_EQ(bs_active_die(NULL), -1);
|
|
}
|
|
|
|
/* Test: out-of-range die_id returns 0 */
|
|
static void test_is_mapped_bounds(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, 0), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, 100), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, -1), 0);
|
|
return;
|
|
}
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, -1), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, BS_MAX_DIES), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, 9999), 0);
|
|
}
|
|
|
|
/* Test: num_dies returns a reasonable value when device is present */
|
|
static void test_num_dies(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_INT_EQ(bs_num_dies(g_ctx), 0);
|
|
return;
|
|
}
|
|
uint32_t n = bs_num_dies(g_ctx);
|
|
ASSERT_TRUE(n >= 2);
|
|
ASSERT_TRUE(n <= BS_MAX_DIES);
|
|
}
|
|
|
|
/* Test: num_slots returns 1 (single MMIO slot in v2) */
|
|
static void test_num_slots(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_INT_EQ(bs_num_slots(g_ctx), 0);
|
|
return;
|
|
}
|
|
ASSERT_INT_EQ(bs_num_slots(g_ctx), 1);
|
|
}
|
|
|
|
/* Test: active_die reports -1 when slot is empty */
|
|
static void test_slot_starts_empty(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_TRUE(1); /* skip */
|
|
return;
|
|
}
|
|
/* If a previous test run left a die acquired, release it first */
|
|
int cur = bs_active_die(g_ctx);
|
|
if (cur >= 0) {
|
|
bs_gpu *stale = bs_acquire(g_ctx, cur); /* re-acquire to get handle */
|
|
bs_release(g_ctx, stale);
|
|
}
|
|
bs_refresh(g_ctx);
|
|
/* Now slot should be empty */
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
uint32_t n = bs_num_dies(g_ctx);
|
|
uint32_t i;
|
|
for (i = 0; i < n; i++)
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, (int)i), 0);
|
|
}
|
|
|
|
/* ---- Phase 2 tests ---- */
|
|
|
|
/* Test: acquire with NULL context returns NULL */
|
|
static void test_acquire_null_ctx(void)
|
|
{
|
|
bs_gpu *gpu = bs_acquire(NULL, 0);
|
|
ASSERT_TRUE(gpu == NULL);
|
|
}
|
|
|
|
/* Test: acquire invalid die_id returns NULL */
|
|
static void test_acquire_invalid_die(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_TRUE(1); /* skip */
|
|
return;
|
|
}
|
|
bs_gpu *gpu;
|
|
|
|
gpu = bs_acquire(g_ctx, -1);
|
|
ASSERT_TRUE(gpu == NULL);
|
|
|
|
gpu = bs_acquire(g_ctx, 99);
|
|
ASSERT_TRUE(gpu == NULL);
|
|
}
|
|
|
|
/* Test: release with NULL is safe no-op */
|
|
static void test_release_null(void)
|
|
{
|
|
bs_release(NULL, NULL);
|
|
if (g_device_available)
|
|
bs_release(g_ctx, NULL);
|
|
ASSERT_TRUE(1);
|
|
}
|
|
|
|
/* Test: acquire die 0, verify addresses, release */
|
|
static void test_acquire_basic(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_TRUE(1); /* skip */
|
|
return;
|
|
}
|
|
bs_gpu *gpu = bs_acquire(g_ctx, 0);
|
|
ASSERT_NOT_NULL(gpu);
|
|
ASSERT_INT_EQ(gpu->die_id, 0);
|
|
ASSERT_TRUE(gpu->bar1_addr != 0);
|
|
ASSERT_TRUE(gpu->bar1_size != 0);
|
|
|
|
/* active_die should be 0 */
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, 0), 1);
|
|
|
|
bs_release(g_ctx, gpu);
|
|
|
|
/* After release, slot should be empty */
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, 0), 0);
|
|
}
|
|
|
|
/* Test: acquire/release each die in sequence */
|
|
static void test_acquire_all_dies(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_TRUE(1); /* skip */
|
|
return;
|
|
}
|
|
uint32_t n = bs_num_dies(g_ctx);
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
bs_gpu *gpu = bs_acquire(g_ctx, (int)i);
|
|
ASSERT_NOT_NULL(gpu);
|
|
ASSERT_INT_EQ(gpu->die_id, (int)i);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), (int)i);
|
|
bs_release(g_ctx, gpu);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
}
|
|
}
|
|
|
|
/* Test: active_die tracking changes correctly */
|
|
static void test_active_die_tracking(void)
|
|
{
|
|
if (!g_device_available) {
|
|
/* Without device, active_die should be -1 */
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
return;
|
|
}
|
|
|
|
/* Start empty */
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
|
|
/* Acquire die 0 */
|
|
bs_gpu *gpu0 = bs_acquire(g_ctx, 0);
|
|
ASSERT_NOT_NULL(gpu0);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), 0);
|
|
|
|
/* Release */
|
|
bs_release(g_ctx, gpu0);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
|
|
/* Acquire die 1 if available */
|
|
if (bs_num_dies(g_ctx) >= 2) {
|
|
bs_gpu *gpu1 = bs_acquire(g_ctx, 1);
|
|
ASSERT_NOT_NULL(gpu1);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), 1);
|
|
bs_release(g_ctx, gpu1);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
}
|
|
}
|
|
|
|
/* Test: acquiring die B while die A is active (implicit release) */
|
|
static void test_implicit_release(void)
|
|
{
|
|
if (!g_device_available || bs_num_dies(g_ctx) < 2) {
|
|
ASSERT_TRUE(1); /* skip */
|
|
return;
|
|
}
|
|
|
|
/* Acquire die 0 */
|
|
bs_gpu *gpu0 = bs_acquire(g_ctx, 0);
|
|
ASSERT_NOT_NULL(gpu0);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), 0);
|
|
|
|
/* Acquire die 1 — should implicitly release die 0 in kernel */
|
|
bs_gpu *gpu1 = bs_acquire(g_ctx, 1);
|
|
ASSERT_NOT_NULL(gpu1);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), 1);
|
|
|
|
/* Die 0 is no longer mapped, die 1 is */
|
|
bs_refresh(g_ctx);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, 0), 0);
|
|
ASSERT_INT_EQ(bs_is_mapped(g_ctx, 1), 1);
|
|
|
|
/* Clean up — free gpu0 handle (kernel already released it),
|
|
* then release gpu1 properly */
|
|
free(gpu0); /* just free the userspace handle */
|
|
bs_release(g_ctx, gpu1);
|
|
ASSERT_INT_EQ(bs_active_die(g_ctx), -1);
|
|
}
|
|
|
|
/* Test: write pattern to VRAM, read back (requires server + /dev/mem) */
|
|
static void test_vram_roundtrip(void)
|
|
{
|
|
if (!g_device_available) {
|
|
ASSERT_TRUE(1); /* skip */
|
|
return;
|
|
}
|
|
|
|
bs_gpu *gpu = bs_acquire(g_ctx, 0);
|
|
ASSERT_NOT_NULL(gpu);
|
|
|
|
if (!gpu->vram) {
|
|
/* /dev/mem not accessible — strict devmem or no root.
|
|
* Verify the handle is still valid with addresses populated. */
|
|
printf(" (note: VRAM not mmap'd — CONFIG_STRICT_DEVMEM or no root)\n");
|
|
ASSERT_TRUE(gpu->bar1_addr != 0);
|
|
ASSERT_TRUE(gpu->bar1_size != 0);
|
|
bs_release(g_ctx, gpu);
|
|
return;
|
|
}
|
|
|
|
/* Probe VRAM — without nvidia driver, GPU memory controller is
|
|
* uninitialized and reads return 0xFFFFFFFF (PCIe timeout). */
|
|
volatile uint32_t *vram = (volatile uint32_t *)gpu->vram;
|
|
uint32_t probe = *vram;
|
|
if (probe == 0xFFFFFFFF) {
|
|
printf(" (note: VRAM reads 0xFFFFFFFF — GPU not initialized by nvidia)\n");
|
|
ASSERT_TRUE(gpu->bar1_addr != 0);
|
|
ASSERT_TRUE(gpu->bar1_size != 0);
|
|
bs_release(g_ctx, gpu);
|
|
return;
|
|
}
|
|
|
|
/* GPU is initialized — write a test pattern and read back */
|
|
*vram = 0xDEADBEEF;
|
|
uint32_t val = *vram;
|
|
ASSERT_TRUE(val == 0xDEADBEEF);
|
|
|
|
bs_release(g_ctx, gpu);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
printf("\n barswap tests\n\n");
|
|
|
|
setup();
|
|
|
|
/* Phase 1 */
|
|
RUN_TEST(init_free);
|
|
RUN_TEST(null_safety);
|
|
RUN_TEST(is_mapped_bounds);
|
|
RUN_TEST(num_dies);
|
|
RUN_TEST(num_slots);
|
|
RUN_TEST(slot_starts_empty);
|
|
|
|
/* Phase 2 */
|
|
RUN_TEST(acquire_null_ctx);
|
|
RUN_TEST(acquire_invalid_die);
|
|
RUN_TEST(release_null);
|
|
RUN_TEST(acquire_basic);
|
|
RUN_TEST(acquire_all_dies);
|
|
RUN_TEST(active_die_tracking);
|
|
RUN_TEST(implicit_release);
|
|
RUN_TEST(vram_roundtrip);
|
|
|
|
teardown();
|
|
|
|
TEST_REPORT();
|
|
return __ft_failed > 0 ? 1 : 0;
|
|
}
|