mirror of
https://dev.lirent.ru/Vatrog/vm-vgpu-streamer.git
synced 2026-07-09 00:46:36 +03:00
271 lines
11 KiB
C
271 lines
11 KiB
C
|
|
/* test_perception.c — table-driven invariant predicates + flat sampling smoke.
|
||
|
|
*
|
||
|
|
* Two layers:
|
||
|
|
* 1) Invariant predicates as a TABLE of cases over a synthesized producer
|
||
|
|
* block (pure, no vmie): valid / latest==NONE / torn odd seq / non-BGRA /
|
||
|
|
* stride!=width*4 / dims out of range — each asserts accept-vs-reject.
|
||
|
|
* 2) Flat sampling smoke: lay out a real region per vgpu_stream.h in a memfd,
|
||
|
|
* build a minimal x86-64 identity page table (2 MiB large pages) that maps
|
||
|
|
* the region at a kernel VA, open it RO via vmie_mem_from_ro_fd, and run the
|
||
|
|
* real gva_*-driven discovery + frame/cursor/geometry/status reads and a
|
||
|
|
* two-phase heartbeat liveness check under that cr3. (cr3 0 over a flat
|
||
|
|
* image cannot translate — gva_* needs real page tables — so we synthesize
|
||
|
|
* them; this exercises the actual translation path the caller will use.)
|
||
|
|
*
|
||
|
|
* Exit 0 on all-pass; nonzero on the first failure.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#define _GNU_SOURCE
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <sys/mman.h>
|
||
|
|
|
||
|
|
#include "perception-internal.h"
|
||
|
|
|
||
|
|
static int g_fail;
|
||
|
|
|
||
|
|
#define CHECK(cond, msg) do { \
|
||
|
|
if (!(cond)) { fprintf(stderr, "FAIL: %s (%s:%d)\n", (msg), __FILE__, __LINE__); ++g_fail; } \
|
||
|
|
} while (0)
|
||
|
|
|
||
|
|
/* ---- layer 1: invariant predicate table ---------------------------------- */
|
||
|
|
|
||
|
|
/* Build a baseline VALID producer block (one published BGRA frame in slot 0). */
|
||
|
|
static void make_valid_producer(vgpu_producer_t* p)
|
||
|
|
{
|
||
|
|
memset(p, 0, sizeof *p);
|
||
|
|
p->latest = 0;
|
||
|
|
p->frame_id = 1;
|
||
|
|
p->seq[0] = 2; /* even = stable */
|
||
|
|
p->desc[0].width = 1920;
|
||
|
|
p->desc[0].height = 1080;
|
||
|
|
p->desc[0].stride = 1920 * 4;
|
||
|
|
p->desc[0].format = VGPU_FMT_BGRA8888;
|
||
|
|
p->desc[0].frame_id = 1;
|
||
|
|
p->status = VGPU_ST_CAPTURING;
|
||
|
|
p->backend = VGPU_BK_DDA;
|
||
|
|
p->supported_formats = (1u << VGPU_FMT_BGRA8888);
|
||
|
|
p->heartbeat = 42;
|
||
|
|
}
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
const char* name;
|
||
|
|
void (*mutate)(vgpu_producer_t*);
|
||
|
|
int expect; /* expected vgpup_invariants_hold result */
|
||
|
|
} inv_case;
|
||
|
|
|
||
|
|
static void mut_none(vgpu_producer_t* p) { (void)p; }
|
||
|
|
static void mut_latest_none(vgpu_producer_t* p) { p->latest = VGPU_LATEST_NONE; }
|
||
|
|
static void mut_latest_oob(vgpu_producer_t* p) { p->latest = VGPU_SLOT_COUNT; }
|
||
|
|
static void mut_seq_odd(vgpu_producer_t* p) { p->seq[0] = 3; }
|
||
|
|
static void mut_not_bgra(vgpu_producer_t* p) { p->desc[0].format = 7; }
|
||
|
|
static void mut_bad_stride(vgpu_producer_t* p) { p->desc[0].stride = 1920 * 4 + 1; }
|
||
|
|
static void mut_width_zero(vgpu_producer_t* p) { p->desc[0].width = 0; }
|
||
|
|
static void mut_width_huge(vgpu_producer_t* p) { p->desc[0].width = VGPU_MAX_WIDTH + 1; }
|
||
|
|
static void mut_height_huge(vgpu_producer_t* p) { p->desc[0].height = VGPU_MAX_HEIGHT + 1; }
|
||
|
|
static void mut_status_oob(vgpu_producer_t* p) { p->status = VGPU_ST_ERROR + 1; }
|
||
|
|
static void mut_backend_oob(vgpu_producer_t* p) { p->backend = VGPU_BK_GDI + 1; }
|
||
|
|
static void mut_no_bgra_support(vgpu_producer_t* p) { p->supported_formats = 0; }
|
||
|
|
|
||
|
|
static const inv_case INV_CASES[] = {
|
||
|
|
{ "valid", mut_none, 1 },
|
||
|
|
{ "latest==NONE", mut_latest_none, 1 }, /* no frame yet, still valid */
|
||
|
|
{ "latest out of range", mut_latest_oob, 0 },
|
||
|
|
{ "torn odd seq", mut_seq_odd, 0 },
|
||
|
|
{ "non-BGRA format", mut_not_bgra, 0 },
|
||
|
|
{ "stride != width*4", mut_bad_stride, 0 },
|
||
|
|
{ "width == 0", mut_width_zero, 0 },
|
||
|
|
{ "width too large", mut_width_huge, 0 },
|
||
|
|
{ "height too large", mut_height_huge, 0 },
|
||
|
|
{ "status out of range", mut_status_oob, 0 },
|
||
|
|
{ "backend out of range", mut_backend_oob, 0 },
|
||
|
|
{ "BGRA not supported", mut_no_bgra_support, 0 },
|
||
|
|
};
|
||
|
|
|
||
|
|
static void run_invariant_table(void)
|
||
|
|
{
|
||
|
|
size_t i;
|
||
|
|
for (i = 0; i < sizeof(INV_CASES) / sizeof(INV_CASES[0]); ++i) {
|
||
|
|
vgpu_producer_t p;
|
||
|
|
int got;
|
||
|
|
make_valid_producer(&p);
|
||
|
|
INV_CASES[i].mutate(&p);
|
||
|
|
got = vgpup_invariants_hold(&p);
|
||
|
|
CHECK(got == INV_CASES[i].expect, INV_CASES[i].name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- layer 2: flat sampling smoke over a real RO vmie_mem ----------------- */
|
||
|
|
|
||
|
|
/* x86-64 paging entry flags for the synthetic identity table. */
|
||
|
|
#define PTE_P 0x1u /* present */
|
||
|
|
#define PTE_RW 0x2u /* writable */
|
||
|
|
#define PTE_PS 0x80u /* page size (2 MiB leaf at PD level) */
|
||
|
|
#define LARGE_PAGE (2ull * 1024 * 1024)
|
||
|
|
|
||
|
|
/* Build a minimal identity page table mapping [0, span) of the image at kernel
|
||
|
|
* VA `base` using 2 MiB large pages, with the PML4/PDPT/PD pages laid out right
|
||
|
|
* after the region in the same image. Returns the cr3 (PML4 GPA). The mapped VA
|
||
|
|
* range fits one PD (covers up to 1 GiB), which is plenty for the region. */
|
||
|
|
static uint64_t build_identity_table(uint8_t* img, uint64_t region_bytes,
|
||
|
|
uint64_t base, uint64_t span)
|
||
|
|
{
|
||
|
|
const uint64_t pml4_gpa = region_bytes; /* one page each, after region */
|
||
|
|
const uint64_t pdpt_gpa = region_bytes + 0x1000;
|
||
|
|
const uint64_t pd_gpa = region_bytes + 0x2000;
|
||
|
|
uint64_t* pml4 = (uint64_t*)(img + pml4_gpa);
|
||
|
|
uint64_t* pdpt = (uint64_t*)(img + pdpt_gpa);
|
||
|
|
uint64_t* pd = (uint64_t*)(img + pd_gpa);
|
||
|
|
const unsigned pml4i = (unsigned)((base >> 39) & 0x1ffu);
|
||
|
|
const unsigned pdpti = (unsigned)((base >> 30) & 0x1ffu);
|
||
|
|
const unsigned pdi0 = (unsigned)((base >> 21) & 0x1ffu);
|
||
|
|
uint64_t mapped = 0;
|
||
|
|
unsigned k = 0;
|
||
|
|
|
||
|
|
pml4[pml4i] = pdpt_gpa | PTE_P | PTE_RW;
|
||
|
|
pdpt[pdpti] = pd_gpa | PTE_P | PTE_RW;
|
||
|
|
while (mapped < span) {
|
||
|
|
pd[pdi0 + k] = mapped | PTE_P | PTE_RW | PTE_PS; /* VA base+k*2M → GPA mapped */
|
||
|
|
mapped += LARGE_PAGE;
|
||
|
|
++k;
|
||
|
|
}
|
||
|
|
return pml4_gpa;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void run_flat_smoke(void)
|
||
|
|
{
|
||
|
|
const uint64_t region_bytes = VGPU_REGION_BYTES;
|
||
|
|
/* region rounded up to a 2 MiB boundary for the large-page identity map */
|
||
|
|
const uint64_t mapped_span = (region_bytes + LARGE_PAGE - 1) & ~(LARGE_PAGE - 1);
|
||
|
|
const size_t total_bytes = (size_t)region_bytes + 0x3000; /* + PML4/PDPT/PD */
|
||
|
|
const uint64_t base_va = KERN_MIN; /* kernel VA */
|
||
|
|
const uint32_t w = 64, h = 32;
|
||
|
|
const size_t frame_bytes = (size_t)w * h * 4u;
|
||
|
|
int fd;
|
||
|
|
uint8_t* img;
|
||
|
|
uint64_t cr3;
|
||
|
|
vmie_mem* m;
|
||
|
|
vgpu_producer_t p;
|
||
|
|
uint8_t marker;
|
||
|
|
|
||
|
|
fd = memfd_create("vgpu-region", 0);
|
||
|
|
CHECK(fd >= 0, "memfd_create");
|
||
|
|
if (fd < 0) { return; }
|
||
|
|
if (ftruncate(fd, (off_t)total_bytes) != 0) { CHECK(0, "ftruncate"); close(fd); return; }
|
||
|
|
|
||
|
|
img = mmap(NULL, total_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||
|
|
CHECK(img != MAP_FAILED, "mmap");
|
||
|
|
if (img == MAP_FAILED) { close(fd); return; }
|
||
|
|
|
||
|
|
/* lay out a valid producer block with one BGRA frame in slot 0 (at GPA 0) */
|
||
|
|
make_valid_producer(&p);
|
||
|
|
p.desc[0].width = w;
|
||
|
|
p.desc[0].height = h;
|
||
|
|
p.desc[0].stride = w * 4u;
|
||
|
|
memcpy(img + VGPU_PRODUCER_OFFSET, &p, sizeof p);
|
||
|
|
|
||
|
|
/* fill the slot-0 frame bytes in the RING with a recognizable marker */
|
||
|
|
marker = 0xA5;
|
||
|
|
memset(img + VGPU_RING_OFFSET + 0 * VGPU_SLOT_STRIDE, marker, frame_bytes);
|
||
|
|
|
||
|
|
/* synthesize an identity table mapping the region at base_va, then open RO */
|
||
|
|
cr3 = build_identity_table(img, region_bytes, base_va, mapped_span);
|
||
|
|
m = vmie_mem_from_ro_fd(fd, total_bytes);
|
||
|
|
CHECK(m != NULL, "vmie_mem_from_ro_fd");
|
||
|
|
if (!m) { munmap(img, total_bytes); close(fd); return; }
|
||
|
|
|
||
|
|
/* discovery: candidate found at the kernel VA with hb0 == 42 */
|
||
|
|
{
|
||
|
|
uint64_t rgva = 0xdead, hb0 = 0;
|
||
|
|
int rc = vgpup_discover_candidate(m, cr3, &rgva, &hb0);
|
||
|
|
CHECK(rc == 0, "discover_candidate rc");
|
||
|
|
CHECK(rgva == base_va, "discover_candidate region gva");
|
||
|
|
CHECK(hb0 == 42, "discover_candidate hb0");
|
||
|
|
|
||
|
|
/* two-phase liveness: not alive until heartbeat advances */
|
||
|
|
CHECK(vgpup_confirm_alive(m, cr3, rgva, hb0) == 0, "confirm not-yet-alive");
|
||
|
|
{ uint64_t hb = 43; memcpy(img + offsetof(vgpu_producer_t, heartbeat), &hb, sizeof hb); }
|
||
|
|
CHECK(vgpup_confirm_alive(m, cr3, rgva, hb0) == 1, "confirm alive after tick");
|
||
|
|
}
|
||
|
|
|
||
|
|
/* open handle + read API */
|
||
|
|
{
|
||
|
|
vgpup_region* r = vgpup_open(m, cr3);
|
||
|
|
CHECK(r != NULL, "vgpup_open");
|
||
|
|
if (r) {
|
||
|
|
uint8_t* dst = malloc(frame_bytes);
|
||
|
|
vgpup_frame_info fi;
|
||
|
|
vgpup_cursor cur;
|
||
|
|
vgpup_geometry geo;
|
||
|
|
vgpup_status st;
|
||
|
|
int rc;
|
||
|
|
|
||
|
|
CHECK(dst != NULL, "malloc dst");
|
||
|
|
|
||
|
|
rc = vgpup_sample_frame(r, m, cr3, dst, frame_bytes, &fi);
|
||
|
|
CHECK(rc == 1, "sample_frame fresh");
|
||
|
|
if (rc == 1) {
|
||
|
|
CHECK(fi.desc.width == w && fi.desc.height == h, "sample dims");
|
||
|
|
CHECK(fi.bytes == frame_bytes, "sample bytes");
|
||
|
|
CHECK(dst[0] == marker && dst[frame_bytes - 1] == marker, "sample content");
|
||
|
|
}
|
||
|
|
|
||
|
|
/* same frame_id → no fresh frame (dedup) */
|
||
|
|
CHECK(vgpup_sample_frame(r, m, cr3, dst, frame_bytes, &fi) == 0, "sample dedup");
|
||
|
|
|
||
|
|
/* too-small buffer → lossy drop (0), not error */
|
||
|
|
CHECK(vgpup_sample_frame(r, m, cr3, dst, 1, &fi) == 0, "sample tiny-cap");
|
||
|
|
|
||
|
|
CHECK(vgpup_read_cursor(r, m, cr3, &cur) == 1, "read_cursor");
|
||
|
|
CHECK(vgpup_read_geometry(r, m, cr3, &geo) == 1, "read_geometry");
|
||
|
|
CHECK(vgpup_read_status(r, m, cr3, &st) == 0, "read_status");
|
||
|
|
CHECK(st.status == VGPU_ST_CAPTURING, "status value");
|
||
|
|
CHECK(st.heartbeat == 43, "status heartbeat");
|
||
|
|
CHECK(vgpup_run_epoch(r) == st.run_epoch, "run_epoch accessor");
|
||
|
|
|
||
|
|
free(dst);
|
||
|
|
vgpup_close(r);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* control-write seam: builds frame + offsets, writes nothing */
|
||
|
|
{
|
||
|
|
vgpup_region* r = vgpup_open(m, cr3);
|
||
|
|
if (r) {
|
||
|
|
vgpup_control_intent in = { VGPU_CMD_RUN, 60, 1, 7 };
|
||
|
|
vgpu_control_t frame;
|
||
|
|
uint64_t ctrl_gva = 0;
|
||
|
|
uint32_t off = 0, len = 0;
|
||
|
|
int rc = vgpup_build_control_write(r, &in, &frame, &ctrl_gva, &off, &len);
|
||
|
|
CHECK(rc == 0, "build_control_write rc");
|
||
|
|
CHECK(frame.desired_state == VGPU_CMD_RUN, "control desired_state");
|
||
|
|
CHECK(frame.target_fps == 60, "control target_fps");
|
||
|
|
CHECK(frame.full_frame_req == 7, "control full_frame_req");
|
||
|
|
CHECK(frame.ctrl_gen == 0, "control ctrl_gen untouched");
|
||
|
|
CHECK(ctrl_gva == base_va + VGPU_CONTROL_OFFSET, "control gva");
|
||
|
|
CHECK(off == offsetof(vgpu_control_t, desired_state), "control off");
|
||
|
|
CHECK(len == offsetof(vgpu_control_t, full_frame_req) + sizeof(uint32_t)
|
||
|
|
- offsetof(vgpu_control_t, desired_state), "control len");
|
||
|
|
vgpup_close(r);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
vmie_mem_close(m); /* the TEST owns vmie_mem here (it is the caller) */
|
||
|
|
munmap(img, total_bytes);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
run_invariant_table();
|
||
|
|
run_flat_smoke();
|
||
|
|
if (g_fail) {
|
||
|
|
fprintf(stderr, "%d check(s) failed\n", g_fail);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
printf("all checks passed\n");
|
||
|
|
return 0;
|
||
|
|
}
|