mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-06-26 04:36:37 +03:00
153 lines
6.7 KiB
C
153 lines
6.7 KiB
C
|
|
#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 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
|
|||
|
|
* 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 */
|