mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-07-11 17:46:37 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
d66f19cb24
|
+11
-1
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
# Single source of truth for the version: CI passes -DVMSIG_VERSION=${TAG#v}, so the project
|
||||
# version (-> libvgpu-perception SONAME/.so version) and the .deb version come from one tag.
|
||||
set(VMSIG_VERSION "0.3.13" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag")
|
||||
set(VMSIG_VERSION "0.3.14" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag")
|
||||
project(vmsig VERSION ${VMSIG_VERSION} LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
@@ -130,6 +130,7 @@ if(VMSIG_WITH_VMIE)
|
||||
add_library(vgpu-perception SHARED
|
||||
src/si/vgpu-perception/discover.c
|
||||
src/si/vgpu-perception/sample.c
|
||||
src/si/vgpu-perception/capture.c
|
||||
src/si/vgpu-perception/control.c)
|
||||
set_target_properties(vgpu-perception PROPERTIES
|
||||
VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) # libvgpu-perception.so.0
|
||||
@@ -147,6 +148,15 @@ if(VMSIG_WITH_VMIE)
|
||||
add_test(NAME vgpu_perception COMMAND vgpu_perceptiontest)
|
||||
set_tests_properties(vgpu_perception PROPERTIES
|
||||
ENVIRONMENT "LD_LIBRARY_PATH=${LIBVMIE_PATH}/.build:${CMAKE_BINARY_DIR}")
|
||||
|
||||
add_executable(vgpu_capturetest src/test/test_capture.c)
|
||||
target_include_directories(vgpu_capturetest PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/si/vgpu-perception/include)
|
||||
target_link_libraries(vgpu_capturetest PRIVATE vgpu-perception)
|
||||
target_compile_options(vgpu_capturetest PRIVATE -O2 -Wall -Wextra)
|
||||
add_test(NAME vgpu_capture COMMAND vgpu_capturetest)
|
||||
set_tests_properties(vgpu_capture PROPERTIES
|
||||
ENVIRONMENT "LD_LIBRARY_PATH=${LIBVMIE_PATH}/.build:${CMAKE_BINARY_DIR}")
|
||||
endif()
|
||||
|
||||
# ---- vmsigd: the management daemon -----------------------------------------
|
||||
|
||||
@@ -234,6 +234,84 @@ int vgpup_read_status(vgpup_region* r, vmie_mem* m, vgpup_status* out);
|
||||
* value; it holds no reset policy (what to reset is the caller's decision). */
|
||||
uint32_t vgpup_run_epoch(const vgpup_region* r);
|
||||
|
||||
/* ---- region capture: coherent pull of a sub-rectangle of the CURRENT frame -- *
|
||||
* A persistent window over a fixed sub-rectangle of the captured surface. Open
|
||||
* allocates a STABLE buffer once (address fixed across syncs); each sync re-fills
|
||||
* it, all-or-nothing, from the CURRENT latest frame under one seqlock window
|
||||
* (byte-exact, never torn — pixels may carry encrypted structures). Pull only: no
|
||||
* poll / sleep / thread inside; the caller drives. NOT zero-copy: the rect crosses
|
||||
* pages and must survive a seqlock re-check, so it is copied via gva_read (zero-copy
|
||||
* would be live, hence non-coherent). Unlike vgpup_sample_frame there is NO frame_id
|
||||
* dedup: each sync yields the coherent rect of the current frame even if frame_id is
|
||||
* unchanged (info reports frame_id so the caller may dedup itself). */
|
||||
|
||||
/* Rectangle in captured-surface pixels; (0,0) = top-left; bounds are validated at
|
||||
* sync time against the CURRENT frame's desc.width/height. No geometry translation. */
|
||||
typedef struct { uint32_t x, y, w, h; } vgpup_rect;
|
||||
|
||||
/* Destination layout (chosen at open, orthogonal to format). */
|
||||
enum { VGPUP_LAYOUT_FLAT = 0, /* one contiguous block, rows back-to-back */
|
||||
VGPUP_LAYOUT_ROWS = 1 }; /* H separate row blocks; rows[] points at each */
|
||||
|
||||
/* Destination pixel format (chosen at open, orthogonal to layout). Source is ABI
|
||||
* VGPU_FMT_BGRA8888 (4 bpp, memory order B,G,R,A, stride == width*4). */
|
||||
enum { VGPUP_PX_BGRA = 0, /* 4 bpp, verbatim B,G,R,A from source */
|
||||
VGPUP_PX_RGB24 = 1 }; /* 3 bpp, BGRA->RGB: drop A, out byte order R,G,B */
|
||||
|
||||
/* Opaque capture window; owns the stable buffer(s). Heap-owned by the core. */
|
||||
typedef struct vgpup_capture vgpup_capture;
|
||||
|
||||
/* Per-sync metadata. desc mirrors the current frame; bytes is the TOTAL coherent
|
||||
* payload size: for FLAT == rect.w*rect.h*out_bpp; for ROWS == rect.h*row_bytes
|
||||
* (the sum of the row payloads, padding excluded). out_bpp / row_bytes are also
|
||||
* available via the accessors below. */
|
||||
typedef struct {
|
||||
vgpup_frame_desc desc; /* descriptor of the frame this rect was taken from */
|
||||
uint64_t frame_id; /* == desc.frame_id; surfaced for caller-side dedup */
|
||||
size_t bytes; /* total coherent payload bytes (see above) */
|
||||
} vgpup_capture_info;
|
||||
|
||||
/* Open a capture window over region handle r for sub-rectangle `rect`, with the
|
||||
* given layout (VGPUP_LAYOUT_*) and format (VGPUP_PX_*). r MUST outlive the window
|
||||
* (open caches proc_cr3 + ring_gva from r; sync does not take r). rect.w/h must be
|
||||
* > 0 and rect.x+rect.w / rect.y+rect.h must not exceed the ABI ceilings
|
||||
* (VGPU_MAX_WIDTH / VGPU_MAX_HEIGHT) — per-frame fit is checked at sync. Returns a
|
||||
* heap-owned window, or NULL on bad args / bad enum / allocation failure.
|
||||
*
|
||||
* If a later sync returns <0 (producer restarted), the old window is invalid: the
|
||||
* caller re-discovers (vgpup_close + vgpup_open) and must vgpup_capture_close this
|
||||
* window and re-open a fresh one on the new region handle. */
|
||||
vgpup_capture* vgpup_capture_open(vgpup_region* r, vgpup_rect rect,
|
||||
int layout, int format);
|
||||
|
||||
/* Coherently re-fill the stable buffer from the CURRENT latest frame, under one
|
||||
* seqlock window (fill, then re-check seq unchanged & even; changed -> retry up to
|
||||
* VGPUP_SEQLOCK_RETRIES). m is BORROWED (== vmie_win32_mem(v)). Returns:
|
||||
* 1 = stable buffer now holds the COHERENT rect (info filled);
|
||||
* 0 = no frame yet / writer busy past the retry limit / rect does not fit the
|
||||
* current frame (lossy SKIP — buffer is INVALID, do not consume, as with
|
||||
* vgpup_sample_frame partial writes);
|
||||
* <0 = hard read error (producer restarted -> caller re-discovers).
|
||||
* NO frame_id dedup: a repeat sync of the same frame returns 1 again. */
|
||||
int vgpup_capture_sync(vgpup_capture* c, vmie_mem* m, vgpup_capture_info* info);
|
||||
|
||||
/* Buffer geometry, fixed at open (do not change across syncs). */
|
||||
uint32_t vgpup_capture_out_bpp(const vgpup_capture* c); /* 4 (BGRA) | 3 (RGB24) */
|
||||
uint32_t vgpup_capture_row_bytes(const vgpup_capture* c); /* rect.w * out_bpp */
|
||||
uint32_t vgpup_capture_row_count(const vgpup_capture* c); /* rect.h */
|
||||
|
||||
/* FLAT accessors: one contiguous block, rows back-to-back, sub-stride == row_bytes,
|
||||
* pixel (x,y) at mem[(y*rect.w + x) * out_bpp]. NULL/0 if the window is ROWS. */
|
||||
const uint8_t* vgpup_capture_mem(const vgpup_capture* c);
|
||||
size_t vgpup_capture_bytes(const vgpup_capture* c); /* row_bytes*row_count */
|
||||
|
||||
/* ROWS accessors: an array of row_count pointers, each at a row_bytes block. The
|
||||
* pointer array and the blocks are stable across syncs. NULL if the window is FLAT. */
|
||||
const uint8_t* const* vgpup_capture_rows(const vgpup_capture* c);
|
||||
|
||||
/* Release the window and its buffer(s). Does NOT touch r / m. Safe on NULL. */
|
||||
void vgpup_capture_close(vgpup_capture* c);
|
||||
|
||||
/* ---- control-write — SEAM ONLY (this never writes) ------------------------ */
|
||||
|
||||
/* Desired control-block value (host-RW fields). The caller builds it and later
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/* capture.c — coherent pull of a sub-rectangle of the CURRENT frame.
|
||||
*
|
||||
* A persistent capture window over a fixed rect of the captured surface. open()
|
||||
* allocates a stable buffer once (its address is fixed across syncs); sync()
|
||||
* re-fills it all-or-nothing from the current latest frame under one seqlock
|
||||
* window — the exact mirror of vgpup_sample_frame's discipline, but for a
|
||||
* sub-rectangle, and WITHOUT frame_id dedup (a repeat sync of the same frame
|
||||
* still yields the coherent rect — see vgpu_perception.h).
|
||||
*
|
||||
* Every read goes through gva_read under c->proc_cr3 into the stable buffer (or
|
||||
* a per-window scratch for the RGB24 conversion); no gva_ptr is ever held across
|
||||
* the seqlock window. NOT zero-copy by design (the rect crosses pages and must
|
||||
* survive the seq re-check). All GVA arithmetic is done in uint64_t.
|
||||
*
|
||||
* The window caches proc_cr3 + ring_gva (copied from the region handle at open);
|
||||
* the producer block lives at region_gva == ring_gva - VGPU_RING_OFFSET, where
|
||||
* latest / seq[] / desc[] are read (the same fields sample.c reads via region_gva).
|
||||
*/
|
||||
|
||||
#include "perception-internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Round `v` up to the next multiple of `a` (a is a power of two). */
|
||||
static inline size_t vgpup_align_up(size_t v, size_t a)
|
||||
{
|
||||
return (v + (a - 1u)) & ~(a - 1u);
|
||||
}
|
||||
|
||||
vgpup_capture* vgpup_capture_open(vgpup_region* r, vgpup_rect rect,
|
||||
int layout, int format)
|
||||
{
|
||||
vgpup_capture* c;
|
||||
uint32_t out_bpp, row_bytes;
|
||||
|
||||
/* validate args and enums; fit against the ABI ceilings (NOT the current
|
||||
* desc — open may precede the first frame; per-frame fit is a sync check).
|
||||
* x+w / y+h computed in uint64_t to avoid overflow. */
|
||||
if (!r) { return NULL; }
|
||||
if (rect.w == 0u || rect.h == 0u) { return NULL; }
|
||||
if (layout != VGPUP_LAYOUT_FLAT && layout != VGPUP_LAYOUT_ROWS) { return NULL; }
|
||||
if (format != VGPUP_PX_BGRA && format != VGPUP_PX_RGB24) { return NULL; }
|
||||
if ((uint64_t)rect.x + rect.w > VGPU_MAX_WIDTH) { return NULL; }
|
||||
if ((uint64_t)rect.y + rect.h > VGPU_MAX_HEIGHT) { return NULL; }
|
||||
|
||||
out_bpp = (format == VGPUP_PX_RGB24) ? 3u : 4u;
|
||||
row_bytes = rect.w * out_bpp;
|
||||
|
||||
c = calloc(1, sizeof *c);
|
||||
if (!c) { return NULL; }
|
||||
|
||||
c->proc_cr3 = r->proc_cr3;
|
||||
c->ring_gva = r->ring_gva;
|
||||
c->x = rect.x;
|
||||
c->y = rect.y;
|
||||
c->w = rect.w;
|
||||
c->h = rect.h;
|
||||
c->out_bpp = out_bpp;
|
||||
c->row_bytes = row_bytes;
|
||||
c->layout = layout;
|
||||
c->format = format;
|
||||
|
||||
/* RGB24 stages each source BGRA row in a per-window scratch (the source row
|
||||
* is too large for the stack: up to VGPU_MAX_WIDTH*4). BGRA reads straight
|
||||
* into the destination row, no scratch needed. */
|
||||
if (format == VGPUP_PX_RGB24) {
|
||||
c->scratch = malloc((size_t)VGPU_MAX_WIDTH * 4u);
|
||||
if (!c->scratch) { vgpup_capture_close(c); return NULL; }
|
||||
}
|
||||
|
||||
if (layout == VGPUP_LAYOUT_FLAT) {
|
||||
/* one contiguous block, rows back-to-back (sub-stride == row_bytes) */
|
||||
c->buf_bytes = (size_t)row_bytes * rect.h;
|
||||
c->buf = calloc(1, c->buf_bytes);
|
||||
if (!c->buf) { vgpup_capture_close(c); return NULL; }
|
||||
} else {
|
||||
/* ROWS: ONE backing block sliced into h cache-line-aligned sub-blocks +
|
||||
* an array of h pointers. One allocation (less fragmentation, addresses
|
||||
* stable since the backing is never reallocated); the padding between
|
||||
* row_bytes..row_stride is never filled and is excluded from info->bytes. */
|
||||
size_t row_stride = vgpup_align_up(row_bytes, VGPUP_ROW_ALIGN);
|
||||
uint32_t i;
|
||||
c->buf_bytes = row_stride * rect.h;
|
||||
c->buf = calloc(1, c->buf_bytes);
|
||||
if (!c->buf) { vgpup_capture_close(c); return NULL; }
|
||||
c->rows = calloc(rect.h, sizeof *c->rows);
|
||||
if (!c->rows) { vgpup_capture_close(c); return NULL; }
|
||||
for (i = 0; i < rect.h; ++i) {
|
||||
c->rows[i] = c->buf + (size_t)i * row_stride;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Convert w source BGRA pixels in `src` (B,G,R,A) to RGB24 (R,G,B) in `dst`. */
|
||||
static inline void vgpup_bgra_to_rgb24(const uint8_t* src, uint8_t* dst, uint32_t w)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < w; ++i) {
|
||||
dst[3u * i + 0u] = src[4u * i + 2u]; /* R */
|
||||
dst[3u * i + 1u] = src[4u * i + 1u]; /* G */
|
||||
dst[3u * i + 2u] = src[4u * i + 0u]; /* B */
|
||||
}
|
||||
}
|
||||
|
||||
int vgpup_capture_sync(vgpup_capture* c, vmie_mem* m, vgpup_capture_info* info)
|
||||
{
|
||||
unsigned attempt;
|
||||
uint64_t region_gva;
|
||||
|
||||
if (!c || !m || !info) { return -1; }
|
||||
|
||||
/* the producer block sits one ring-offset below the cached ring base */
|
||||
region_gva = c->ring_gva - VGPU_RING_OFFSET;
|
||||
|
||||
for (attempt = 0; attempt < VGPUP_SEQLOCK_RETRIES; ++attempt) {
|
||||
uint32_t latest = 0, seq_before = 0, seq_after = 0;
|
||||
vgpu_desc_t d;
|
||||
uint64_t seq_gva, desc_gva, slot_base, src_bytes;
|
||||
uint32_t row;
|
||||
|
||||
/* latest (acquire-equivalent: its own read) */
|
||||
if (vgpup_read_field(m, c->proc_cr3, region_gva,
|
||||
offsetof(vgpu_producer_t, latest),
|
||||
&latest, sizeof latest) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (latest == VGPU_LATEST_NONE || latest >= VGPU_SLOT_COUNT) {
|
||||
return 0; /* no frame yet */
|
||||
}
|
||||
|
||||
seq_gva = region_gva + offsetof(vgpu_producer_t, seq)
|
||||
+ (uint64_t)latest * sizeof(uint32_t);
|
||||
desc_gva = region_gva + offsetof(vgpu_producer_t, desc)
|
||||
+ (uint64_t)latest * sizeof(vgpu_desc_t);
|
||||
|
||||
if (vgpup_read_seq(m, c->proc_cr3, seq_gva, &seq_before) < 0) { return -1; }
|
||||
if (vgpup_seq_is_writing(seq_before)) { continue; } /* writer in slot */
|
||||
|
||||
if (gva_read(m, (uintptr_t)c->proc_cr3, (uintptr_t)desc_gva,
|
||||
&d, sizeof d) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* descriptor sanity within the read window (tight BGRA, bounded dims) */
|
||||
if (d.format != VGPU_FMT_BGRA8888 || d.stride != d.width * 4u ||
|
||||
d.width == 0u || d.width > VGPU_MAX_WIDTH ||
|
||||
d.height == 0u || d.height > VGPU_MAX_HEIGHT) {
|
||||
continue; /* likely a torn read; retry */
|
||||
}
|
||||
|
||||
/* NO frame_id dedup (intentional): a repeat sync of the same frame must
|
||||
* still return the coherent rect (see vgpu_perception.h). */
|
||||
|
||||
/* fit against THIS frame: rect must lie within the current dimensions.
|
||||
* A frame that became smaller is a strict lossy skip (no partial read). */
|
||||
if ((uint64_t)c->x + c->w > d.width || (uint64_t)c->y + c->h > d.height) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* fill the whole rect, row by row, from the latest slot under proc_cr3 */
|
||||
slot_base = c->ring_gva + (uint64_t)latest * VGPU_SLOT_STRIDE;
|
||||
src_bytes = (uint64_t)c->w * 4u; /* source is BGRA, 4 bpp */
|
||||
for (row = 0; row < c->h; ++row) {
|
||||
uint64_t src_gva = slot_base
|
||||
+ (uint64_t)(c->y + row) * d.stride
|
||||
+ (uint64_t)c->x * 4u;
|
||||
uint8_t* dst = (c->layout == VGPUP_LAYOUT_ROWS)
|
||||
? (uint8_t*)c->rows[row]
|
||||
: c->buf + (size_t)row * c->row_bytes;
|
||||
|
||||
if (c->format == VGPUP_PX_RGB24) {
|
||||
/* stage the source BGRA row, then convert into the dst row */
|
||||
if (gva_read(m, (uintptr_t)c->proc_cr3, (uintptr_t)src_gva,
|
||||
c->scratch, src_bytes) < 0) {
|
||||
return -1;
|
||||
}
|
||||
vgpup_bgra_to_rgb24(c->scratch, dst, c->w);
|
||||
} else {
|
||||
/* BGRA verbatim: read straight into the dst row */
|
||||
if (gva_read(m, (uintptr_t)c->proc_cr3, (uintptr_t)src_gva,
|
||||
dst, src_bytes) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* re-check the slot seq ONCE after the whole rect is filled: unchanged
|
||||
* and still even → the rect is one coherent snapshot (all-or-nothing). */
|
||||
if (vgpup_read_seq(m, c->proc_cr3, seq_gva, &seq_after) < 0) { return -1; }
|
||||
if (seq_after != seq_before || vgpup_seq_is_writing(seq_after)) {
|
||||
continue; /* the slot was rewritten under us — re-fill the rect */
|
||||
}
|
||||
|
||||
info->desc.width = d.width;
|
||||
info->desc.height = d.height;
|
||||
info->desc.stride = d.stride;
|
||||
info->desc.format = d.format;
|
||||
info->desc.frame_id = d.frame_id;
|
||||
info->desc.timestamp_ns = d.timestamp_ns;
|
||||
info->frame_id = d.frame_id;
|
||||
info->bytes = (size_t)c->row_bytes * c->h; /* payload only */
|
||||
return 1;
|
||||
}
|
||||
return 0; /* writer kept the slot busy past the retry limit — lossy skip */
|
||||
}
|
||||
|
||||
uint32_t vgpup_capture_out_bpp(const vgpup_capture* c)
|
||||
{
|
||||
return c ? c->out_bpp : 0u;
|
||||
}
|
||||
|
||||
uint32_t vgpup_capture_row_bytes(const vgpup_capture* c)
|
||||
{
|
||||
return c ? c->row_bytes : 0u;
|
||||
}
|
||||
|
||||
uint32_t vgpup_capture_row_count(const vgpup_capture* c)
|
||||
{
|
||||
return c ? c->h : 0u;
|
||||
}
|
||||
|
||||
const uint8_t* vgpup_capture_mem(const vgpup_capture* c)
|
||||
{
|
||||
if (!c || c->layout != VGPUP_LAYOUT_FLAT) { return NULL; }
|
||||
return c->buf;
|
||||
}
|
||||
|
||||
size_t vgpup_capture_bytes(const vgpup_capture* c)
|
||||
{
|
||||
if (!c || c->layout != VGPUP_LAYOUT_FLAT) { return 0u; }
|
||||
return (size_t)c->row_bytes * c->h;
|
||||
}
|
||||
|
||||
const uint8_t* const* vgpup_capture_rows(const vgpup_capture* c)
|
||||
{
|
||||
if (!c || c->layout != VGPUP_LAYOUT_ROWS) { return NULL; }
|
||||
return c->rows;
|
||||
}
|
||||
|
||||
void vgpup_capture_close(vgpup_capture* c)
|
||||
{
|
||||
if (!c) { return; }
|
||||
free(c->rows);
|
||||
free(c->scratch);
|
||||
free(c->buf);
|
||||
free(c);
|
||||
}
|
||||
@@ -40,6 +40,30 @@ struct vgpup_region {
|
||||
uint32_t run_epoch; /* last run_epoch seen via vgpup_read_status */
|
||||
};
|
||||
|
||||
/* Row-start alignment for the ROWS layout backing block: one cache line, so the
|
||||
* upper layer gets SIMD-friendly per-row pointers. row_stride = align_up(row_bytes,
|
||||
* VGPUP_ROW_ALIGN); the padding between row_bytes..row_stride is never filled. */
|
||||
#define VGPUP_ROW_ALIGN 64u
|
||||
|
||||
/* Capture window over a fixed sub-rectangle of the captured surface. Owns its
|
||||
* stable buffer(s); the address is fixed across syncs (the backing is never
|
||||
* reallocated). proc_cr3 / ring_gva are COPIED from the region handle at open so
|
||||
* sync never dereferences `r` (it may be closed early by a caller bug — that must
|
||||
* surface as re-discover, not a use-after-free). Heap-owned by the core. */
|
||||
struct vgpup_capture {
|
||||
uint64_t proc_cr3; /* copied from r at open (producer user-AS cr3) */
|
||||
uint64_t ring_gva; /* copied from r at open (ring base GVA) */
|
||||
uint32_t x, y, w, h; /* sub-rectangle in captured-surface pixels */
|
||||
uint32_t out_bpp; /* 4 (BGRA) | 3 (RGB24) */
|
||||
uint32_t row_bytes; /* w * out_bpp */
|
||||
int layout; /* VGPUP_LAYOUT_* */
|
||||
int format; /* VGPUP_PX_* */
|
||||
uint8_t* buf; /* FLAT: the whole block; ROWS: backing for all rows */
|
||||
size_t buf_bytes; /* total allocated payload bytes */
|
||||
const uint8_t** rows; /* ROWS only: row_count pointers into buf; NULL if FLAT */
|
||||
uint8_t* scratch; /* RGB24 only: VGPU_MAX_WIDTH*4 BGRA row staging; NULL */
|
||||
};
|
||||
|
||||
/* Per-cr3 user-AS region scan (discovery steps 3–5 for ONE address space): scan
|
||||
* gva_regions over [USER_MIN, USER_MAX] under `cr3` for a contiguous RW run of
|
||||
* >= VGPU_REGION_BYTES, read the producer block at its base, and accept it iff
|
||||
@@ -62,6 +86,15 @@ static inline int vgpup_read_seq(vmie_mem* m, uintptr_t cr3, uint64_t gva,
|
||||
return gva_read(m, cr3, (uintptr_t)gva, out, sizeof *out) < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Read one cold-line / packed field at producer offset `off` into dst under the
|
||||
* producer's user-AS cr3. Shared by all perception TUs (sample / capture) so the
|
||||
* field-read discipline is not duplicated across the module. 0 / <0. */
|
||||
static inline int vgpup_read_field(vmie_mem* m, uintptr_t cr3, uint64_t region_gva,
|
||||
size_t off, void* dst, size_t n)
|
||||
{
|
||||
return gva_read(m, cr3, (uintptr_t)region_gva + off, dst, n) < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
/* ---- packed-field unpackers (cursor line) -------------------------------- */
|
||||
|
||||
static inline int32_t vgpup_cursor_x(uint64_t pos) { return (int32_t)(uint32_t)(pos & 0xFFFFFFFFu); }
|
||||
|
||||
@@ -18,14 +18,6 @@
|
||||
#include "perception-internal.h"
|
||||
#include <stdio.h> /* TEMP debug (revert): stderr skip-reason trace */
|
||||
|
||||
/* Read one cold-line / packed field at producer offset `off` into dst under the
|
||||
* producer's user-AS cr3. */
|
||||
static int read_field(vmie_mem* m, uintptr_t cr3, uint64_t region_gva,
|
||||
size_t off, void* dst, size_t n)
|
||||
{
|
||||
return gva_read(m, cr3, (uintptr_t)region_gva + off, dst, n) < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
int vgpup_sample_frame(vgpup_region* r, vmie_mem* m,
|
||||
uint8_t* dst, size_t cap, vgpup_frame_info* info)
|
||||
{
|
||||
@@ -42,7 +34,7 @@ int vgpup_sample_frame(vgpup_region* r, vmie_mem* m,
|
||||
size_t frame_bytes;
|
||||
|
||||
/* latest (acquire-equivalent: its own read) */
|
||||
if (read_field(m, r->proc_cr3, r->region_gva,
|
||||
if (vgpup_read_field(m, r->proc_cr3, r->region_gva,
|
||||
offsetof(vgpu_producer_t, latest), &latest, sizeof latest) < 0) {
|
||||
if (_dbg) fprintf(stderr, "VGPUP_DBG ret=-1 latest-read-fail\n");
|
||||
return -1;
|
||||
@@ -132,11 +124,11 @@ int vgpup_read_cursor(vgpup_region* r, vmie_mem* m, vgpup_cursor* out)
|
||||
&seq_before) < 0) { return -1; }
|
||||
if (vgpup_seq_is_writing(seq_before)) { continue; }
|
||||
|
||||
if (read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_visible), &visible, sizeof visible) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_pos), &pos, sizeof pos) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_hotspot), &hotspot, sizeof hotspot) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_glyph), &glyph, sizeof glyph) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_id), &id, sizeof id) < 0) {
|
||||
if (vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_visible), &visible, sizeof visible) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_pos), &pos, sizeof pos) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_hotspot), &hotspot, sizeof hotspot) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_glyph), &glyph, sizeof glyph) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_id), &id, sizeof id) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -173,14 +165,14 @@ int vgpup_read_geometry(vgpup_region* r, vmie_mem* m, vgpup_geometry* out)
|
||||
&seq_before) < 0) { return -1; }
|
||||
if (vgpup_seq_is_writing(seq_before)) { continue; }
|
||||
|
||||
if (read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_x), &virt_x, sizeof virt_x) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_y), &virt_y, sizeof virt_y) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_w), &virt_w, sizeof virt_w) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_h), &virt_h, sizeof virt_h) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cap_x), &cap_x, sizeof cap_x) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cap_y), &cap_y, sizeof cap_y) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, dpi), &dpi, sizeof dpi) < 0 ||
|
||||
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, refresh_mhz), &refresh_mhz, sizeof refresh_mhz) < 0) {
|
||||
if (vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_x), &virt_x, sizeof virt_x) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_y), &virt_y, sizeof virt_y) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_w), &virt_w, sizeof virt_w) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_h), &virt_h, sizeof virt_h) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cap_x), &cap_x, sizeof cap_x) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cap_y), &cap_y, sizeof cap_y) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, dpi), &dpi, sizeof dpi) < 0 ||
|
||||
vgpup_read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, refresh_mhz), &refresh_mhz, sizeof refresh_mhz) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
/* test_capture.c — region capture (sub-rectangle coherent pull) smoke test.
|
||||
*
|
||||
* Mirrors test_perception.c layer 2: lay out a real vgpu region per
|
||||
* vgpu_stream.h in a memfd, build a minimal x86-64 identity page table (2 MiB
|
||||
* large pages) mapping the region at a USER VA, open it RO via vmie_mem_from_ro_fd,
|
||||
* and craft one BGRA frame in slot 0 with a coordinate-derived pixel pattern.
|
||||
* Then exercise vgpup_capture_open/sync/accessors/close across layout × format,
|
||||
* offset rects, the OOB strict skip, the no-dedup contract, and bad args.
|
||||
*
|
||||
* The TEST owns the vmie_mem (it is the caller). Exit 0 on all-pass.
|
||||
*/
|
||||
|
||||
#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)
|
||||
|
||||
/* ---- synthetic identity page table (same mechanics as test_perception.c) --- */
|
||||
|
||||
#define PTE_P 0x1u
|
||||
#define PTE_RW 0x2u
|
||||
#define PTE_US 0x4u
|
||||
#define PTE_PS 0x80u
|
||||
#define LARGE_PAGE (2ull * 1024 * 1024)
|
||||
|
||||
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;
|
||||
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 | PTE_US;
|
||||
pdpt[pdpti] = pd_gpa | PTE_P | PTE_RW | PTE_US;
|
||||
while (mapped < span) {
|
||||
pd[pdi0 + k] = mapped | PTE_P | PTE_RW | PTE_US | PTE_PS;
|
||||
mapped += LARGE_PAGE;
|
||||
++k;
|
||||
}
|
||||
return pml4_gpa;
|
||||
}
|
||||
|
||||
/* The full-surface frame dimensions crafted in slot 0. */
|
||||
#define FRAME_W 256u
|
||||
#define FRAME_H 128u
|
||||
|
||||
/* Coordinate-derived BGRA bytes for surface pixel (px, py) — distinct per
|
||||
* channel so offset AND conversion are both checkable on concrete bytes. */
|
||||
static uint8_t pat_B(uint32_t px, uint32_t py) { (void)py; return (uint8_t)(px & 0xFFu); }
|
||||
static uint8_t pat_G(uint32_t px, uint32_t py) { (void)px; return (uint8_t)(py & 0xFFu); }
|
||||
static uint8_t pat_R(uint32_t px, uint32_t py) { return (uint8_t)((px ^ py) & 0xFFu); }
|
||||
static uint8_t pat_A(uint32_t px, uint32_t py) { (void)px; (void)py; return 0xFFu; }
|
||||
|
||||
/* Fill the slot-0 frame in the RING with the coordinate pattern (BGRA order). */
|
||||
static void fill_pattern_frame(uint8_t* img, uint32_t w, uint32_t h)
|
||||
{
|
||||
uint8_t* slot = img + VGPU_RING_OFFSET + 0u * VGPU_SLOT_STRIDE;
|
||||
uint32_t py, px;
|
||||
for (py = 0; py < h; ++py) {
|
||||
for (px = 0; px < w; ++px) {
|
||||
uint8_t* p = slot + ((size_t)py * w + px) * 4u;
|
||||
p[0] = pat_B(px, py);
|
||||
p[1] = pat_G(px, py);
|
||||
p[2] = pat_R(px, py);
|
||||
p[3] = pat_A(px, py);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void make_producer(vgpu_producer_t* p, uint32_t w, uint32_t h)
|
||||
{
|
||||
memset(p, 0, sizeof *p);
|
||||
p->latest = 0;
|
||||
p->frame_id = 7;
|
||||
p->seq[0] = 2; /* even = stable */
|
||||
p->desc[0].width = w;
|
||||
p->desc[0].height = h;
|
||||
p->desc[0].stride = w * 4u;
|
||||
p->desc[0].format = VGPU_FMT_BGRA8888;
|
||||
p->desc[0].frame_id = 7;
|
||||
p->status = VGPU_ST_CAPTURING;
|
||||
p->backend = VGPU_BK_DDA;
|
||||
p->supported_formats = (1u << VGPU_FMT_BGRA8888);
|
||||
p->heartbeat = 1;
|
||||
}
|
||||
|
||||
/* ---- the test driver, given an open RO region handle ---------------------- */
|
||||
|
||||
static void run_capture(vmie_mem* m, uint64_t base_va, uint64_t cr3, uint8_t* img)
|
||||
{
|
||||
vgpup_region rr;
|
||||
vgpup_region* r = &rr;
|
||||
|
||||
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;
|
||||
|
||||
/* an offset sub-rectangle, strictly inside the frame (case 6: x,y > 0,
|
||||
* w < width, h < height). */
|
||||
const uint32_t rx = 10, ry = 20, rw = 32, rh = 16;
|
||||
vgpup_rect rect = { rx, ry, rw, rh };
|
||||
|
||||
/* ---- case 2: FLAT × BGRA ---------------------------------------------- */
|
||||
{
|
||||
vgpup_capture* c = vgpup_capture_open(r, rect, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA);
|
||||
vgpup_capture_info info;
|
||||
int rc;
|
||||
CHECK(c != NULL, "flat/bgra open");
|
||||
rc = vgpup_capture_sync(c, m, &info);
|
||||
CHECK(rc == 1, "flat/bgra sync==1");
|
||||
CHECK(vgpup_capture_out_bpp(c) == 4u, "flat/bgra out_bpp");
|
||||
CHECK(vgpup_capture_row_bytes(c) == rw * 4u, "flat/bgra row_bytes");
|
||||
CHECK(vgpup_capture_bytes(c) == (size_t)rw * rh * 4u, "flat/bgra bytes");
|
||||
CHECK(vgpup_capture_rows(c) == NULL, "flat/bgra rows NULL");
|
||||
if (rc == 1) {
|
||||
const uint8_t* mem = vgpup_capture_mem(c);
|
||||
uint32_t yy, xx;
|
||||
int ok = 1;
|
||||
CHECK(mem != NULL, "flat/bgra mem non-NULL");
|
||||
for (yy = 0; yy < rh && ok; ++yy) {
|
||||
for (xx = 0; xx < rw; ++xx) {
|
||||
const uint8_t* px = mem + ((size_t)yy * rw + xx) * 4u;
|
||||
uint32_t sx = rx + xx, sy = ry + yy;
|
||||
if (px[0] != pat_B(sx, sy) || px[1] != pat_G(sx, sy) ||
|
||||
px[2] != pat_R(sx, sy) || px[3] != pat_A(sx, sy)) {
|
||||
ok = 0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(ok, "flat/bgra verbatim content");
|
||||
CHECK(info.frame_id == 7, "flat/bgra info frame_id"); /* case 11 */
|
||||
CHECK(info.desc.frame_id == 7, "flat/bgra desc frame_id"); /* case 11 */
|
||||
CHECK(info.bytes == (size_t)rw * rh * 4u, "flat/bgra info bytes");
|
||||
}
|
||||
|
||||
/* case 9: NO dedup — a repeat sync of the same frame returns 1 again. */
|
||||
CHECK(vgpup_capture_sync(c, m, &info) == 1, "flat/bgra no-dedup repeat==1");
|
||||
vgpup_capture_close(c);
|
||||
}
|
||||
|
||||
/* ---- case 3: FLAT × RGB24 -------------------------------------------- */
|
||||
{
|
||||
vgpup_capture* c = vgpup_capture_open(r, rect, VGPUP_LAYOUT_FLAT, VGPUP_PX_RGB24);
|
||||
vgpup_capture_info info;
|
||||
int rc;
|
||||
CHECK(c != NULL, "flat/rgb24 open");
|
||||
CHECK(vgpup_capture_out_bpp(c) == 3u, "flat/rgb24 out_bpp");
|
||||
rc = vgpup_capture_sync(c, m, &info);
|
||||
CHECK(rc == 1, "flat/rgb24 sync==1");
|
||||
CHECK(vgpup_capture_bytes(c) == (size_t)rw * rh * 3u, "flat/rgb24 bytes");
|
||||
if (rc == 1) {
|
||||
const uint8_t* mem = vgpup_capture_mem(c);
|
||||
uint32_t yy, xx;
|
||||
int ok = 1;
|
||||
for (yy = 0; yy < rh && ok; ++yy) {
|
||||
for (xx = 0; xx < rw; ++xx) {
|
||||
const uint8_t* px = mem + ((size_t)yy * rw + xx) * 3u;
|
||||
uint32_t sx = rx + xx, sy = ry + yy;
|
||||
/* R,G,B (drop A, swap B<->R) */
|
||||
if (px[0] != pat_R(sx, sy) || px[1] != pat_G(sx, sy) ||
|
||||
px[2] != pat_B(sx, sy)) {
|
||||
ok = 0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(ok, "flat/rgb24 converted content");
|
||||
CHECK(info.bytes == (size_t)rw * rh * 3u, "flat/rgb24 info bytes");
|
||||
}
|
||||
vgpup_capture_close(c);
|
||||
}
|
||||
|
||||
/* ---- case 4: ROWS × BGRA -------------------------------------------- */
|
||||
{
|
||||
vgpup_capture* c = vgpup_capture_open(r, rect, VGPUP_LAYOUT_ROWS, VGPUP_PX_BGRA);
|
||||
vgpup_capture_info info, info2;
|
||||
const uint8_t* const* rows;
|
||||
const uint8_t* saved[64];
|
||||
int rc;
|
||||
CHECK(c != NULL, "rows/bgra open");
|
||||
CHECK(vgpup_capture_mem(c) == NULL, "rows/bgra mem NULL");
|
||||
CHECK(vgpup_capture_row_count(c) == rh, "rows/bgra row_count");
|
||||
rc = vgpup_capture_sync(c, m, &info);
|
||||
CHECK(rc == 1, "rows/bgra sync==1");
|
||||
rows = vgpup_capture_rows(c);
|
||||
CHECK(rows != NULL, "rows/bgra rows non-NULL");
|
||||
if (rc == 1 && rows) {
|
||||
uint32_t yy, xx;
|
||||
int ok = 1;
|
||||
for (yy = 0; yy < rh && ok; ++yy) {
|
||||
const uint8_t* line = rows[yy];
|
||||
saved[yy] = line;
|
||||
for (xx = 0; xx < rw; ++xx) {
|
||||
const uint8_t* px = line + (size_t)xx * 4u;
|
||||
uint32_t sx = rx + xx, sy = ry + yy;
|
||||
if (px[0] != pat_B(sx, sy) || px[1] != pat_G(sx, sy) ||
|
||||
px[2] != pat_R(sx, sy) || px[3] != pat_A(sx, sy)) {
|
||||
ok = 0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(ok, "rows/bgra per-row content");
|
||||
}
|
||||
/* row pointers stable across a second sync */
|
||||
CHECK(vgpup_capture_sync(c, m, &info2) == 1, "rows/bgra second sync==1");
|
||||
{
|
||||
const uint8_t* const* rows2 = vgpup_capture_rows(c);
|
||||
uint32_t yy;
|
||||
int stable = 1;
|
||||
for (yy = 0; yy < rh; ++yy) {
|
||||
if (rows2[yy] != saved[yy]) { stable = 0; break; }
|
||||
}
|
||||
CHECK(stable, "rows/bgra row pointers stable across syncs");
|
||||
}
|
||||
vgpup_capture_close(c);
|
||||
}
|
||||
|
||||
/* ---- case 5: ROWS × RGB24 ------------------------------------------- */
|
||||
{
|
||||
vgpup_capture* c = vgpup_capture_open(r, rect, VGPUP_LAYOUT_ROWS, VGPUP_PX_RGB24);
|
||||
vgpup_capture_info info;
|
||||
const uint8_t* const* rows;
|
||||
int rc;
|
||||
CHECK(c != NULL, "rows/rgb24 open");
|
||||
CHECK(vgpup_capture_row_count(c) == rh, "rows/rgb24 row_count");
|
||||
CHECK(vgpup_capture_row_bytes(c) == rw * 3u, "rows/rgb24 row_bytes");
|
||||
rc = vgpup_capture_sync(c, m, &info);
|
||||
CHECK(rc == 1, "rows/rgb24 sync==1");
|
||||
rows = vgpup_capture_rows(c);
|
||||
if (rc == 1 && rows) {
|
||||
uint32_t yy, xx;
|
||||
int ok = 1;
|
||||
for (yy = 0; yy < rh && ok; ++yy) {
|
||||
const uint8_t* line = rows[yy];
|
||||
for (xx = 0; xx < rw; ++xx) {
|
||||
const uint8_t* px = line + (size_t)xx * 3u;
|
||||
uint32_t sx = rx + xx, sy = ry + yy;
|
||||
if (px[0] != pat_R(sx, sy) || px[1] != pat_G(sx, sy) ||
|
||||
px[2] != pat_B(sx, sy)) {
|
||||
ok = 0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(ok, "rows/rgb24 per-row converted content");
|
||||
}
|
||||
vgpup_capture_close(c);
|
||||
}
|
||||
|
||||
/* ---- case 7: coherence / retry — odd seq -> 0, even -> 1 ------------- */
|
||||
{
|
||||
vgpup_capture* c = vgpup_capture_open(r, rect, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA);
|
||||
vgpup_capture_info info;
|
||||
uint32_t seq_odd = 3, seq_even = 2;
|
||||
size_t seq0_off = offsetof(vgpu_producer_t, seq); /* slot 0 */
|
||||
CHECK(c != NULL, "retry open");
|
||||
/* writer busy: slot seq odd before sync -> retry-exhaust -> 0 */
|
||||
memcpy(img + seq0_off, &seq_odd, sizeof seq_odd);
|
||||
CHECK(vgpup_capture_sync(c, m, &info) == 0, "retry odd-seq -> 0");
|
||||
/* back to even -> coherent -> 1 */
|
||||
memcpy(img + seq0_off, &seq_even, sizeof seq_even);
|
||||
CHECK(vgpup_capture_sync(c, m, &info) == 1, "retry even-seq -> 1");
|
||||
vgpup_capture_close(c);
|
||||
}
|
||||
|
||||
/* ---- case 8: strict OOB skip (frame smaller than rect) --------------- */
|
||||
{
|
||||
/* rect valid vs ABI ceilings but past THIS frame's width -> sync 0 */
|
||||
vgpup_rect big = { FRAME_W - 8u, 0u, 16u, 8u }; /* x+w = FRAME_W+8 > width */
|
||||
vgpup_capture* c = vgpup_capture_open(r, big, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA);
|
||||
vgpup_capture_info info;
|
||||
CHECK(c != NULL, "oob open (valid vs ABI)");
|
||||
CHECK(vgpup_capture_sync(c, m, &info) == 0, "oob sync lossy skip == 0");
|
||||
vgpup_capture_close(c);
|
||||
}
|
||||
|
||||
/* ---- case 10: bad args ---------------------------------------------- */
|
||||
{
|
||||
vgpup_rect ok_rect = { 0, 0, 8, 8 };
|
||||
vgpup_rect w0 = { 0, 0, 0, 8 };
|
||||
vgpup_rect h0 = { 0, 0, 8, 0 };
|
||||
vgpup_rect xw = { VGPU_MAX_WIDTH - 4u, 0, 8, 8 }; /* x+w > MAX_WIDTH */
|
||||
vgpup_rect yh = { 0, VGPU_MAX_HEIGHT - 4u, 8, 8 }; /* y+h > MAX_HEIGHT */
|
||||
CHECK(vgpup_capture_open(NULL, ok_rect, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA) == NULL, "bad: r NULL");
|
||||
CHECK(vgpup_capture_open(r, w0, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA) == NULL, "bad: w==0");
|
||||
CHECK(vgpup_capture_open(r, h0, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA) == NULL, "bad: h==0");
|
||||
CHECK(vgpup_capture_open(r, xw, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA) == NULL, "bad: x+w>MAX");
|
||||
CHECK(vgpup_capture_open(r, yh, VGPUP_LAYOUT_FLAT, VGPUP_PX_BGRA) == NULL, "bad: y+h>MAX");
|
||||
CHECK(vgpup_capture_open(r, ok_rect, 99, VGPUP_PX_BGRA) == NULL, "bad: layout");
|
||||
CHECK(vgpup_capture_open(r, ok_rect, VGPUP_LAYOUT_FLAT, 99) == NULL, "bad: format");
|
||||
}
|
||||
|
||||
/* close on NULL is safe */
|
||||
vgpup_capture_close(NULL);
|
||||
}
|
||||
|
||||
static void run_smoke(void)
|
||||
{
|
||||
const uint64_t region_bytes = VGPU_REGION_BYTES;
|
||||
const uint64_t mapped_span = (region_bytes + LARGE_PAGE - 1) & ~(LARGE_PAGE - 1);
|
||||
const size_t total_bytes = (size_t)region_bytes + 0x3000;
|
||||
const uint64_t base_va = 0x0000000010000000ull;
|
||||
int fd;
|
||||
uint8_t* img;
|
||||
uint64_t cr3;
|
||||
vmie_mem* m;
|
||||
vgpu_producer_t p;
|
||||
|
||||
fd = memfd_create("vgpu-cap-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; }
|
||||
|
||||
make_producer(&p, FRAME_W, FRAME_H);
|
||||
memcpy(img + VGPU_PRODUCER_OFFSET, &p, sizeof p);
|
||||
fill_pattern_frame(img, FRAME_W, FRAME_H);
|
||||
|
||||
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; }
|
||||
|
||||
run_capture(m, base_va, cr3, img);
|
||||
|
||||
vmie_mem_close(m);
|
||||
munmap(img, total_bytes);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
run_smoke();
|
||||
if (g_fail) {
|
||||
fprintf(stderr, "%d check(s) failed\n", g_fail);
|
||||
return 1;
|
||||
}
|
||||
printf("all checks passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user