mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-06-26 04:36:37 +03:00
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:
@@ -0,0 +1,169 @@
|
||||
#ifndef VGPU_STREAM_H
|
||||
#define VGPU_STREAM_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h> /* offsetof */
|
||||
#include <stdalign.h> /* alignas */
|
||||
#include <assert.h> /* static_assert */
|
||||
|
||||
/* ===== Geometry — single source of truth (bare ABI, both ends agree) ===== */
|
||||
#define VGPU_PAGE 4096u
|
||||
#define VGPU_SLOT_COUNT 3u
|
||||
#define VGPU_SLOT_STRIDE (32u * 1024u * 1024u)
|
||||
#define VGPU_RING_OFFSET (2u * 1024u * 1024u)
|
||||
#define VGPU_PRODUCER_OFFSET 0u
|
||||
#define VGPU_CONTROL_OFFSET VGPU_PAGE
|
||||
#define VGPU_REGION_BYTES (VGPU_RING_OFFSET + (uint64_t)VGPU_SLOT_COUNT * VGPU_SLOT_STRIDE)
|
||||
#define VGPU_MAX_WIDTH 3840u
|
||||
#define VGPU_MAX_HEIGHT 2160u
|
||||
#define VGPU_HEARTBEAT_PERIOD_MS 250u /* producer ticks heartbeat >= 4 Hz always */
|
||||
#define VGPU_LATEST_NONE 0xFFFFFFFFu
|
||||
|
||||
static_assert((uint64_t)VGPU_MAX_WIDTH * VGPU_MAX_HEIGHT * 4u <= VGPU_SLOT_STRIDE,
|
||||
"max-mode tight BGRA must fit one slot");
|
||||
|
||||
/* enum values travel as uint32 wire-values (not as enum fields → no width instability) */
|
||||
enum { VGPU_FMT_BGRA8888 = 0 };
|
||||
enum { VGPU_ST_INIT=0, VGPU_ST_CAPTURING=1, VGPU_ST_PAUSED=2, VGPU_ST_STOPPED=3, VGPU_ST_ERROR=4 };
|
||||
enum { VGPU_BK_NONE=0, VGPU_BK_NVFBC=1, VGPU_BK_DDA=2, VGPU_BK_GDI=3 };
|
||||
enum { VGPU_CMD_STOP=0, VGPU_CMD_RUN=1, VGPU_CMD_PAUSE=2 };
|
||||
/* cursor shape identity (wire-uint32); UNKNOWN=0 → custom/unrecognized glyph */
|
||||
enum { VGPU_CURSOR_ID_UNKNOWN=0, VGPU_CURSOR_ID_ARROW=1, VGPU_CURSOR_ID_IBEAM=2,
|
||||
VGPU_CURSOR_ID_WAIT=3, VGPU_CURSOR_ID_CROSS=4, VGPU_CURSOR_ID_HAND=5,
|
||||
VGPU_CURSOR_ID_SIZENS=6, VGPU_CURSOR_ID_SIZEWE=7, VGPU_CURSOR_ID_SIZENWSE=8,
|
||||
VGPU_CURSOR_ID_SIZENESW=9, VGPU_CURSOR_ID_SIZEALL=10, VGPU_CURSOR_ID_NO=11,
|
||||
VGPU_CURSOR_ID_APPSTARTING=12 };
|
||||
|
||||
/* ===== Per-slot descriptor (under hot.seq[slot]) ===== */
|
||||
typedef struct {
|
||||
uint32_t width; /* pixels */
|
||||
uint32_t height; /* pixels */
|
||||
uint32_t stride; /* bytes/row; INVARIANT: == width*4 (tight) */
|
||||
uint32_t format; /* VGPU_FMT_* */
|
||||
uint64_t frame_id; /* == producer.frame_id at publish time */
|
||||
uint64_t timestamp_ns; /* capture time, monotonic */
|
||||
} vgpu_desc_t;
|
||||
static_assert(sizeof(vgpu_desc_t) == 32, "desc layout");
|
||||
static_assert(offsetof(vgpu_desc_t, width) == 0, "desc.width");
|
||||
static_assert(offsetof(vgpu_desc_t, height) == 4, "desc.height");
|
||||
static_assert(offsetof(vgpu_desc_t, stride) == 8, "desc.stride");
|
||||
static_assert(offsetof(vgpu_desc_t, format) == 12, "desc.format");
|
||||
static_assert(offsetof(vgpu_desc_t, frame_id) == 16, "desc.frame_id");
|
||||
static_assert(offsetof(vgpu_desc_t, timestamp_ns) == 24, "desc.timestamp_ns");
|
||||
|
||||
/* ===== Producer block (host-RO): hot publish line + cold status line ===== */
|
||||
typedef struct {
|
||||
/* --- hot publish line --- */
|
||||
alignas(64)
|
||||
uint32_t latest; /* index of last; VGPU_LATEST_NONE until 1st frame */
|
||||
uint32_t _r0;
|
||||
uint64_t frame_id; /* monotonic frame counter (8-aligned) */
|
||||
uint32_t seq[VGPU_SLOT_COUNT]; /* per-slot seqlock: even=stable, odd=writing */
|
||||
uint32_t _r1;
|
||||
vgpu_desc_t desc[VGPU_SLOT_COUNT]; /* self-describing slots */
|
||||
|
||||
/* --- cold status line --- */
|
||||
alignas(64)
|
||||
uint64_t heartbeat; /* monotonic; ticks always (even STOPPED/PAUSED) */
|
||||
uint32_t run_epoch; /* +1 per start (session break for host) */
|
||||
uint32_t status; /* VGPU_ST_* */
|
||||
uint32_t backend; /* VGPU_BK_* */
|
||||
uint32_t error_code; /* 0=none; else fatal detail */
|
||||
uint32_t applied_fps; /* publish-rate cap the producer actually applies;
|
||||
actual rate may be lower on static content or
|
||||
backend limits — host measures real fps from
|
||||
desc.timestamp_ns */
|
||||
uint32_t supported_formats; /* bitmask (1u<<VGPU_FMT_*) */
|
||||
uint32_t ctrl_ack; /* echo of control.ctrl_gen (even) applied */
|
||||
uint32_t full_frame_ack; /* echo of control.full_frame_req honored */
|
||||
/* --- cursor reporting (host-RO; position is sensor data, independent
|
||||
* of control.draw_cursor / cursor compositing) --- */
|
||||
uint32_t cursor_seq; /* @168: monotonic; bumps each cursor publish.
|
||||
Host reads it last (acquire) to gate a
|
||||
consistent {cursor_pos,cursor_visible}; lets the
|
||||
host tell "cursor idle" from "producer stopped
|
||||
reporting". */
|
||||
uint32_t cursor_visible; /* @172: 1=cursor shown (CURSOR_SHOWING), 0=hidden */
|
||||
uint64_t cursor_pos; /* @176: packed screen position, 8-aligned single
|
||||
atomic MOV. low 32 bits = x, high 32 = y, each a
|
||||
signed int32 (two's-complement; multi-monitor →
|
||||
negatives). Pair never tears (one 64-bit store). */
|
||||
/* --- cursor Tier-1 (host-RO; same cursor_seq gate as cursor_pos/visible) --- */
|
||||
uint32_t cursor_hotspot; /* @184: low16=hot_x, high16=hot_y (unsigned) */
|
||||
uint32_t cursor_glyph; /* @188: low16=glyph_w, high16=glyph_h (unsigned) */
|
||||
uint32_t cursor_id; /* @192: VGPU_CURSOR_ID_* shape identity */
|
||||
|
||||
/* --- graphics static-idle: monotonic stamp of last scene-content change --- */
|
||||
alignas(8) uint64_t content_change_ns; /* @200: host derives idle-ms vs its own clock */
|
||||
|
||||
/* --- display geometry (own cache line; geom_seq seqlock; sampled rarely) ---
|
||||
* captured-surface SIZE is NOT here: it is desc.width/height (authoritative, tight). */
|
||||
alignas(64)
|
||||
uint32_t geom_seq; /* @256: even=stable, odd=writing (frame-seqlock) */
|
||||
int32_t virt_x; /* @260: virtual-desktop origin (signed) */
|
||||
int32_t virt_y; /* @264 */
|
||||
uint32_t virt_w; /* @268: virtual-desktop bbox size (interprets neg pos) */
|
||||
uint32_t virt_h; /* @272 */
|
||||
int32_t cap_x; /* @276: captured-output origin in virtual-desktop coords */
|
||||
int32_t cap_y; /* @280: (captured size = desc.width/height, not here) */
|
||||
uint32_t dpi; /* @284: captured-output effective DPI; 96=100%; 0=unknown */
|
||||
uint32_t refresh_mhz; /* @288: captured-output refresh in milli-Hz; 0=unknown */
|
||||
} vgpu_producer_t;
|
||||
static_assert(alignof(vgpu_producer_t) == 64, "producer align");
|
||||
static_assert(sizeof(vgpu_producer_t) <= VGPU_PAGE, "producer fits page 0");
|
||||
/* host-read field layout frozen as ABI */
|
||||
static_assert(offsetof(vgpu_producer_t, latest) == 0, "producer.latest");
|
||||
static_assert(offsetof(vgpu_producer_t, frame_id) == 8, "producer.frame_id");
|
||||
static_assert(offsetof(vgpu_producer_t, seq) == 16, "producer.seq");
|
||||
static_assert(offsetof(vgpu_producer_t, desc) == 32, "producer.desc");
|
||||
static_assert(offsetof(vgpu_producer_t, heartbeat) == 128, "producer.heartbeat");
|
||||
static_assert(offsetof(vgpu_producer_t, run_epoch) == 136, "producer.run_epoch");
|
||||
static_assert(offsetof(vgpu_producer_t, status) == 140, "producer.status");
|
||||
static_assert(offsetof(vgpu_producer_t, backend) == 144, "producer.backend");
|
||||
static_assert(offsetof(vgpu_producer_t, error_code) == 148, "producer.error_code");
|
||||
static_assert(offsetof(vgpu_producer_t, applied_fps) == 152, "producer.applied_fps");
|
||||
static_assert(offsetof(vgpu_producer_t, supported_formats) == 156, "producer.supported_formats");
|
||||
static_assert(offsetof(vgpu_producer_t, ctrl_ack) == 160, "producer.ctrl_ack");
|
||||
static_assert(offsetof(vgpu_producer_t, full_frame_ack) == 164, "producer.full_frame_ack");
|
||||
static_assert(offsetof(vgpu_producer_t, cursor_seq) == 168, "producer.cursor_seq");
|
||||
static_assert(offsetof(vgpu_producer_t, cursor_visible) == 172, "producer.cursor_visible");
|
||||
static_assert(offsetof(vgpu_producer_t, cursor_pos) == 176, "producer.cursor_pos");
|
||||
/* cursor Tier-1 (cursor line, gated by cursor_seq) */
|
||||
static_assert(offsetof(vgpu_producer_t, cursor_hotspot) == 184, "producer.cursor_hotspot");
|
||||
static_assert(offsetof(vgpu_producer_t, cursor_glyph) == 188, "producer.cursor_glyph");
|
||||
static_assert(offsetof(vgpu_producer_t, cursor_id) == 192, "producer.cursor_id");
|
||||
/* graphics static-idle */
|
||||
static_assert(offsetof(vgpu_producer_t, content_change_ns) == 200, "producer.content_change_ns");
|
||||
/* display geometry (own cache line; captured SIZE is desc.width/height, not here) */
|
||||
static_assert(offsetof(vgpu_producer_t, geom_seq) == 256, "producer.geom_seq");
|
||||
static_assert(offsetof(vgpu_producer_t, virt_x) == 260, "producer.virt_x");
|
||||
static_assert(offsetof(vgpu_producer_t, virt_y) == 264, "producer.virt_y");
|
||||
static_assert(offsetof(vgpu_producer_t, virt_w) == 268, "producer.virt_w");
|
||||
static_assert(offsetof(vgpu_producer_t, virt_h) == 272, "producer.virt_h");
|
||||
static_assert(offsetof(vgpu_producer_t, cap_x) == 276, "producer.cap_x");
|
||||
static_assert(offsetof(vgpu_producer_t, cap_y) == 280, "producer.cap_y");
|
||||
static_assert(offsetof(vgpu_producer_t, dpi) == 284, "producer.dpi");
|
||||
static_assert(offsetof(vgpu_producer_t, refresh_mhz) == 288, "producer.refresh_mhz");
|
||||
|
||||
/* ===== Control block (host-RW), own page, generation-guarded ===== */
|
||||
typedef struct {
|
||||
alignas(64)
|
||||
uint32_t ctrl_gen; /* generation seqlock: even=stable, odd=writing (host writes) */
|
||||
uint32_t desired_state; /* VGPU_CMD_* (STOP/RUN/PAUSE) */
|
||||
uint32_t target_fps; /* desired fps; 0=producer default */
|
||||
uint32_t draw_cursor; /* 1=compose cursor */
|
||||
uint32_t full_frame_req; /* edge counter: bump → force fresh full frame */
|
||||
uint32_t consumer_tick; /* host heartbeat (producer watches with timeout) */
|
||||
uint32_t attached; /* 1=host attached (intent, not death-proof) */
|
||||
} vgpu_control_t;
|
||||
static_assert(alignof(vgpu_control_t) == 64, "control align");
|
||||
static_assert(sizeof(vgpu_control_t) <= VGPU_PAGE, "control fits page 1");
|
||||
/* host-write field layout frozen as ABI */
|
||||
static_assert(offsetof(vgpu_control_t, ctrl_gen) == 0, "control.ctrl_gen");
|
||||
static_assert(offsetof(vgpu_control_t, desired_state) == 4, "control.desired_state");
|
||||
static_assert(offsetof(vgpu_control_t, target_fps) == 8, "control.target_fps");
|
||||
static_assert(offsetof(vgpu_control_t, draw_cursor) == 12, "control.draw_cursor");
|
||||
static_assert(offsetof(vgpu_control_t, full_frame_req) == 16, "control.full_frame_req");
|
||||
static_assert(offsetof(vgpu_control_t, consumer_tick) == 20, "control.consumer_tick");
|
||||
static_assert(offsetof(vgpu_control_t, attached) == 24, "control.attached");
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user