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,21 @@
|
||||
#ifndef VMSIG_H
|
||||
#define VMSIG_H
|
||||
|
||||
/* vmsig.h — umbrella header for the signaling layer of the SISC paradigm.
|
||||
*
|
||||
* vmsig binds the three SI repos (sensors vmie/vgpustream + input vmctl) to the control
|
||||
* (algorithm OR human), bidirectionally translating transfer events. The layer itself
|
||||
* contains NO sensing, actuation, or decision/behavioral-timing logic.
|
||||
*
|
||||
* Application wiring: create the context (vmsig_ctx_new) -> core (vmsig_core_new) ->
|
||||
* register per-VM adapters (vmsig_core_add_adapter) and control
|
||||
* (vmsig_core_add_control) -> vmsig_core_run. */
|
||||
|
||||
#include "vmsig_event.h" /* neutral transfer-event model + payload */
|
||||
#include "vmsig_memctx.h" /* address-space context handoff contract (kcr3+locator) */
|
||||
#include "vmsig_ctx.h" /* transfer context: priority/seq/protocol timing */
|
||||
#include "vmsig_adapter.h" /* unified SI adapter interface + factories */
|
||||
#include "vmsig_control.h" /* control-agnostic seam + reference in-proc */
|
||||
#include "vmsig_core.h" /* epoll core */
|
||||
|
||||
#endif /* VMSIG_H */
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef VMSIG_ADAPTER_H
|
||||
#define VMSIG_ADAPTER_H
|
||||
#include "vmsig_event.h"
|
||||
#include "vmsig_memctx.h" /* vmsig_memctx_reg — address-space context registration seam */
|
||||
|
||||
/* vmsig_adapter.h — unified SI adapter interface. One vtable, three readiness
|
||||
* shapes behind it. The adapter is the ONLY place that includes neighbor headers
|
||||
* (memmodel.h/win32.h/vgpu_stream.h/vmctl.h). It registers 0..N fds with the core;
|
||||
* the core does not know whether it is a socket, eventfd or timerfd. SI specifics
|
||||
* never leave these functions. */
|
||||
|
||||
typedef struct vmsig_adapter vmsig_adapter; /* opaque adapter instance */
|
||||
|
||||
/* How the adapter expresses readiness. The core treats all three as ordinary
|
||||
* epoll fds; the enum is documentation + the choice of default epoll flags. */
|
||||
typedef enum {
|
||||
VMSIG_RDY_FD = 0, /* native pollable fd (socket) */
|
||||
VMSIG_RDY_TIMERFD = 1, /* timerfd; adapter samples shared memory */
|
||||
VMSIG_RDY_EVENTFD = 2 /* worker thread bridges a blocking API -> eventfd */
|
||||
} vmsig_readiness;
|
||||
|
||||
/* Sink handed by the core to the adapter for emitting UP events without knowing
|
||||
* the internals of the context. emit() is thread-safe (also called from worker
|
||||
* threads); register_memctx/unregister_memctx are called ONLY on the loop thread.
|
||||
* The registration hooks may be NULL (adapters/tests need not call them). */
|
||||
typedef struct {
|
||||
int (*emit)(void* token, vmsig_event* ev); /* UP (thread-safe) */
|
||||
int (*register_memctx)(void* token, const vmsig_memctx_reg* reg); /* loop thread: AS context; 0/-1 */
|
||||
void (*unregister_memctx)(void* token, uint32_t endpoint); /* loop thread: context gone */
|
||||
void* token;
|
||||
} vmsig_emit;
|
||||
|
||||
/* One fd contributed by the adapter, with epoll flags and a cookie for demux. */
|
||||
typedef struct {
|
||||
int fd;
|
||||
uint32_t epoll_events; /* EPOLLIN / EPOLLOUT / ... */
|
||||
vmsig_readiness shape;
|
||||
uint32_t cookie; /* adapter-private fd discriminator */
|
||||
} vmsig_fd_reg;
|
||||
|
||||
/* Adapter vtable. Each SI adapter implements this; SI specifics do not leak. */
|
||||
typedef struct vmsig_adapter_ops {
|
||||
const char* name; /* "memctx"/"input"/"vmhost" — diagnostics */
|
||||
vmsig_source source; /* neutral seam role */
|
||||
uint32_t codec; /* vmsig_codec owned by the adapter */
|
||||
|
||||
/* Create an instance from opaque cfg (adapter parses it; core passes as-is).
|
||||
* Returns an instance or NULL. `endpoint` is the id of the VM it binds to. */
|
||||
vmsig_adapter* (*open)(const void* cfg, uint32_t endpoint);
|
||||
|
||||
/* Attach: open the SI contract, bring up workers, hand fds into reg[]
|
||||
* (<=cap), store `emit` for UP. Returns the number of registered fds (>=0) / -1. */
|
||||
int (*attach)(vmsig_adapter* a, const vmsig_emit* emit,
|
||||
vmsig_fd_reg* reg, int cap);
|
||||
|
||||
/* Readiness of one of the adapter's fds: `cookie` identifies the fd, `events`
|
||||
* are the epoll flags. The adapter does NON-blocking work (reads the socket /
|
||||
* drains the eventfd / reads the timerfd + samples counters) and calls emit on
|
||||
* each UP. 0 — ok, -1 — fatal (the core detaches the adapter). */
|
||||
int (*on_readiness)(vmsig_adapter* a, uint32_t cookie, uint32_t events);
|
||||
|
||||
/* Consume a DOWN event (a control decision): encode it into the contract
|
||||
* (vmctl_batch / vmctl power; write the vgpu control block; read request to vmie).
|
||||
* For blocking sinks it hands the work to a worker and returns immediately;
|
||||
* completion arrives later as an UP VMSIG_EV_ACT_ACK (keyed by ev->corr).
|
||||
* 0 — accepted, 1 — rejected (not for this seam), -1 — error. */
|
||||
int (*submit)(vmsig_adapter* a, const vmsig_event* ev);
|
||||
|
||||
/* Detach + free: stop workers, close SI handles and fds. */
|
||||
void (*close)(vmsig_adapter* a);
|
||||
} vmsig_adapter_ops;
|
||||
|
||||
/* Factories (defined in each adapter's TU — the only symbol the build/cli layer
|
||||
* needs; keeps neighbor headers out of the core's include-path). */
|
||||
const vmsig_adapter_ops* vmsig_memctx_ops(void); /* vmie: address-space context (kcr3+locator) */
|
||||
const vmsig_adapter_ops* vmsig_input_ops(void); /* vmctl */
|
||||
const vmsig_adapter_ops* vmsig_vmhost_ops(void); /* QEMU/QMP (its own signaling) */
|
||||
/* (vgpu frame sensor is no longer a signaling adapter: vgpu perception lives in an
|
||||
* out-of-repo S-lib that consumes memctx; see vgpu-perception-handoff.) */
|
||||
|
||||
#endif /* VMSIG_ADAPTER_H */
|
||||
@@ -0,0 +1,126 @@
|
||||
#ifndef VMSIG_CONTROL_H
|
||||
#define VMSIG_CONTROL_H
|
||||
#include "vmsig_event.h"
|
||||
|
||||
/* vmsig_control.h — control-agnostic seam. Control (an algorithm OR a human)
|
||||
* attaches via ONE neutral interface: a command queue (down) + an event
|
||||
* subscription (up). In-process implements the vtable with direct callbacks
|
||||
* (fd = -1); out-of-process is a socket whose fd is registered with the core like
|
||||
* any source. The core treats both the same. Orchestration is NOT wired in here —
|
||||
* only the seam. */
|
||||
|
||||
/* Subscription filter: which UP events the control WANTS. This is only a
|
||||
* NARROWING; the real ceiling is set by the grant (effective = sub ∩ grant). */
|
||||
typedef struct {
|
||||
uint32_t source_mask; /* bit (1u<<vmsig_source) for the seam of interest */
|
||||
vmsig_prio prio_min; /* drop UP below this priority */
|
||||
uint64_t endpoint_mask; /* 0 = all VMs; otherwise bit (1ull<<endpoint) */
|
||||
} vmsig_sub;
|
||||
|
||||
/* ===== Security layer: a control's grant (capability set) =====
|
||||
* Neutral ceiling of a poller's rights: which VMs, which UP sources, which classes
|
||||
* of DOWN commands. The enforcement mechanism is in the core (admission/pump_up/
|
||||
* emit_down); the policy (who gets what) is set by the embedding program/
|
||||
* orchestrator. Default DENY: an empty grant => not a valid poller (receives and
|
||||
* sends nothing). */
|
||||
#define VMSIG_CAP_OBSERVE 0x1u /* UP of SEAM/generic coherent state (observation) */
|
||||
#define VMSIG_CAP_INPUT 0x2u /* CMD_INPUT */
|
||||
/* (0x4 is the freed bit of the removed CAP_STREAM; the future vgpu-control down-path
|
||||
* returns via write-signaled/MEMWRITE. Do NOT reuse.) */
|
||||
#define VMSIG_CAP_LIFECYCLE 0x8u /* CMD_LIFECYCLE safe ones (pause/resume/wakeup) */
|
||||
/* (0x10 is the freed bit of the removed CAP_MEMREAD; do NOT reuse: a stale grant
|
||||
* with this bit must not silently alias to the privileged memory cap.) */
|
||||
#define VMSIG_CAP_POWER 0x20u /* destructive lifecycle/VM (powerdown/reset/quit) */
|
||||
#define VMSIG_CAP_VM 0x40u /* CMD_VM safe ones (query/cont/stop), VMHOST seam */
|
||||
#define VMSIG_CAP_MEMCTX 0x80u /* SUBSCRIPTION to a coherent AS context (UP MEMCTX*, re-share RO-fd).
|
||||
* NOT an access broker (that is OS-DAC on the fd) — gates RECEIVING the datum. */
|
||||
#define VMSIG_CAP_MEMWRITE 0x100u /* CMD_MEMWRITE: atomic write-signaled mutation of shared guest memory
|
||||
* (separate from the freed CAP_MEMREAD bit — read != write; fresh bit
|
||||
* avoids stale-grant aliasing to this privileged cap). */
|
||||
|
||||
typedef struct {
|
||||
uint32_t principal; /* id for auditing (uid/token) */
|
||||
uint64_t endpoint_mask; /* which VMs (bit 1ull<<endpoint, endpoint<64); 0=none */
|
||||
uint32_t source_mask; /* which UP sources (bit 1u<<vmsig_source) */
|
||||
uint32_t cap_mask; /* VMSIG_CAP_* */
|
||||
uint32_t arb_prio; /* lease arbitration priority: higher=stronger; supervisor=max. */
|
||||
/* Separate from vmsig_prio (on-wire ordering). The default */
|
||||
/* arbitration policy compares it (STRICTLY higher preempts, ties=owner). */
|
||||
} vmsig_grant;
|
||||
|
||||
/* ===== Lease arbitration policy (orchestrator; §5) =====
|
||||
* Signaling owns the MECHANISM (exclusivity, preemption, fencing, finalization);
|
||||
* the POLICY (preempt or deny on conflict) is set by the orchestrator via a
|
||||
* pluggable callback. Default (cb==NULL) = arb_prio comparison: STRICTLY higher ->
|
||||
* PREEMPT, otherwise DENY. */
|
||||
typedef enum {
|
||||
VMSIG_ARB_DENY = 0, /* deny the contender, the owner keeps it */
|
||||
VMSIG_ARB_PREEMPT = 1 /* take it from the owner, give it to the contender (QUEUE — reserved) */
|
||||
} vmsig_arb_decision;
|
||||
|
||||
/* Called ONLY when (endpoint,class) is held by a LIVE owner (incumbent) and an
|
||||
* ACQUIRE arrives from another contender. incumbent/contender are the parties'
|
||||
* grants (live, not copies); incumbent is NEVER NULL (a dead owner is treated as a
|
||||
* free slot and policy is not called). Called on the loop thread. */
|
||||
typedef vmsig_arb_decision (*vmsig_arb_policy)(void* ud, uint32_t endpoint, uint32_t cls,
|
||||
const vmsig_grant* incumbent,
|
||||
const vmsig_grant* contender);
|
||||
|
||||
/* Control endpoint vtable. The core calls deliver() for UP; control sends DOWN via
|
||||
* the emit hook that the core installs in set_emit_down(). */
|
||||
typedef struct vmsig_control_ops {
|
||||
const char* name;
|
||||
|
||||
/* fd for an out-of-process control (socket). -1 => in-process, callbacks only
|
||||
* (no registration in epoll). */
|
||||
int (*fd)(void* ctl);
|
||||
|
||||
/* Declare interest (called once at attach). */
|
||||
int (*subscribe)(void* ctl, vmsig_sub* out);
|
||||
|
||||
/* Core -> control: an UP event for the subscriber. For in-process, a direct
|
||||
* call; for socket-control, serialization onto the wire. Borrowed: whatever
|
||||
* must outlive the call must be copied. */
|
||||
int (*deliver)(void* ctl, const vmsig_event* ev);
|
||||
|
||||
/* Core -> control (socket only): the control-fd is readable; the implementation
|
||||
* parses the wire into DOWN events and calls the installed down-emit. */
|
||||
int (*on_readable)(void* ctl);
|
||||
|
||||
/* The core installs the hook by which control sends DOWN commands; the core
|
||||
* routes them into vmsig_ctx_submit(ctx, VMSIG_DIR_DOWN, ev). */
|
||||
void (*set_emit_down)(void* ctl, int (*emit)(void* token, vmsig_event*),
|
||||
void* token);
|
||||
|
||||
void (*close)(void* ctl);
|
||||
|
||||
/* Core -> control: deliver a coherent address-space context (UP MEMCTX) + RO-fd
|
||||
* of the RAM region. Socket: a vmsig_wire frame (kind=MEMCTX, inln=vmsig_memctx) + fd in cmsg
|
||||
* (SCM_RIGHTS); the segs payload does NOT go on the wire (the holder opens
|
||||
* via `low`). In-proc: direct fd + event (segs in payload, decode with vmsig_memctx_segs).
|
||||
* The fd is BORROWED for the duration of the call (the core closes it afterwards) — the holder
|
||||
* dup's/mmap's it to keep it. Optional: NULL => control does not accept MEMCTX. 0/-1. */
|
||||
int (*attach_memctx)(void* ctl, const vmsig_event* ev, int fd);
|
||||
} vmsig_control_ops;
|
||||
|
||||
/* Reference in-process control: a thin shim turning a C callback into a vtable, for
|
||||
* embedding an algorithm directly. */
|
||||
typedef struct {
|
||||
int (*on_event)(void* user, const vmsig_event* up); /* core -> algorithm */
|
||||
void* user;
|
||||
vmsig_sub sub; /* subscription filter */
|
||||
/* Core -> algorithm: a coherent AS context (UP MEMCTX) + RO-fd as a direct int. The fd
|
||||
* is borrowed (dup/mmap to keep it). NULL => does not accept. 0/-1. */
|
||||
int (*on_memctx)(void* user, const vmsig_event* ev, int fd);
|
||||
} vmsig_inproc_cfg;
|
||||
|
||||
/* Create a reference in-proc control over cfg (which is copied). Returns an opaque
|
||||
* ctl for vmsig_core_add_control(core, vmsig_inproc_control_ops(), ctl). Freed via
|
||||
* ops->close(ctl). NULL on OOM. */
|
||||
const vmsig_control_ops* vmsig_inproc_control_ops(void);
|
||||
void* vmsig_inproc_control_new(const vmsig_inproc_cfg* cfg);
|
||||
|
||||
/* Send a DOWN command from an in-proc control (after attach). 0 — ok, -1 — error. */
|
||||
int vmsig_inproc_send(void* ctl, vmsig_event* down);
|
||||
|
||||
#endif /* VMSIG_CONTROL_H */
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef VMSIG_CORE_H
|
||||
#define VMSIG_CORE_H
|
||||
#include "vmsig_event.h"
|
||||
#include "vmsig_ctx.h"
|
||||
#include "vmsig_adapter.h"
|
||||
#include "vmsig_control.h"
|
||||
|
||||
/* vmsig_core.h — non-blocking epoll core. It knows a single vocabulary: "here is
|
||||
* an fd — call the neutral handler on readiness; the handler produces/consumes
|
||||
* neutral events". All neighbor mechanisms are just different ways to spawn an
|
||||
* fd. The core structurally cannot name a neighbor's type: neighbor headers are
|
||||
* visible only from the adapter TUs. */
|
||||
|
||||
typedef struct vmsig_core vmsig_core;
|
||||
|
||||
/* Create the core over a transfer context (the core does NOT own ctx; ctx's
|
||||
* lifetime must cover the core). NULL on error. */
|
||||
vmsig_core* vmsig_core_new(vmsig_ctx* ctx);
|
||||
|
||||
/* Stop, detach all adapters/control, free. Safe on NULL. */
|
||||
void vmsig_core_free(vmsig_core* c);
|
||||
|
||||
/* ===== Audit (observability of admissions/denials) ===== */
|
||||
typedef enum {
|
||||
VMSIG_AUDIT_ADMIT = 0, /* poller admitted (socket accept) */
|
||||
VMSIG_AUDIT_REJECT = 1, /* poller rejected at accept (empty grant) */
|
||||
VMSIG_AUDIT_DOWN_DENIED = 2, /* DOWN command denied by grant/cap */
|
||||
/* --- lease arbitration --- */
|
||||
VMSIG_AUDIT_LEASE_GRANTED = 3, /* lease granted/preempted */
|
||||
VMSIG_AUDIT_LEASE_DENIED = 4, /* ACQUIRE denied OR destructive dropped by lease gate */
|
||||
VMSIG_AUDIT_LEASE_REVOKED = 5, /* lease revoked by preemption */
|
||||
VMSIG_AUDIT_LEASE_RECLAIMED = 6, /* lease reclaimed on owner death (reclaim) */
|
||||
VMSIG_AUDIT_MEMCTX_GRANTED = 7 /* address-space context granted/replayed to holder */
|
||||
} vmsig_audit_kind;
|
||||
|
||||
typedef struct {
|
||||
vmsig_audit_kind kind;
|
||||
uint32_t principal; /* uid/token (grant.principal or peer uid) */
|
||||
uint32_t endpoint;
|
||||
uint32_t cmd; /* vmsig_kind for DOWN_DENIED */
|
||||
uint32_t detail; /* extra (e.g. peer pid) */
|
||||
} vmsig_audit;
|
||||
|
||||
/* Set the audit callback (NULL = off). Called on the loop thread. */
|
||||
void vmsig_core_set_audit(vmsig_core* c,
|
||||
void (*cb)(void* ud, const vmsig_audit* a), void* ud);
|
||||
|
||||
/* Set the lease arbitration policy (NULL => default: contender.arb_prio >
|
||||
* incumbent.arb_prio ? PREEMPT : DENY). Called on the loop thread. */
|
||||
void vmsig_core_set_arb_policy(vmsig_core* c, vmsig_arb_policy cb, void* ud);
|
||||
|
||||
/* Register an adapter for VM `endpoint`: open(cfg,endpoint) -> attach(...),
|
||||
* enroll each yielded fd into epoll and into the dispatch table fd->(adapter,cookie).
|
||||
* Returns the adapter id (>=0) or -1. */
|
||||
int vmsig_core_add_adapter(vmsig_core* c, const vmsig_adapter_ops* ops,
|
||||
const void* cfg, uint32_t endpoint);
|
||||
|
||||
/* Attach a control endpoint (in-process or socket) with a GRANT (capability set).
|
||||
* grant == NULL => default-deny (poller inert). The core sees only the neutral
|
||||
* vtable + grant + (opt.) fd. Returns the control id (>=0) or -1. */
|
||||
int vmsig_core_add_control(vmsig_core* c, const vmsig_control_ops* ops, void* ctl,
|
||||
const vmsig_grant* grant);
|
||||
|
||||
/* Spin the loop until a stop is requested. 0 — clean, -1 — fatal. */
|
||||
int vmsig_core_run(vmsig_core* c);
|
||||
|
||||
/* Asynchronous, signal-safe stop request: writes the wakeup eventfd. */
|
||||
void vmsig_core_stop(vmsig_core* c);
|
||||
|
||||
#endif /* VMSIG_CORE_H */
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef VMSIG_CTX_H
|
||||
#define VMSIG_CTX_H
|
||||
#include "vmsig_event.h"
|
||||
|
||||
/* vmsig_ctx.h — the "transfer context": the SISC-critical seam owning PRIORITY,
|
||||
* SEQUENCING and PROTOCOL timing of delivery. Behavioral timing does NOT belong
|
||||
* here — commands arrive already decided from control; the context merely
|
||||
* orders and paces them on the "wire". */
|
||||
|
||||
typedef struct vmsig_ctx vmsig_ctx; /* opaque: queues, seq, timing */
|
||||
|
||||
/* Protocol (RS232-like) transmission timings — transport ONLY, not behavior.
|
||||
* All zeros = pass-through (no pacing). */
|
||||
typedef struct {
|
||||
uint32_t min_gap_ns; /* min. gap between channel events (rate-cap) */
|
||||
uint32_t coalesce_ns; /* collapse bursts of one kind within a window */
|
||||
uint32_t max_inflight; /* backpressure depth on a channel before drop */
|
||||
uint8_t drop_policy; /* VMSIG_DROP_* */
|
||||
} vmsig_timing;
|
||||
|
||||
#define VMSIG_DROP_OLDEST 0
|
||||
#define VMSIG_DROP_NEWEST 1
|
||||
#define VMSIG_DROP_BLOCK 2
|
||||
|
||||
vmsig_ctx* vmsig_ctx_new(void);
|
||||
void vmsig_ctx_free(vmsig_ctx* c);
|
||||
|
||||
/* Policy per (source,dir): default priority + protocol timing. They live
|
||||
* here, NOT in adapters and NOT in control. */
|
||||
int vmsig_ctx_set_policy(vmsig_ctx* c, vmsig_source src, vmsig_dir dir,
|
||||
vmsig_prio default_prio, const vmsig_timing* t);
|
||||
|
||||
/* Enqueue an event into the `dir`-direction context (assigns seq, applies
|
||||
* priority/timing/coalescing/backpressure). 0 — enqueued, 1 —
|
||||
* coalesced/dropped by policy, -1 — error. On success takes ownership of
|
||||
* ev->payload. Thread-safe (the UP side is called from worker threads). */
|
||||
int vmsig_ctx_submit(vmsig_ctx* c, vmsig_dir dir, vmsig_event* ev);
|
||||
|
||||
/* Fetch the next event of direction `dir` ready for delivery, honoring
|
||||
* priority + protocol timing. 1 — event written to out, 0 — nothing yet
|
||||
* (caller arms timing_fd), -1 — error. */
|
||||
int vmsig_ctx_next(vmsig_ctx* c, vmsig_dir dir, vmsig_event* out);
|
||||
|
||||
/* timerfd by which the context wakes the loop when a paced/coalesced event
|
||||
* has matured. Registered in the core like any source. -1 if not needed. */
|
||||
int vmsig_ctx_timing_fd(vmsig_ctx* c, vmsig_dir dir);
|
||||
|
||||
#endif /* VMSIG_CTX_H */
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,101 @@
|
||||
#ifndef VMSIG_MEMCTX_H
|
||||
#define VMSIG_MEMCTX_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "vmsig_event.h"
|
||||
|
||||
/* vmsig_memctx.h — NEUTRAL handoff contract for the guest address-space context.
|
||||
*
|
||||
* signaling is a COHERENCE layer for shared state, not perception. Over memory it
|
||||
* vends ONE coherent datum: the root of the guest address space (the permanent System
|
||||
* DirectoryTableBase, `kcr3`) PAIRED with a RAM-region locator — a pre-opened `O_RDONLY`
|
||||
* fd. The holder (an S library / any control, including a human operator via their shim)
|
||||
* subscribes to this datum, opens ITS OWN read-only context FROM the received fd (keyed on
|
||||
* `kcr3`), and does proc_list/gva_read/scan/pmap itself. Perception and semantics are NOT here.
|
||||
*
|
||||
* Holder invariants:
|
||||
* - The locator is valid ONLY against the received `O_RDONLY` fd. From it the holder opens
|
||||
* its own read-only context, keyed on the vended `kcr3`:
|
||||
* * raw reads under a cr3 it already holds — vmie_mem_from_ro_fd(fd, low) (nseg==0) or
|
||||
* vmie_mem_from_ro_fd_segs(fd, segs, nseg) (nseg>0); gva_read keyed on (mem, kcr3);
|
||||
* * FULL read context WITH process/module discovery — vmie_win32_open_ro_fd(fd, low,
|
||||
* kcr3): builds the offset profile read-only from the image (no beacon/ACK) and
|
||||
* enables proc_list/proc_modules plus the section/import/export/scan surfaces. A
|
||||
* sensor that must FIND a process (then read its private AS) needs this one — kcr3
|
||||
* alone gives reads-under-a-known-cr3, not discovery.
|
||||
* Both map PROT_READ (gva_write -> -1). `kcr3` is valid ONLY within its `epoch`.
|
||||
* - On UP MEMCTX_INVALIDATED{endpoint,epoch} the holder closes its context/fd-mmap
|
||||
* and waits for the next MEMCTX{epoch+1} (re-multicast with a new kcr3 and a fresh fd).
|
||||
* - The fd is always `O_RDONLY` (VMSIG_MEMCTX_RDONLY set by this layer): mmap(PROT_WRITE)
|
||||
* through it -> EACCES. Writing into the guest is structurally impossible on the holder
|
||||
* side — it goes through the write-signaled MEMWRITE command (CMD_MEMWRITE under the
|
||||
* MEMWRITE lease), never this RO mapping. */
|
||||
|
||||
/* Locator-POD flag: the region is vended read-only (always set by signaling). */
|
||||
#define VMSIG_MEMCTX_RDONLY 0x1u
|
||||
|
||||
/* Address-space context locator-POD (rides in vmsig_event.inln; <=48 bytes).
|
||||
* Flat self-describing encoding: nseg explicit, no offset magic. */
|
||||
typedef struct {
|
||||
uint64_t kcr3; /* permanent System DirectoryTableBase (guest AS root) */
|
||||
uint64_t low; /* below-4G RAM size (PCI-hole split point; single-low open) */
|
||||
uint32_t epoch; /* VM-session epoch; kcr3 valid ONLY within it */
|
||||
uint32_t nseg; /* number of segments in the owned-payload (0 => single-low by `low`) */
|
||||
uint32_t flags; /* VMSIG_MEMCTX_RDONLY */
|
||||
uint32_t _pad;
|
||||
} vmsig_memctx; /* 8+8+4+4+4+4 = 32 bytes */
|
||||
|
||||
/* One GPA->file segment (mirrors the neighbor's gpa_seg from memmodel.h, but self-contained:
|
||||
* this header does NOT pull in the neighbor's contract). Rides in the owned-payload of the
|
||||
* MEMCTX event when nseg>0. For a single-low image nseg==0 and the holder opens by `low`. */
|
||||
typedef struct {
|
||||
uint64_t gpa; /* GPA of the window */
|
||||
uint64_t len; /* window length in bytes */
|
||||
uint64_t file_off; /* offset into the RAM-backing file */
|
||||
} vmsig_memseg;
|
||||
|
||||
/* Epoch invalidation (UP VMSIG_EV_MEMCTX_INVALIDATED, in inln). */
|
||||
typedef struct {
|
||||
uint32_t endpoint;
|
||||
uint32_t epoch; /* new epoch; the previous one's context is invalid */
|
||||
} vmsig_memctx_inv;
|
||||
|
||||
/* Decode the MEMCTX event's owned-payload into segs[] (pointer + nseg). A pure function over
|
||||
* the event: no ownership, no allocations. Returns a pointer to the segments (or NULL, setting
|
||||
* *out_nseg=0, if there are none — e.g. a single-low image OR socket delivery, where the
|
||||
* payload does not cross the wire and the holder opens by `low`). */
|
||||
static inline const vmsig_memseg* vmsig_memctx_segs(const vmsig_event* ev,
|
||||
uint32_t* out_nseg) {
|
||||
const vmsig_memctx* m = (const vmsig_memctx*)ev->inln;
|
||||
uint32_t n = m->nseg;
|
||||
if (!n || !ev->payload.data ||
|
||||
ev->payload.len < (size_t)n * sizeof(vmsig_memseg)) {
|
||||
if (out_nseg) *out_nseg = 0;
|
||||
return NULL;
|
||||
}
|
||||
if (out_nseg) *out_nseg = n;
|
||||
return (const vmsig_memseg*)ev->payload.data;
|
||||
}
|
||||
|
||||
/* ===== Registration seam adapter -> core =====
|
||||
*
|
||||
* The memctx adapter registers THIS in the core via vmsig_emit.register_memctx. The core
|
||||
* keeps the registration per-endpoint (retained-context) and does NOT store a copy of the
|
||||
* locator: on delivery/replay it calls describe() (current locator snapshot) + share_fd()
|
||||
* (fresh O_RDONLY fd). The epoch is stamped by the CORE (single source of truth); describe
|
||||
* does NOT fill it. invalidate() — the core asks the adapter to re-bootstrap on an epoch
|
||||
* change (the adapter re-emits MEMCTX once ready). All callbacks are called on the loop
|
||||
* thread. ctx — the adapter's private context. */
|
||||
typedef struct vmsig_memctx_reg {
|
||||
uint32_t endpoint;
|
||||
uint32_t source; /* VMSIG_SRC_MEMCTX */
|
||||
void* ctx; /* adapter's private context */
|
||||
/* Current locator snapshot: kcr3/low/nseg/flags + segs (borrowed, owned by the
|
||||
* adapter; lives across epochs). The core overwrites epoch with its own value. */
|
||||
void (*describe)(void* ctx, vmsig_memctx* out_pod,
|
||||
const vmsig_memseg** out_segs, uint32_t* out_nseg);
|
||||
int (*share_fd)(void* ctx); /* fresh O_RDONLY fd of the RAM region (caller closes) */
|
||||
void (*invalidate)(void* ctx, uint32_t epoch); /* re-bootstrap for the new epoch */
|
||||
} vmsig_memctx_reg;
|
||||
|
||||
#endif /* VMSIG_MEMCTX_H */
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef VMSIG_SOCKET_H
|
||||
#define VMSIG_SOCKET_H
|
||||
#include "vmsig_event.h"
|
||||
#include "vmsig_control.h" /* vmsig_grant */
|
||||
#include "vmsig_core.h" /* vmsig_core */
|
||||
|
||||
/* vmsig_socket.h — out-of-process control over a unix socket (human/service poller).
|
||||
* signaling LISTENS; each accepted connection is authenticated (SO_PEERCRED) and,
|
||||
* per policy, receives a grant -> becomes a distinct control behind the same seam. */
|
||||
|
||||
/* Wire format: fixed-size, pointer-free — the same contract on the external
|
||||
* poller. Single host (unix socket) => native byte order. Only the event's
|
||||
* inline part is serialized (payload pointers do not go on the wire). */
|
||||
#define VMSIG_WIRE_MAGIC 0x47495356u /* 'VSIG' */
|
||||
#define VMSIG_WIRE_VERSION 1u
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t kind; /* vmsig_kind */
|
||||
uint32_t source; /* vmsig_source */
|
||||
uint32_t dir; /* vmsig_dir */
|
||||
uint32_t prio; /* vmsig_prio */
|
||||
uint32_t endpoint;
|
||||
uint32_t corr;
|
||||
uint8_t inln[48]; /* inline event payload */
|
||||
} vmsig_wire;
|
||||
|
||||
/* Frame <-> event codec (for external clients too). */
|
||||
void vmsig_wire_encode(vmsig_wire* w, const vmsig_event* ev);
|
||||
int vmsig_wire_decode(const vmsig_wire* w, vmsig_event* ev); /* 0 ok, -1 bad magic/ver */
|
||||
|
||||
/* Admission policy: given the authenticated peer (SO_PEERCRED), return a grant.
|
||||
* An empty grant (cap_mask==0 || endpoint_mask==0) => connection is rejected. */
|
||||
typedef vmsig_grant (*vmsig_socket_policy)(uint32_t uid, uint32_t pid, void* ud);
|
||||
|
||||
/* Bring up a unix-socket control listener on `path` (prefix '@' => abstract socket).
|
||||
* Driven by the epoll core: accept -> SO_PEERCRED -> policy -> grant -> per-conn
|
||||
* control. Returns 0/-1. */
|
||||
int vmsig_socket_attach(vmsig_core* core, const char* path,
|
||||
vmsig_socket_policy policy, void* ud);
|
||||
|
||||
#endif /* VMSIG_SOCKET_H */
|
||||
Reference in New Issue
Block a user