mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-06-25 20:36:36 +03:00
9bde398b6c
- 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
229 lines
11 KiB
C
229 lines
11 KiB
C
/* sample.c — consumer seqlock reads: frame sampling, cursor, geometry, status.
|
|
*
|
|
* Every guest read goes through gva_read into a local copy; we never hold a
|
|
* gva_ptr across a seqlock window (it is borrowed and not atomic for re-check).
|
|
* The discipline is the mirror of the producer's publish order in atomic-shim.h,
|
|
* but an independent body — this is consumer code, not shared producer code.
|
|
*
|
|
* Lossy by contract: when a writer keeps a window busy past VGPUP_SEQLOCK_RETRIES
|
|
* we return 0 (skip), never block. Blocking longer would be behavioural timing
|
|
* (control's concern), which has no place in the sensor.
|
|
*
|
|
* All reads go under r->proc_cr3 (the producer's user-AS cr3, cached in the
|
|
* handle at discovery), NOT the System kcr3. A <0 from any gva_read means a page
|
|
* is gone — the producer process may have restarted; we propagate <0 and the
|
|
* caller re-discovers (see vgpu_perception.h "Two epochs + producer restart").
|
|
*/
|
|
|
|
#include "perception-internal.h"
|
|
#include <stdio.h> /* TEMP debug (revert): stderr skip-reason trace */
|
|
|
|
/* Read one cold-line / packed field at producer offset `off` into dst under the
|
|
* producer's user-AS cr3. */
|
|
static int read_field(vmie_mem* m, uintptr_t cr3, uint64_t region_gva,
|
|
size_t off, void* dst, size_t n)
|
|
{
|
|
return gva_read(m, cr3, (uintptr_t)region_gva + off, dst, n) < 0 ? -1 : 0;
|
|
}
|
|
|
|
int vgpup_sample_frame(vgpup_region* r, vmie_mem* m,
|
|
uint8_t* dst, size_t cap, vgpup_frame_info* info)
|
|
{
|
|
unsigned attempt;
|
|
static unsigned long _dc = 0; /* TEMP debug: 1/240 call gate */
|
|
int _dbg = ((_dc++ % 240u) == 0u);
|
|
|
|
if (!r || !m || !dst || !info) { return -1; }
|
|
|
|
for (attempt = 0; attempt < VGPUP_SEQLOCK_RETRIES; ++attempt) {
|
|
uint32_t latest = 0, seq_before = 0, seq_after = 0;
|
|
vgpu_desc_t d;
|
|
uint64_t slot_gva, seq_gva, desc_gva;
|
|
size_t frame_bytes;
|
|
|
|
/* latest (acquire-equivalent: its own read) */
|
|
if (read_field(m, r->proc_cr3, r->region_gva,
|
|
offsetof(vgpu_producer_t, latest), &latest, sizeof latest) < 0) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG ret=-1 latest-read-fail\n");
|
|
return -1;
|
|
}
|
|
if (latest == VGPU_LATEST_NONE || latest >= VGPU_SLOT_COUNT) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG ret=0 A latest=%u\n", latest);
|
|
return 0;
|
|
}
|
|
|
|
seq_gva = r->region_gva + offsetof(vgpu_producer_t, seq) + (uint64_t)latest * sizeof(uint32_t);
|
|
desc_gva = r->region_gva + offsetof(vgpu_producer_t, desc) + (uint64_t)latest * sizeof(vgpu_desc_t);
|
|
|
|
if (vgpup_read_seq(m, r->proc_cr3, seq_gva, &seq_before) < 0) { return -1; }
|
|
if (vgpup_seq_is_writing(seq_before)) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG cont B att=%u latest=%u seqB=%u (writing)\n", attempt, latest, seq_before);
|
|
continue; /* writer in slot */
|
|
}
|
|
|
|
if (gva_read(m, (uintptr_t)r->proc_cr3, (uintptr_t)desc_gva, &d, sizeof d) < 0) { return -1; }
|
|
|
|
/* dedup by frame_id: nothing newer than what we already sampled */
|
|
if (d.frame_id <= r->last_frame_id) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG ret=0 C dedup dfid=%llu last=%llu\n",
|
|
(unsigned long long)d.frame_id, (unsigned long long)r->last_frame_id);
|
|
return 0;
|
|
}
|
|
|
|
/* descriptor sanity within the read window (tight BGRA, bounded dims) */
|
|
if (d.format != VGPU_FMT_BGRA8888 || d.stride != d.width * 4u ||
|
|
d.width == 0u || d.width > VGPU_MAX_WIDTH ||
|
|
d.height == 0u || d.height > VGPU_MAX_HEIGHT) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG cont D torn att=%u w=%u h=%u s=%u f=%u\n",
|
|
attempt, d.width, d.height, d.stride, d.format);
|
|
continue; /* likely a torn read; retry */
|
|
}
|
|
|
|
frame_bytes = (size_t)d.height * d.stride;
|
|
if (frame_bytes > VGPU_SLOT_STRIDE) { return 0; } /* impossible-large → skip */
|
|
if (frame_bytes > cap) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG ret=0 F fbytes=%zu cap=%zu\n", frame_bytes, cap);
|
|
return 0; /* would not fit → lossy drop */
|
|
}
|
|
|
|
slot_gva = r->ring_gva + (uint64_t)latest * VGPU_SLOT_STRIDE;
|
|
if (gva_read(m, (uintptr_t)r->proc_cr3, (uintptr_t)slot_gva, dst, frame_bytes) < 0) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG ret=-1 G slot-read-fail latest=%u fbytes=%zu\n", latest, frame_bytes);
|
|
return -1;
|
|
}
|
|
|
|
/* re-check the slot seq: unchanged and still even → snapshot consistent */
|
|
if (vgpup_read_seq(m, r->proc_cr3, seq_gva, &seq_after) < 0) { return -1; }
|
|
if (seq_after != seq_before || vgpup_seq_is_writing(seq_after)) {
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG cont H att=%u latest=%u seqB=%u seqA=%u\n",
|
|
attempt, latest, seq_before, seq_after);
|
|
continue; /* the slot was rewritten under us — retry */
|
|
}
|
|
|
|
info->desc.width = d.width;
|
|
info->desc.height = d.height;
|
|
info->desc.stride = d.stride;
|
|
info->desc.format = d.format;
|
|
info->desc.frame_id = d.frame_id;
|
|
info->desc.timestamp_ns = d.timestamp_ns;
|
|
info->bytes = frame_bytes;
|
|
|
|
r->last_frame_id = d.frame_id;
|
|
return 1;
|
|
}
|
|
if (_dbg) fprintf(stderr, "VGPUP_DBG ret=0 I retry-exhaust (%u attempts all busy)\n", VGPUP_SEQLOCK_RETRIES);
|
|
return 0; /* writer kept the slot busy past the retry limit — skip */
|
|
}
|
|
|
|
int vgpup_read_cursor(vgpup_region* r, vmie_mem* m, vgpup_cursor* out)
|
|
{
|
|
unsigned attempt;
|
|
|
|
if (!r || !m || !out) { return -1; }
|
|
|
|
/* The producer bumps cursor_seq LAST (acquire), so we read the cursor line
|
|
* first and gate on cursor_seq being even and unchanged across the window. */
|
|
for (attempt = 0; attempt < VGPUP_SEQLOCK_RETRIES; ++attempt) {
|
|
uint32_t seq_before = 0, seq_after = 0;
|
|
uint32_t visible = 0, hotspot = 0, glyph = 0, id = 0;
|
|
uint64_t pos = 0;
|
|
|
|
if (vgpup_read_seq(m, r->proc_cr3, r->region_gva + offsetof(vgpu_producer_t, cursor_seq),
|
|
&seq_before) < 0) { return -1; }
|
|
if (vgpup_seq_is_writing(seq_before)) { continue; }
|
|
|
|
if (read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_visible), &visible, sizeof visible) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_pos), &pos, sizeof pos) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_hotspot), &hotspot, sizeof hotspot) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_glyph), &glyph, sizeof glyph) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cursor_id), &id, sizeof id) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (vgpup_read_seq(m, r->proc_cr3, r->region_gva + offsetof(vgpu_producer_t, cursor_seq),
|
|
&seq_after) < 0) { return -1; }
|
|
if (seq_after != seq_before || vgpup_seq_is_writing(seq_after)) { continue; }
|
|
|
|
out->seq = seq_after;
|
|
out->visible = visible;
|
|
out->x = vgpup_cursor_x(pos);
|
|
out->y = vgpup_cursor_y(pos);
|
|
out->hot_x = vgpup_lo16(hotspot);
|
|
out->hot_y = vgpup_hi16(hotspot);
|
|
out->glyph_w = vgpup_lo16(glyph);
|
|
out->glyph_h = vgpup_hi16(glyph);
|
|
out->id = id;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int vgpup_read_geometry(vgpup_region* r, vmie_mem* m, vgpup_geometry* out)
|
|
{
|
|
unsigned attempt;
|
|
|
|
if (!r || !m || !out) { return -1; }
|
|
|
|
for (attempt = 0; attempt < VGPUP_SEQLOCK_RETRIES; ++attempt) {
|
|
uint32_t seq_before = 0, seq_after = 0;
|
|
int32_t virt_x = 0, virt_y = 0, cap_x = 0, cap_y = 0;
|
|
uint32_t virt_w = 0, virt_h = 0, dpi = 0, refresh_mhz = 0;
|
|
|
|
if (vgpup_read_seq(m, r->proc_cr3, r->region_gva + offsetof(vgpu_producer_t, geom_seq),
|
|
&seq_before) < 0) { return -1; }
|
|
if (vgpup_seq_is_writing(seq_before)) { continue; }
|
|
|
|
if (read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_x), &virt_x, sizeof virt_x) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_y), &virt_y, sizeof virt_y) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_w), &virt_w, sizeof virt_w) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, virt_h), &virt_h, sizeof virt_h) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cap_x), &cap_x, sizeof cap_x) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, cap_y), &cap_y, sizeof cap_y) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, dpi), &dpi, sizeof dpi) < 0 ||
|
|
read_field(m, r->proc_cr3, r->region_gva, offsetof(vgpu_producer_t, refresh_mhz), &refresh_mhz, sizeof refresh_mhz) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (vgpup_read_seq(m, r->proc_cr3, r->region_gva + offsetof(vgpu_producer_t, geom_seq),
|
|
&seq_after) < 0) { return -1; }
|
|
if (seq_after != seq_before || vgpup_seq_is_writing(seq_after)) { continue; }
|
|
|
|
out->virt_x = virt_x;
|
|
out->virt_y = virt_y;
|
|
out->virt_w = virt_w;
|
|
out->virt_h = virt_h;
|
|
out->cap_x = cap_x;
|
|
out->cap_y = cap_y;
|
|
out->dpi = dpi;
|
|
out->refresh_mhz = refresh_mhz;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int vgpup_read_status(vgpup_region* r, vmie_mem* m, vgpup_status* out)
|
|
{
|
|
vgpu_producer_t p;
|
|
|
|
if (!r || !m || !out) { return -1; }
|
|
|
|
/* Cold line: single naturally-aligned atomic fields with no seqlock. Read
|
|
* the whole producer block once and pick the cold fields — "fresh enough"
|
|
* by the lossy contract. */
|
|
if (gva_read(m, (uintptr_t)r->proc_cr3, (uintptr_t)r->region_gva, &p, sizeof p) < 0) { return -1; }
|
|
|
|
out->heartbeat = p.heartbeat;
|
|
out->run_epoch = p.run_epoch;
|
|
out->status = p.status;
|
|
out->backend = p.backend;
|
|
out->error_code = p.error_code;
|
|
out->applied_fps = p.applied_fps;
|
|
out->supported_formats = p.supported_formats;
|
|
out->ctrl_ack = p.ctrl_ack;
|
|
out->full_frame_ack = p.full_frame_ack;
|
|
out->content_change_ns = p.content_change_ns;
|
|
|
|
r->run_epoch = p.run_epoch; /* feed the session-break detector */
|
|
return 0;
|
|
}
|