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
This commit is contained in:
2026-06-22 17:25:06 +03:00
parent 0d387a4249
commit 9bde398b6c
55 changed files with 4703 additions and 61 deletions
@@ -0,0 +1,152 @@
#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 */