mirror of
https://dev.lirent.ru/Vatrog/vm-vgpu-streamer.git
synced 2026-07-08 23:46:36 +03:00
fix: discover the vgpu region in the producer process user space
The region is a shared mapping in a producer process's user address space, not a kernel VA: open a read-only process context, enumerate processes, and locate it by structural invariants under each process cr3. Frames, cursor, geometry and status are read under the producer's cr3.
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
/* test_perception.c — table-driven invariant predicates + flat sampling smoke.
|
||||
/* test_perception.c — table-driven invariant predicates + per-cr3 user-AS scan.
|
||||
*
|
||||
* Two layers:
|
||||
* Two layers (no proc_list / win32 — that path needs a real Windows kernel
|
||||
* bring-up and is covered by an out-of-tree integration run, not this unit):
|
||||
* 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.)
|
||||
* 2) Per-cr3 user-AS scan + sampling under a SYNTHETIC cr3: 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 USER VA (the region
|
||||
* really lives in a producer's user-AS), open it RO via vmie_mem_from_ro_fd,
|
||||
* and run vgpup_scan_user_as_for_region + a two-phase heartbeat liveness
|
||||
* check, then construct a handle (proc_cr3 = synth cr3) and run the real
|
||||
* frame/cursor/geometry/status reads and the control-write seam under it.
|
||||
* (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.) The win32 proc_list wrapper is deliberately NOT exercised
|
||||
* here: vgpup_scan_user_as_for_region is the pure per-cr3 core it calls.
|
||||
*
|
||||
* Exit 0 on all-pass; nonzero on the first failure.
|
||||
*/
|
||||
@@ -97,17 +102,19 @@ static void run_invariant_table(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- layer 2: flat sampling smoke over a real RO vmie_mem ----------------- */
|
||||
/* ---- layer 2: per-cr3 user-AS scan + sampling 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_US 0x4u /* user-accessible (the region is in a user-AS) */
|
||||
#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
|
||||
/* Build a minimal identity page table mapping [0, span) of the image at user VA
|
||||
* `base` using 2 MiB large pages, with the PML4/PDPT/PD pages laid out right
|
||||
* after the region in the same image. Every level carries US so the run reports
|
||||
* VR_W|VR_U (a real user-AS mapping). 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)
|
||||
@@ -124,10 +131,10 @@ static uint64_t build_identity_table(uint8_t* img, uint64_t region_bytes,
|
||||
uint64_t mapped = 0;
|
||||
unsigned k = 0;
|
||||
|
||||
pml4[pml4i] = pdpt_gpa | PTE_P | PTE_RW;
|
||||
pdpt[pdpti] = pd_gpa | PTE_P | PTE_RW;
|
||||
pml4[pml4i] = pdpt_gpa | PTE_P | PTE_RW | PTE_US;
|
||||
pdpt[pdpti] = pd_gpa | PTE_P | PTE_RW | PTE_US;
|
||||
while (mapped < span) {
|
||||
pd[pdi0 + k] = mapped | PTE_P | PTE_RW | PTE_PS; /* VA base+k*2M → GPA mapped */
|
||||
pd[pdi0 + k] = mapped | PTE_P | PTE_RW | PTE_US | PTE_PS; /* VA base+k*2M → GPA mapped */
|
||||
mapped += LARGE_PAGE;
|
||||
++k;
|
||||
}
|
||||
@@ -140,7 +147,9 @@ static void run_flat_smoke(void)
|
||||
/* 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 */
|
||||
/* a USER VA, 2 MiB-aligned, within [USER_MIN, USER_MAX] — the region lives in
|
||||
* a producer's user address space, so we map it there (not at a kernel VA). */
|
||||
const uint64_t base_va = 0x0000000010000000ull;
|
||||
const uint32_t w = 64, h = 32;
|
||||
const size_t frame_bytes = (size_t)w * h * 4u;
|
||||
int fd;
|
||||
@@ -176,13 +185,13 @@ static void run_flat_smoke(void)
|
||||
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 */
|
||||
/* per-cr3 user-AS scan: candidate found at the user 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");
|
||||
int rc = vgpup_scan_user_as_for_region(m, cr3, &rgva, &hb0);
|
||||
CHECK(rc == 0, "scan_user_as rc");
|
||||
CHECK(rgva == base_va, "scan_user_as region gva");
|
||||
CHECK(hb0 == 42, "scan_user_as hb0");
|
||||
|
||||
/* two-phase liveness: not alive until heartbeat advances */
|
||||
CHECK(vgpup_confirm_alive(m, cr3, rgva, hb0) == 0, "confirm not-yet-alive");
|
||||
@@ -190,56 +199,55 @@ static void run_flat_smoke(void)
|
||||
CHECK(vgpup_confirm_alive(m, cr3, rgva, hb0) == 1, "confirm alive after tick");
|
||||
}
|
||||
|
||||
/* open handle + read API */
|
||||
/* construct a handle directly (the proc_list/win32 path is not unit-testable;
|
||||
* proc_cr3 is the synthetic cr3 here) and exercise the read API + control seam */
|
||||
{
|
||||
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;
|
||||
vgpup_region rr;
|
||||
vgpup_region* r = &rr;
|
||||
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");
|
||||
memset(&rr, 0, sizeof rr);
|
||||
rr.proc_cr3 = cr3;
|
||||
rr.region_gva = base_va;
|
||||
rr.ctrl_gva = base_va + VGPU_CONTROL_OFFSET;
|
||||
rr.ring_gva = base_va + VGPU_RING_OFFSET;
|
||||
|
||||
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");
|
||||
}
|
||||
CHECK(dst != NULL, "malloc dst");
|
||||
|
||||
/* 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);
|
||||
rc = vgpup_sample_frame(r, m, 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");
|
||||
}
|
||||
}
|
||||
|
||||
/* control-write seam: builds frame + offsets, writes nothing */
|
||||
{
|
||||
vgpup_region* r = vgpup_open(m, cr3);
|
||||
if (r) {
|
||||
/* same frame_id → no fresh frame (dedup) */
|
||||
CHECK(vgpup_sample_frame(r, m, dst, frame_bytes, &fi) == 0, "sample dedup");
|
||||
|
||||
/* too-small buffer → lossy drop (0), not error */
|
||||
CHECK(vgpup_sample_frame(r, m, dst, 1, &fi) == 0, "sample tiny-cap");
|
||||
|
||||
CHECK(vgpup_read_cursor(r, m, &cur) == 1, "read_cursor");
|
||||
CHECK(vgpup_read_geometry(r, m, &geo) == 1, "read_geometry");
|
||||
CHECK(vgpup_read_status(r, m, &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");
|
||||
|
||||
/* control-write seam: builds frame + offsets, writes nothing */
|
||||
{
|
||||
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");
|
||||
int crc = vgpup_build_control_write(r, &in, &frame, &ctrl_gva, &off, &len);
|
||||
CHECK(crc == 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");
|
||||
@@ -248,8 +256,9 @@ static void run_flat_smoke(void)
|
||||
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);
|
||||
}
|
||||
|
||||
free(dst);
|
||||
}
|
||||
|
||||
vmie_mem_close(m); /* the TEST owns vmie_mem here (it is the caller) */
|
||||
|
||||
Reference in New Issue
Block a user