/* 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 /* 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; }