mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-06-20 19:06:37 +03:00
vmsig: a neutral signaling layer between sensors/input and controls
An epoll-driven, neutral transfer-event bus that connects sensors and input actuators to one or more controls, bidirectionally. It owns the transfer context and events — delivery order, priority, protocol-level timing, and an interrupt-driven event model over fd sources (eventfd/timerfd/sockets) — and stays agnostic to both the sensor/input drivers and the control. What lives here: - memctx: a coherent address-space context per endpoint — the guest address-space root paired with a pre-opened read-only RAM-region fd, with per-endpoint epoch invalidation and retained replay to late subscribers. Perception lives in out-of-tree sensor libraries that consume this datum read-only. - exclusive-ownership leases for destructive resource classes (input, power, memory-write). - write-signaled memory writes (MEMWRITE): an atomic write to guest memory routed through the seam under an exclusive lease, never a writable mapping. - a host-management seam for VM lifecycle/status and a neutral input-injection command path. - multi-VM endpoints; capability-gated, audited control authorization over an in-process or unix-socket transport. Builds against headers only by default (a stub mode that exercises the seam without a VM); armed builds link the real sensor/input libraries behind flags. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
#ifndef VMSIG_EVENT_H
|
||||
#define VMSIG_EVENT_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* vmsig_event.h — neutral "transfer event" + "payload" model.
|
||||
*
|
||||
* This is the ONLY type that crosses the signaling core. The taxonomy names the
|
||||
* transfer SEMANTICS, not neighbor types: a TU compiled against this header
|
||||
* cannot name vmctl_batch, vgpu_producer_t, or vmie_mem. The SI data body lives
|
||||
* in an opaque payload owned by the source adapter's codec; the core does NOT
|
||||
* dereference it — it only routes the event and carries the payload. */
|
||||
|
||||
/* Transfer direction relative to control. */
|
||||
typedef enum {
|
||||
VMSIG_DIR_UP = 0, /* sensor/state -> control */
|
||||
VMSIG_DIR_DOWN = 1 /* control decision -> actuation/SI */
|
||||
} vmsig_dir;
|
||||
|
||||
/* Logical seam (SI role) the event crosses. NEUTRAL roles, not driver names:
|
||||
* assigned at adapter registration, used only for routing, the priority default,
|
||||
* and the subscription filter. */
|
||||
typedef enum {
|
||||
VMSIG_SRC_NONE = 0,
|
||||
VMSIG_SRC_FRAME = 1, /* vgpu desktop sensor role; reserved: no signaling adapter,
|
||||
* the future vgpu-perception shell-as-control carries it (CURSOR_STATE) */
|
||||
VMSIG_SRC_INPUT = 2, /* input/actuation + lifecycle (vmctl role) */
|
||||
VMSIG_SRC_CONTROL = 3, /* originated by a control endpoint */
|
||||
VMSIG_SRC_CORE = 4, /* core-internal (shutdown/error/tick) */
|
||||
VMSIG_SRC_VMHOST = 5, /* VM substrate / QEMU: lifecycle + events (own QMP) */
|
||||
VMSIG_SRC_MEMCTX = 6, /* coherent guest address-space context (kcr3+locator) */
|
||||
VMSIG_SRC_MAX
|
||||
} vmsig_source;
|
||||
|
||||
/* Delivery priority class. Higher value — earlier delivery. This is NOT a
|
||||
* behavioral timing but ordering on the "wire". The default is assigned per
|
||||
* source at registration; the emitter may override it per event. */
|
||||
typedef enum {
|
||||
VMSIG_PRIO_BULK = 0, /* frames, large state deltas */
|
||||
VMSIG_PRIO_NORMAL = 1, /* routine ack/samples */
|
||||
VMSIG_PRIO_HIGH = 2, /* input commands (latency-sensitive) */
|
||||
VMSIG_PRIO_URGENT = 3, /* lifecycle, seam-down, errors */
|
||||
VMSIG_PRIO_MAX
|
||||
} vmsig_prio;
|
||||
|
||||
/* NEUTRAL event taxonomy: each kind is a transfer MEANING that exactly one
|
||||
* adapter codec decodes from / encodes into its contract. The core routes by
|
||||
* kind + source + dir + prio and does not interpret the payload. */
|
||||
typedef enum {
|
||||
/* --- generic / lifecycle (any seam) --- */
|
||||
VMSIG_EV_NONE = 0,
|
||||
VMSIG_EV_SEAM_UP = 1, /* SI seam came up (attach/bootstrap ok) */
|
||||
VMSIG_EV_SEAM_DOWN = 2, /* seam lost (heartbeat stale, socket closed) */
|
||||
VMSIG_EV_ERROR = 3, /* adapter/core error, details in payload */
|
||||
|
||||
/* (16..19 — retired STATE_* of the MEMSTATE seam; do NOT reuse numbers: on a
|
||||
* version skew an old STATE kind must not alias a new kind on the wire.) */
|
||||
|
||||
/* (32..36 — retired FRAME_READY/FRAME_STATE/BULK_ATTACHED/BULK_READY/BULK_DETACHED of
|
||||
* the removed FRAME adapter + bulk data-plane (vgpu perception moved to an S-lib);
|
||||
* do NOT reuse numbers — wire-skew safety.) */
|
||||
|
||||
/* --- UP: cursor (vgpu sensor; emitted by the vgpu-perception shell-as-control) --- */
|
||||
VMSIG_EV_CURSOR_STATE = 37, /* cursor position/visibility; inln=vmsig_cursor; cap OBSERVE|INPUT */
|
||||
|
||||
/* --- UP: input/lifecycle ack (INPUT seam) --- */
|
||||
VMSIG_EV_ACT_ACK = 48, /* down-command completed (ok/err) */
|
||||
VMSIG_EV_VM_LIFECYCLE = 49, /* power/lifecycle state report */
|
||||
|
||||
/* --- UP: lease arbitration (all addressed, origin=initiator; source=CORE) --- */
|
||||
VMSIG_EV_LEASE_GRANTED = 50, /* lease granted (CMD_ACQUIRE succeeded) */
|
||||
VMSIG_EV_LEASE_DENIED = 51, /* lease denied (reason in vmsig_lease_req) */
|
||||
VMSIG_EV_LEASE_RELEASED= 52, /* lease released by owner (CMD_RELEASE) */
|
||||
VMSIG_EV_LEASE_REVOKED = 53, /* lease taken away by preemption/death */
|
||||
VMSIG_EV_LEASE_STATUS = 54, /* response to CMD_LEASE_STATUS (vmsig_lease_status) */
|
||||
|
||||
/* --- UP: response to a held-input query (INPUT seam, addressed to initiator) --- */
|
||||
VMSIG_EV_INPUT_HELD = 55, /* set of held KEY/BTN from the vmctl record; inln=vmsig_input_held */
|
||||
|
||||
/* --- DOWN: control decisions --- */
|
||||
VMSIG_EV_CMD_INPUT = 64, /* input injection (abs/rel/btn/key/scroll) */
|
||||
VMSIG_EV_CMD_LIFECYCLE = 65, /* powerdown/reset/wakeup/pause/resume */
|
||||
/* (66 — retired CMD_STREAM of the removed FRAME adapter; the future vgpu-control
|
||||
* down-path returns via write-signaled/MEMWRITE. 67..69 — retired
|
||||
* CMD_QUERY/WATCH/UNWATCH; do NOT reuse numbers.) */
|
||||
VMSIG_EV_CMD_VM = 70, /* base VM control (vmsig_vm_cmd; VMHOST seam) */
|
||||
/* (71..72 — retired CMD_SUBSCRIBE_BULK/UNSUBSCRIBE_BULK of the bulk data-plane;
|
||||
* do NOT reuse numbers.) */
|
||||
|
||||
/* --- DOWN: lease arbitration (intercepted by the core, not forwarded to the adapter) --- */
|
||||
VMSIG_EV_CMD_ACQUIRE = 73, /* request an exclusive lease of a class: inln=vmsig_lease_req */
|
||||
VMSIG_EV_CMD_RELEASE = 74, /* release your own lease of a class: inln=vmsig_lease_req */
|
||||
VMSIG_EV_CMD_LEASE_STATUS = 75, /* query lease status of a class: inln=vmsig_lease_req */
|
||||
VMSIG_EV_CMD_QUERY_INPUT = 76, /* query held KEY/BTN (from the vmctl record); reply UP INPUT_HELD; cap INPUT */
|
||||
|
||||
/* --- UP: address-space context (MEMCTX seam; coherent kcr3+locator datum) --- */
|
||||
VMSIG_EV_MEMCTX = 77, /* context multicast/replay: inln=vmsig_memctx,
|
||||
* payload=vmsig_memseg[] (owned), RO-fd alongside */
|
||||
VMSIG_EV_MEMCTX_INVALIDATED = 78, /* epoch invalidation: inln=vmsig_memctx_inv (URGENT) */
|
||||
|
||||
/* --- DOWN: coherent memory write (write-signaled; MEMCTX seam) --- */
|
||||
VMSIG_EV_CMD_MEMWRITE = 79, /* atomic gva_write under the held lease; inln=vmsig_memwrite (+tail/payload bytes);
|
||||
* cap MEMWRITE + lease MEMWRITE + extent. ACK via ACT_ACK{ok,corr}. */
|
||||
VMSIG_EV_KIND_MAX
|
||||
} vmsig_kind;
|
||||
|
||||
/* ===== Lease arbitration (exclusive-ownership layer for destructive resources) =====
|
||||
* A destructive VM resource is owned by EXACTLY one control (per endpoint+class pair).
|
||||
* The class is generic; INPUT, POWER and MEMWRITE are active. MEMWRITE is the
|
||||
* write-signaled atomic guest-memory write on the MEMCTX seam. */
|
||||
typedef enum {
|
||||
VMSIG_LEASE_INPUT = 0, /* exclusive grab of input (CMD_INPUT) */
|
||||
VMSIG_LEASE_POWER = 1, /* exclusive destructive power (lifecycle/VM) */
|
||||
VMSIG_LEASE_MEMWRITE = 2, /* exclusive atomic guest-memory write (gva_write); NO finalization */
|
||||
VMSIG_LEASE_CLASS_MAX
|
||||
} vmsig_lease_class;
|
||||
|
||||
/* Lease denial reason (vmsig_lease_req.reason in UP LEASE_DENIED). */
|
||||
enum {
|
||||
VMSIG_LEASE_DENY_HELD = 0, /* held by an equal/higher; the owner holds it */
|
||||
VMSIG_LEASE_DENY_NOCAP = 1, /* no cap for the class (CAP_INPUT/CAP_POWER) */
|
||||
VMSIG_LEASE_DENY_NOGRANT = 2, /* endpoint outside the grant (endpoint_mask) */
|
||||
VMSIG_LEASE_DENY_BADCLASS = 3, /* class out of range */
|
||||
VMSIG_LEASE_DENY_LOWER_PRIO = 4 /* contender priority not above the owner's */
|
||||
};
|
||||
|
||||
/* Lease request/response (DOWN CMD_ACQUIRE/RELEASE/LEASE_STATUS and UP LEASE_*, in inln). */
|
||||
typedef struct {
|
||||
uint32_t cls; /* vmsig_lease_class */
|
||||
uint32_t reason; /* DOWN: 0; UP LEASE_DENIED: VMSIG_LEASE_DENY_* */
|
||||
} vmsig_lease_req;
|
||||
|
||||
/* Response to CMD_LEASE_STATUS (UP LEASE_STATUS, in inln). */
|
||||
typedef struct {
|
||||
uint32_t cls; /* requested class */
|
||||
uint32_t busy; /* 1=held by a live owner, 0=free */
|
||||
uint32_t owner_principal; /* owner principal (for audit/UI); 0 if free */
|
||||
} vmsig_lease_status;
|
||||
|
||||
/* Lifecycle operations for CMD_LIFECYCLE (code in inln[0]). Destructive ones
|
||||
* (POWERDOWN/RESET) require CAP_POWER; safe ones — CAP_LIFECYCLE. */
|
||||
enum {
|
||||
VMSIG_LIFE_POWERDOWN = 0,
|
||||
VMSIG_LIFE_RESET = 1,
|
||||
VMSIG_LIFE_WAKEUP = 2,
|
||||
VMSIG_LIFE_PAUSE = 3,
|
||||
VMSIG_LIFE_RESUME = 4
|
||||
};
|
||||
|
||||
/* ===== Input (DOWN VMSIG_EV_CMD_INPUT, in inln) — NEUTRAL =====
|
||||
* control describes input abstractly (axis/button/key/scroll), WITHOUT knowing the driver
|
||||
* (uinput/QMP): the input adapter translates it into its contract. Requires CAP_INPUT. This
|
||||
* is the ONLY public input-encoding contract — an external control encodes vmsig_input into
|
||||
* vmsig_event.inln. */
|
||||
typedef enum {
|
||||
VMSIG_INPUT_ABS = 0, /* absolute axis: code=axis, value=coordinate */
|
||||
VMSIG_INPUT_REL = 1, /* relative axis: code=axis, value=delta */
|
||||
VMSIG_INPUT_BTN = 2, /* button: code=button, value=pressed(1)/released(0) */
|
||||
VMSIG_INPUT_KEY = 3, /* key: code=evdev code, value=pressed/released */
|
||||
VMSIG_INPUT_SCROLL = 4 /* scroll: code=axis, scroll=magnitude */
|
||||
} vmsig_input_kind;
|
||||
|
||||
typedef struct {
|
||||
uint32_t kind; /* vmsig_input_kind */
|
||||
int32_t code; /* axis / button / evdev code (neutral event code) */
|
||||
int32_t value; /* abs coordinate / rel delta / pressed(1)|released(0) */
|
||||
double scroll; /* scroll magnitude (VMSIG_INPUT_SCROLL only) */
|
||||
} vmsig_input; /* fits in vmsig_event.inln[48] */
|
||||
|
||||
/* ===== Memory write (DOWN VMSIG_EV_CMD_MEMWRITE) — NEUTRAL, write-signaled =====
|
||||
* control describes an ATOMIC write into guest memory abstractly (guest VA + length),
|
||||
* WITHOUT knowing vmie/cr3: the memctx adapter resolves it under the held kcr3 and does
|
||||
* ONE gva_write. Requires CAP_MEMWRITE + an exclusive MEMWRITE lease + an extent check.
|
||||
* SRC bytes: inline (<= VMSIG_MEMWRITE_INLINE) ride in the inln tail right after this header
|
||||
* (flags & INLINE); larger in-proc writes ride in the borrowed payload (flags & PAYLOAD). */
|
||||
#define VMSIG_MEMWRITE_INLINE 32u /* inln tail capacity for SRC (48 - 16 header) */
|
||||
#define VMSIG_MW_SRC_INLINE 0x1u /* SRC bytes are in inln tail (len<=INLINE) */
|
||||
#define VMSIG_MW_SRC_PAYLOAD 0x2u /* SRC bytes are in ev->payload.data (in-proc) */
|
||||
typedef struct {
|
||||
uint64_t gva; /* guest virtual address to write (resolved under the adapter's kcr3) */
|
||||
uint32_t len; /* number of bytes to write (1..VMSIG_MEMWRITE_MAX) */
|
||||
uint32_t flags; /* VMSIG_MW_SRC_INLINE | VMSIG_MW_SRC_PAYLOAD */
|
||||
/* inline SRC tail (when VMSIG_MW_SRC_INLINE): up to VMSIG_MEMWRITE_INLINE bytes follow */
|
||||
} vmsig_memwrite; /* header = 8+4+4 = 16 bytes; +32 tail = 48 (exactly inln[48]) */
|
||||
|
||||
/* ===== Cursor (UP VMSIG_EV_CURSOR_STATE, in inln) — NEUTRAL =====
|
||||
* Cursor position from the SCREEN sensor (vgpu). NEUTRAL payload format only: emitted by the
|
||||
* out-of-repo vgpu-perception shell-as-control (source VMSIG_SRC_FRAME), not by a signaling
|
||||
* adapter — signaling just fans it out. x,y signed (multi-monitor -> negative). cap OBSERVE|INPUT. */
|
||||
typedef struct {
|
||||
int32_t x; /* screen coordinate X (signed) */
|
||||
int32_t y; /* screen coordinate Y (signed) */
|
||||
uint32_t visible; /* 1=shown, 0=hidden */
|
||||
uint32_t seq; /* monotonic cursor-publication counter (vgpu) */
|
||||
} vmsig_cursor;
|
||||
|
||||
/* ===== Held input (UP VMSIG_EV_INPUT_HELD, in inln) — response to CMD_QUERY_INPUT =====
|
||||
* Set of held KEY/BTN from the ACTUATOR record (vmctl): signaling only returns it on request,
|
||||
* does NOT track it itself and does NOT decide release (that is control). flags & TRUNC => more
|
||||
* held than ent. */
|
||||
#define VMSIG_INPUT_HELD_TRUNC 0x1u
|
||||
typedef struct {
|
||||
uint32_t count; /* number of valid entries in ent[] */
|
||||
uint32_t flags; /* VMSIG_INPUT_HELD_TRUNC if more held than capacity */
|
||||
struct { uint16_t kind; uint16_t code; } ent[10]; /* kind=VMSIG_INPUT_KEY/BTN; code */
|
||||
} vmsig_input_held; /* 4+4+10*4 = 48 (exactly inln[48]) */
|
||||
|
||||
/* ===== QEMU/QMP host-plane (VMHOST seam) — VM-substrate control =====
|
||||
* VM state (UP VMSIG_EV_VM_LIFECYCLE, in inln). */
|
||||
enum {
|
||||
VMSIG_VM_RUNNING = 0, VMSIG_VM_PAUSED, VMSIG_VM_SHUTDOWN,
|
||||
VMSIG_VM_RESET, VMSIG_VM_POWERDOWN, VMSIG_VM_CRASHED, VMSIG_VM_UNKNOWN
|
||||
};
|
||||
typedef struct { uint32_t state; uint32_t detail; } vmsig_vm_state;
|
||||
|
||||
/* VM control operations (DOWN VMSIG_EV_CMD_VM, in inln). Destructive ones
|
||||
* (RESET/POWERDOWN/QUIT) require CAP_POWER; safe ones — CAP_VM. */
|
||||
enum {
|
||||
VMSIG_VMOP_QUERY = 0, /* query-status */
|
||||
VMSIG_VMOP_CONT, /* cont (resume) */
|
||||
VMSIG_VMOP_STOP, /* stop (pause) */
|
||||
VMSIG_VMOP_RESET, /* system_reset (destructive) */
|
||||
VMSIG_VMOP_POWERDOWN, /* system_powerdown (destructive) */
|
||||
VMSIG_VMOP_QUIT /* quit (destructive) */
|
||||
};
|
||||
typedef struct { uint32_t op; } vmsig_vm_cmd;
|
||||
|
||||
/* Codec tags: which adapter owns the payload body (for release/diagnostics). */
|
||||
typedef enum {
|
||||
VMSIG_CODEC_NONE = 0,
|
||||
VMSIG_CODEC_INPUT = 1,
|
||||
VMSIG_CODEC_VMHOST = 2,
|
||||
VMSIG_CODEC_MEMCTX = 3 /* owned-payload locator (vmsig_memseg[]) of the MEMCTX seam */
|
||||
} vmsig_codec;
|
||||
|
||||
/* Payload ownership flags. */
|
||||
#define VMSIG_PL_OWNED 0x1u /* core frees it via release() on drop */
|
||||
#define VMSIG_PL_BORROWED 0x2u /* borrowed (e.g. a seqlock frame): copy */
|
||||
/* or revalidate before release() */
|
||||
#define VMSIG_PL_INLINE 0x4u /* small body lives in vmsig_event.inln */
|
||||
|
||||
/* Opaque, releasable payload. The body is owned by the emitting adapter's codec
|
||||
* (mmap'd frame slot, vmie heap diff, ...). The core carries the bearer and calls
|
||||
* release() EXACTLY once on consumption/drop. The core never dereferences data. */
|
||||
typedef struct vmsig_payload {
|
||||
void* data; /* opaque body, codec-defined */
|
||||
size_t len; /* bytes in data (0 if borrowed) */
|
||||
uint32_t codec; /* vmsig_codec: whose payload it is */
|
||||
uint32_t flags; /* VMSIG_PL_* */
|
||||
void (*release)(struct vmsig_payload*); /* idempotent; may be NULL */
|
||||
void* owner; /* codec context for release() */
|
||||
} vmsig_payload;
|
||||
|
||||
/* TRANSFER EVENT. Fixed-size header + a small inline zone; large bodies hang off
|
||||
* the payload. */
|
||||
typedef struct vmsig_event {
|
||||
vmsig_kind kind;
|
||||
vmsig_source source; /* source seam */
|
||||
vmsig_dir dir;
|
||||
vmsig_prio prio;
|
||||
uint32_t endpoint; /* VM/endpoint id — multi-VM-ready */
|
||||
uint32_t seq; /* monotonic sequence (set by the context) */
|
||||
uint32_t corr; /* correlation: links an ACK to its CMD */
|
||||
uint32_t origin; /* INTERNAL: id+1 of the control that initiated DOWN (0=none/broadcast). */
|
||||
/* Set by the core in emit_down; NOT serialized onto the wire */
|
||||
/* (a poller cannot forge it). Addressed reply delivery. */
|
||||
uint64_t ts_ns; /* CLOCK_MONOTONIC at emit time */
|
||||
vmsig_payload payload; /* opaque body (may be empty) */
|
||||
uint8_t inln[48]; /* inline zone for small events (VMSIG_PL_INLINE) */
|
||||
} vmsig_event;
|
||||
|
||||
/* Release the event's payload (if it has release and is not yet freed). Idempotent. */
|
||||
static inline void vmsig_payload_release(vmsig_event* ev) {
|
||||
if (ev && ev->payload.release) {
|
||||
ev->payload.release(&ev->payload);
|
||||
ev->payload.release = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* VMSIG_EVENT_H */
|
||||
Reference in New Issue
Block a user