Files
vatrog-vm-signaling/src/si/vgpu-perception/include/perception-internal.h
T
lirent 9bde398b6c vmsig: management daemon, runtime endpoint lifecycle, roster, discovery, in-tree drivers, packaging
- core: runtime attach/detach of a per-endpoint adapter trio (runtime-safe add_adapter + vmsig_core_detach_endpoint, deferred reap)
- roster: VMSIG_EV_ROSTER + CAP_ROSTER, retained per-endpoint and replayed to late subscribers
- discovery: inotify trigger dir, vmid/endpoint slot allocator, host probe; vmsigd daemon with config + per-uid admission
- input driver and vgpu perception built in-tree; vgpu perception as a separate library
- memctx: own the supplied ro_fd (closed at detach)
- deb packaging: install rules, systemd unit, tmpfiles, default config
2026-06-22 17:25:06 +03:00

153 lines
6.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef VGPU_PERCEPTION_INTERNAL_H
#define VGPU_PERCEPTION_INTERNAL_H
/* perception-internal.h — private consumer-side helpers (NOT a public surface).
*
* Holds the core's private state type, the consumer-side seqlock read discipline
* (the mirror of the producer's atomic-shim accessors, but an independent body —
* we read into local copies via gva_read, never sharing producer code), the
* structural-invariant validator table used by discovery, and the bit unpackers
* for the packed cursor fields. Included only by the perception TUs.
*
* Consumer seqlock discipline: every guest read goes through gva_read into a
* local copy, so the compiler cannot reorder a data read across the seq read —
* each gva_read is an opaque call. We still bump the seq read into its own
* gva_read and treat odd seq / changed seq as "writer in flight → retry".
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "vgpu_stream.h"
#include "memmodel.h"
#include "vgpu_perception.h"
/* Bounded seqlock retry. Producer windows are short (a single slot publish), so
* a small count suffices; spinning longer would be a behavioural timing choice
* (control's job), which does not belong in the sensor. Exhausted → lossy skip. */
#define VGPUP_SEQLOCK_RETRIES 8u
/* Private core state. Owns nothing of the address space — only where the region
* lives (in the producer's user-AS, keyed by proc_cr3) and the last-seen
* monotonic markers for dedup / session-break. */
struct vgpup_region {
uint64_t proc_cr3; /* producer process cr3 — key to its user-AS */
uint64_t region_gva; /* producer-block GVA == region base */
uint64_t ctrl_gva; /* region_gva + VGPU_CONTROL_OFFSET (cached) */
uint64_t ring_gva; /* region_gva + VGPU_RING_OFFSET (cached) */
uint64_t last_frame_id; /* dedup: only frames with a greater id are "fresh" */
uint32_t run_epoch; /* last run_epoch seen via vgpup_read_status */
};
/* Per-cr3 user-AS region scan (discovery steps 35 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
* the structural-invariant table holds. On the first hit writes the region base
* GVA to *out_region_gva and the heartbeat snapshot to *out_hb0 and returns 0;
* <0 if none is found / a read fails. Pure gva_* (no proc_list / win32) so it is
* testable under a synthetic cr3; vgpup_discover_candidate calls it per process. */
int vgpup_scan_user_as_for_region(vmie_mem* m, uint64_t cr3,
uint64_t* out_region_gva, uint64_t* out_hb0);
/* ---- seqlock primitives -------------------------------------------------- */
static inline int vgpup_seq_is_writing(uint32_t seq) { return (seq & 1u) != 0u; }
/* Read one 32-bit seq field at `gva` into *out under `cr3` (the producer's
* user-AS cr3). 0 on success, <0 on read error. */
static inline int vgpup_read_seq(vmie_mem* m, uintptr_t cr3, uint64_t gva,
uint32_t* out)
{
return gva_read(m, cr3, (uintptr_t)gva, out, sizeof *out) < 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); }
static inline int32_t vgpup_cursor_y(uint64_t pos) { return (int32_t)(uint32_t)(pos >> 32); }
static inline uint16_t vgpup_lo16(uint32_t v) { return (uint16_t)(v & 0xFFFFu); }
static inline uint16_t vgpup_hi16(uint32_t v) { return (uint16_t)(v >> 16); }
/* ---- structural-invariant validator (discovery, BY TABLE — no magic) ------
*
* Discovery has no magic field in the ABI (the owner forbids one). The
* discriminator is the conjunction of structural invariants derived from the
* ABI bounds in vgpu_stream.h, plus the two-phase heartbeat liveness handled by
* the caller. The predicates run cheap→costly with early exit; each takes a
* decoded producer-block snapshot and returns 1 (holds) / 0 (rejects). */
typedef int (*vgpup_inv_fn)(const vgpu_producer_t* p);
/* Is `latest` a valid slot index, or the legitimate "no frame yet" sentinel?
* latest == NONE is NOT a rejection (a freshly-started region has no frame). */
static inline int vgpup_inv_latest_in_range(const vgpu_producer_t* p)
{
return p->latest == VGPU_LATEST_NONE || p->latest < VGPU_SLOT_COUNT;
}
/* If a frame is published, its slot seq must be even (stable, not mid-write). */
static inline int vgpup_inv_latest_seq_stable(const vgpu_producer_t* p)
{
if (p->latest == VGPU_LATEST_NONE) { return 1; }
return !vgpup_seq_is_writing(p->seq[p->latest]);
}
/* If a frame is published, its descriptor must be a tight BGRA frame within the
* ABI dimension bounds. */
static inline int vgpup_inv_latest_desc_valid(const vgpu_producer_t* p)
{
const vgpu_desc_t* d;
if (p->latest == VGPU_LATEST_NONE) { return 1; }
d = &p->desc[p->latest];
if (d->format != VGPU_FMT_BGRA8888) { return 0; }
if (d->width == 0u || d->width > VGPU_MAX_WIDTH) { return 0; }
if (d->height == 0u || d->height > VGPU_MAX_HEIGHT) { return 0; }
if (d->stride != d->width * 4u) { return 0; }
return 1;
}
/* Cold-line status enum must be in the ABI range. */
static inline int vgpup_inv_status_in_range(const vgpu_producer_t* p)
{
return p->status <= VGPU_ST_ERROR;
}
/* Cold-line backend enum must be in the ABI range. */
static inline int vgpup_inv_backend_in_range(const vgpu_producer_t* p)
{
return p->backend <= VGPU_BK_GDI;
}
/* The producer must advertise the one wire format we consume. */
static inline int vgpup_inv_supports_bgra(const vgpu_producer_t* p)
{
return (p->supported_formats & (1u << VGPU_FMT_BGRA8888)) != 0u;
}
/* The invariant table, cheap→costly. A candidate is accepted (phase 1) iff
* every predicate holds; the table is the single discriminator, no scattered
* ifs and no hardcoded numbers (all bounds come from vgpu_stream.h). */
static const vgpup_inv_fn VGPUP_INVARIANTS[] = {
vgpup_inv_latest_in_range,
vgpup_inv_status_in_range,
vgpup_inv_backend_in_range,
vgpup_inv_supports_bgra,
vgpup_inv_latest_seq_stable,
vgpup_inv_latest_desc_valid,
};
#define VGPUP_INVARIANT_COUNT (sizeof(VGPUP_INVARIANTS) / sizeof(VGPUP_INVARIANTS[0]))
/* Run the whole invariant table over a decoded producer-block snapshot.
* Returns 1 if every predicate holds, 0 on the first rejection. */
static inline int vgpup_invariants_hold(const vgpu_producer_t* p)
{
size_t i;
for (i = 0; i < VGPUP_INVARIANT_COUNT; ++i) {
if (!VGPUP_INVARIANTS[i](p)) { return 0; }
}
return 1;
}
#endif /* VGPU_PERCEPTION_INTERNAL_H */