/* virtio_gpu.c — UTSM virtio-gpu 2D backend (VSCode integration Phase 3). * * Implements a minimal virtio-gpu 2D device (device_id=16) so the Linux guest * gets /dev/dri/card0 + /dev/fb0 (via DRM fbdev emulation). The X server * (Xfbdev MVP / Xorg Phase 4) drives the scanout; the composited frame lands * in the shared graphics surface pool, which the Deshab desktop reads to blit * guest graphics onto the IDE host area. * * Device layout: slot 5 @ 0xF4005000 IRQ10, two queues (ctrlq + cursorq). * * Supported 2D command set (Linux uapi / Virtio 1.2 §5.7.6): * GET_DISPLAY_INFO, RESOURCE_CREATE_2D, RESOURCE_UNREF, SET_SCANOUT, * RESOURCE_FLUSH, TRANSFER_TO_HOST_2D, RESOURCE_ATTACH_BACKING, * RESOURCE_DETACH_BACKING. * * Cursor commands (UPDATE_CURSOR / MOVE_CURSOR) are acknowledged but not * rendered — the Deshab desktop draws its own PS/2 cursor over the blit. * * Data flow: * 1. Guest DRM driver creates a 2D resource, attaches guest RAM backing, * binds it to scanout 0 (SET_SCANOUT), and TRANSFER_TO_HOST_2D copies a * rect from guest backing into the surface pool (scanout shadow). * 2. RESOURCE_FLUSH marks the scanout dirty. * 3. The desktop polls virtio_gpu_get_scanout_info() and blits the dirty * scanout to the framebuffer IDE host area, clearing dirty. * * MVP simplifications: * - Single scanout (num_scanouts=1), fixed display 1024×768 XRGB8888. * - Resource backing assumed contiguous (first mem_entry's GPA + total len). * Guest framebuffer allocations satisfy this. * - No virgl 3D, no capsets, no cross-endian byte swap (raw 32bpp copy). */ #include #include #include #include /* ===== virtio-gpu protocol structs (must match ) ===== */ struct virtio_gpu_config { u32 events_read; u32 events_clear; u32 num_scanouts; u32 num_capsets; } __attribute__((packed)); struct virtio_gpu_ctrl_hdr { u32 type; u32 flags; u64 fence_id; u32 ctx_id; u32 padding; } __attribute__((packed)); struct virtio_gpu_rect { u32 x; u32 y; u32 width; u32 height; } __attribute__((packed)); struct virtio_gpu_display_one { struct virtio_gpu_rect r; u32 enabled; u32 flags; } __attribute__((packed)); struct virtio_gpu_resp_display_info { struct virtio_gpu_ctrl_hdr hdr; struct virtio_gpu_display_one pmodes[16]; } __attribute__((packed)); struct virtio_gpu_resp_num_scanouts { struct virtio_gpu_ctrl_hdr hdr; u32 num_scanouts; u32 padding; } __attribute__((packed)); struct virtio_gpu_resource_create_2d { struct virtio_gpu_ctrl_hdr hdr; u32 resource_id; u32 format; u32 width; u32 height; } __attribute__((packed)); struct virtio_gpu_mem_entry { u64 addr; u32 length; u32 padding; } __attribute__((packed)); struct virtio_gpu_resource_attach_backing { struct virtio_gpu_ctrl_hdr hdr; u32 resource_id; u32 nr_entries; } __attribute__((packed)); struct virtio_gpu_set_scanout { struct virtio_gpu_ctrl_hdr hdr; struct virtio_gpu_rect r; u32 scanout_id; u32 resource_id; } __attribute__((packed)); struct virtio_gpu_transfer_to_host_2d { struct virtio_gpu_ctrl_hdr hdr; struct virtio_gpu_rect r; u64 offset; u32 resource_id; u32 padding; } __attribute__((packed)); struct virtio_gpu_resource_flush { struct virtio_gpu_ctrl_hdr hdr; struct virtio_gpu_rect r; u32 resource_id; u32 padding; } __attribute__((packed)); /* Cursor VQ (Linux uapi virtio_gpu.h). pos is scanout_id/x/y, not a 2D rect. */ struct virtio_gpu_cursor_pos { u32 scanout_id; u32 x; u32 y; u32 padding; } __attribute__((packed)); struct virtio_gpu_update_cursor { struct virtio_gpu_ctrl_hdr hdr; struct virtio_gpu_cursor_pos pos; u32 resource_id; /* 0 = hide cursor */ u32 hot_x; u32 hot_y; u32 padding; } __attribute__((packed)); #define VGPU_CURSOR_MAX 64 /* max cursor image dimension (pixels) */ /* Command types MUST match Linux include/uapi/linux/virtio_gpu.h. */ #define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100 #define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101 #define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102 #define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103 #define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104 #define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105 #define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106 #define VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING 0x0107 #define VIRTIO_GPU_CMD_GET_CAPSET_INFO 0x0108 #define VIRTIO_GPU_CMD_GET_CAPSET 0x0109 #define VIRTIO_GPU_CMD_GET_EDID 0x010a #define VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID 0x010b #define VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB 0x010c #define VIRTIO_GPU_CMD_SET_SCANOUT_BLOB 0x010d #define VIRTIO_GPU_CMD_UPDATE_CURSOR 0x0300 #define VIRTIO_GPU_CMD_MOVE_CURSOR 0x0301 /* response types */ #define VIRTIO_GPU_RESP_OK_NODATA 0x1100 #define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101 #define VIRTIO_GPU_RESP_ERR_UNSPEC 0x1200 #define VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY 0x1201 #define VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID 0x1202 #define VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID 0x1203 #define VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER 0x1205 /* formats we accept (32bpp). Values match Linux virtio_gpu_formats. */ #define VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM 1 #define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2 #define VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM 3 #define VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM 4 #define VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM 67 #define VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM 68 #define VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM 121 #define VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM 134 /* fence flag (we just ack fences, no real fencing) */ #define VIRTIO_GPU_FLAG_FENCE (1 << 0) /* Fixed scanout geometry advertised to the guest. 1024×768 fits in the 16MB * surface pool (4 MB) with headroom, and is a standard Xfbdev mode. */ #define VGPU_SCANOUT_W 1024 #define VGPU_SCANOUT_H 768 #define VGPU_BPP 4 /* XRGB8888 = 4 bytes/pixel */ #define VGPU_SCANOUT_STRIDE (VGPU_SCANOUT_W * VGPU_BPP) /* 4096 bytes/row */ #define VGPU_SCANOUT_SIZE (VGPU_SCANOUT_STRIDE * VGPU_SCANOUT_H) /* 3 MB */ #define VGPU_MAX_RESOURCES 16 /* 1024×768×4 is 768 pages; keep headroom for larger dumb buffers. */ #define VGPU_MAX_BACKING_ENTRIES 2048 struct vgpu_resource { int in_use; u32 resource_id; u32 format; u32 width; u32 height; int has_backing; u64 backing_gpa; /* first entry (debug / cursor fallback) */ u64 backing_len; /* sum of all entries */ u32 nr_entries; struct virtio_gpu_mem_entry entries[VGPU_MAX_BACKING_ENTRIES]; }; /* Per-device state. Single instance (one virtio-gpu device). */ static struct { u64 gpa_base; u32 irq; /* config space image (16 bytes) */ struct virtio_gpu_config cfg; /* resource table */ struct vgpu_resource res[VGPU_MAX_RESOURCES]; /* scanout 0 state */ int scanout_enabled; u32 scanout_res_id; /* 0 = none */ u32 scanout_x, scanout_y; u32 scanout_w, scanout_h; /* surface pool (host vaddr) — composited frames land here */ void *surface_vaddr; u64 surface_size; /* dirty: set on FLUSH of the scanout resource, cleared by get_scanout_info */ int dirty; /* Phase 7: 累积 dirty rect(scanout 坐标系并集),desktop 局部 blit 用。 * dirty_rect_valid=0 时无矩形(与 dirty=0 等价;首帧建议全量拷贝)。 */ int dirty_rect_valid; u32 dirty_rx, dirty_ry, dirty_rw, dirty_rh; /* P7.6: hardware cursor state (cursorq UPDATE_CURSOR / MOVE_CURSOR)。 * cursor_visible=0 直到 guest 发 UPDATE_CURSOR 且 resource_id!=0。 * cursor_bitmap 存 ARGB 像素(row stride = VGPU_CURSOR_MAX),供 desktop * alpha-blend 叠加到 IDE host 区域。 */ int cursor_visible; u32 cursor_x, cursor_y; /* position on scanout */ u32 cursor_hot_x, cursor_hot_y; u32 cursor_w, cursor_h; /* actual cursor image dimensions */ u32 cursor_bitmap[VGPU_CURSOR_MAX * VGPU_CURSOR_MAX]; /* per-queue avail cursor */ u16 last_avail_ctrl; u16 last_avail_cursor; /* P9: RESOURCE_FLUSH 计数(只计 scanout resource 的 flush)。 * verify 脚本据 \[VGPU\] FLUSH_N 判断 Plasma/Xorg 是否真的推帧—— * SET_SCANOUT 只证明 fbdev 绑定了 resource,不代表有新像素。 */ u64 flush_count; struct virtio_backend backend; } g_gpu; /* ===== resource table helpers ===== */ static struct vgpu_resource *res_find(u32 id) { if (id == 0) return 0; for (int i = 0; i < VGPU_MAX_RESOURCES; i++) { if (g_gpu.res[i].in_use && g_gpu.res[i].resource_id == id) return &g_gpu.res[i]; } return 0; } static struct vgpu_resource *res_alloc(void) { for (int i = 0; i < VGPU_MAX_RESOURCES; i++) { if (!g_gpu.res[i].in_use) { g_gpu.res[i].in_use = 1; return &g_gpu.res[i]; } } return 0; } static void res_release(struct vgpu_resource *r) { if (!r) return; r->in_use = 0; r->resource_id = 0; r->has_backing = 0; r->backing_gpa = 0; r->backing_len = 0; r->nr_entries = 0; } /* ===== surface pool access ===== * The scanout shadow lives at the start of the 16MB graphics surface pool * (allocated in linux_loader.c). virtio-gpu composites there; the desktop * reads the same region. Falls back to disabled scanout if the pool is absent. */ static int surface_ready(void) { return g_gpu.surface_vaddr != 0 && g_gpu.surface_size >= VGPU_SCANOUT_SIZE; } /* ===== descriptor chain walker ===== * A virtio-gpu ctrl chain is typically: [cmd desc (read, F_NEXT)] → * [resp desc (write)]. We collect the first readable buffer as the command * and the first writable buffer as the response. Returns 0 on success and * fills cmd/resp host vaddrs + lens; resp may be NULL if driver omitted it. */ struct chain_bufs { void *cmd; u32 cmd_len; void *cmd_extra; u32 cmd_extra_len; /* 2nd readable desc (attach mem_entries) */ void *resp; u32 resp_len; u16 head; /* chain head index (for used ring) */ }; static int walk_chain(struct virtq_desc *desc, u32 qnum, u16 head, struct chain_bufs *out) { u16 cur = head; int steps = 0; out->cmd = 0; out->cmd_len = 0; out->cmd_extra = 0; out->cmd_extra_len = 0; out->resp = 0; out->resp_len = 0; out->head = head; while (steps < 64) { /* bound chain length */ if (cur >= qnum) return -1; struct virtq_desc *d = &desc[cur]; void *host = virtio_gpa_to_host(d->addr); if (!host) return -1; /* unmapped GPA */ if (d->flags & VIRTQ_DESC_F_WRITE) { if (!out->resp) { out->resp = host; out->resp_len = d->len; } } else { if (!out->cmd) { out->cmd = host; out->cmd_len = d->len; } else if (!out->cmd_extra) { out->cmd_extra = host; out->cmd_extra_len = d->len; } } if (!(d->flags & VIRTQ_DESC_F_NEXT)) break; cur = d->next; steps++; } return (out->cmd != 0) ? 0 : -1; } /* Write a simple NODATA response (just the ctrl_hdr). */ static void write_resp_nodata(void *resp, u32 resp_cap, u32 cmd_type, u32 resp_type) { if (!resp || resp_cap < (u32)sizeof(struct virtio_gpu_ctrl_hdr)) return; struct virtio_gpu_ctrl_hdr *h = (struct virtio_gpu_ctrl_hdr *)resp; h->type = resp_type; h->flags = 0; h->fence_id = 0; h->ctx_id = 0; h->padding = 0; (void)cmd_type; } /* ===== 2D command handlers ===== * Each returns the response type to write. cmd points at the full command * (ctrl_hdr + command-specific body). */ static u32 handle_get_display_info(void *resp, u32 resp_cap) { if (!resp || resp_cap < (u32)sizeof(struct virtio_gpu_resp_display_info)) return VIRTIO_GPU_RESP_ERR_UNSPEC; struct virtio_gpu_resp_display_info *r = (struct virtio_gpu_resp_display_info *)resp; /* header */ r->hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO; r->hdr.flags = 0; r->hdr.fence_id = 0; r->hdr.ctx_id = 0; r->hdr.padding = 0; /* one enabled scanout at (0,0,1024,768) */ for (int i = 0; i < 16; i++) { r->pmodes[i].r.x = 0; r->pmodes[i].r.y = 0; r->pmodes[i].r.width = 0; r->pmodes[i].r.height = 0; r->pmodes[i].enabled = 0; r->pmodes[i].flags = 0; } r->pmodes[0].r.width = VGPU_SCANOUT_W; r->pmodes[0].r.height = VGPU_SCANOUT_H; r->pmodes[0].enabled = 1; return VIRTIO_GPU_RESP_OK_DISPLAY_INFO; } static u32 handle_resource_create_2d(struct virtio_gpu_resource_create_2d *cmd) { if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; u32 id = cmd->resource_id; if (id == 0) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; if (res_find(id)) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; /* accept 32bpp formats used by Linux DRM/dumb buffers */ if (cmd->format != VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM && cmd->format != VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM && cmd->format != VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM && cmd->format != VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM && cmd->format != VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM && cmd->format != VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM && cmd->format != VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM && cmd->format != VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; if (cmd->width == 0 || cmd->height == 0 || cmd->width > 4096 || cmd->height > 4096) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; struct vgpu_resource *r = res_alloc(); if (!r) return VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; r->resource_id = id; r->format = cmd->format; r->width = cmd->width; r->height = cmd->height; r->has_backing = 0; return VIRTIO_GPU_RESP_OK_NODATA; } static u32 handle_resource_destroy(u32 resource_id) { struct vgpu_resource *r = res_find(resource_id); if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; /* if it was the scanout resource, detach */ if (g_gpu.scanout_enabled && g_gpu.scanout_res_id == resource_id) { g_gpu.scanout_enabled = 0; g_gpu.scanout_res_id = 0; } res_release(r); return VIRTIO_GPU_RESP_OK_NODATA; } static u32 handle_attach_backing(struct virtio_gpu_resource_attach_backing *cmd, u32 cmd_len, void *extra, u32 extra_len) { if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; u32 id = cmd->resource_id; struct vgpu_resource *r = res_find(id); if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; u32 n = cmd->nr_entries; if (n == 0 || n > VGPU_MAX_BACKING_ENTRIES) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; u32 hdr_off = (u32)sizeof(struct virtio_gpu_resource_attach_backing); u32 need = n * (u32)sizeof(struct virtio_gpu_mem_entry); struct virtio_gpu_mem_entry *entries = 0; if (cmd_len >= hdr_off + need) { entries = (struct virtio_gpu_mem_entry *)((u8 *)cmd + hdr_off); } else if (extra && extra_len >= need) { entries = (struct virtio_gpu_mem_entry *)extra; } else { return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; } u64 total = 0; for (u32 i = 0; i < n; i++) { r->entries[i] = entries[i]; total += entries[i].length; } r->nr_entries = n; r->has_backing = 1; r->backing_gpa = entries[0].addr; r->backing_len = total; return VIRTIO_GPU_RESP_OK_NODATA; } static u32 handle_detach_backing(u32 resource_id) { struct vgpu_resource *r = res_find(resource_id); if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; r->has_backing = 0; r->backing_gpa = 0; r->backing_len = 0; r->nr_entries = 0; return VIRTIO_GPU_RESP_OK_NODATA; } static u32 handle_set_scanout(struct virtio_gpu_set_scanout *cmd) { if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; if (cmd->scanout_id != 0) return VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID; if (cmd->resource_id == 0) { /* disabling scanout */ g_gpu.scanout_enabled = 0; g_gpu.scanout_res_id = 0; return VIRTIO_GPU_RESP_OK_NODATA; } struct vgpu_resource *r = res_find(cmd->resource_id); if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; if (cmd->r.width == 0 || cmd->r.height == 0) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; g_gpu.scanout_enabled = 1; g_gpu.scanout_res_id = cmd->resource_id; g_gpu.scanout_x = cmd->r.x; g_gpu.scanout_y = cmd->r.y; g_gpu.scanout_w = cmd->r.width; g_gpu.scanout_h = cmd->r.height; g_gpu.dirty = 1; log_info("[VGPU] SET_SCANOUT"); log_hex64("[VGPU] scanout w=", g_gpu.scanout_w); log_hex64("[VGPU] scanout h=", g_gpu.scanout_h); return VIRTIO_GPU_RESP_OK_NODATA; } /* Copy `len` bytes at resource-relative `off` from scatter-gather backing. */ static int backing_copy(struct vgpu_resource *r, u64 off, u32 len, u8 *dst) { u64 pos = 0; u32 i; if (!r || !dst || !r->has_backing || len == 0) return -1; for (i = 0; i < r->nr_entries && len; i++) { u64 elen = r->entries[i].length; if (off >= pos + elen) { pos += elen; continue; } { u64 skip = (off > pos) ? (off - pos) : 0; u64 take = elen - skip; void *host; u8 *s; u32 k; if (take > len) take = len; host = virtio_gpa_to_host(r->entries[i].addr + skip); if (!host) return -1; s = (u8 *)host; for (k = 0; k < (u32)take; k++) dst[k] = s[k]; dst += take; len -= (u32)take; off += take; pos += elen; } } return (len == 0) ? 0 : -1; } /* Copy a rect of a resource from guest backing memory into the surface pool * scanout region. Called for both TRANSFER_TO_HOST_2D and (implicitly via flush) * — we do the actual copy on TRANSFER, flush just marks dirty. */ static void composite_rect(struct vgpu_resource *r, struct virtio_gpu_rect *rect, u64 offset) { if (!surface_ready() || !r || !r->has_backing) return; /* only composite the resource currently bound to the scanout */ if (!g_gpu.scanout_enabled || g_gpu.scanout_res_id != r->resource_id) return; u32 src_stride = r->width * VGPU_BPP; u32 dst_stride = VGPU_SCANOUT_STRIDE; /* scanout is fixed 1024×768 */ /* clamp rect to resource bounds */ u32 rx = rect->x, ry = rect->y, rw = rect->width, rh = rect->height; if (rx >= r->width || ry >= r->height) return; if (rx + rw > r->width) rw = r->width - rx; if (ry + rh > r->height) rh = r->height - ry; /* clamp to scanout bounds */ u32 dx = g_gpu.scanout_x + rx; u32 dy = g_gpu.scanout_y + ry; if (dx >= VGPU_SCANOUT_W || dy >= VGPU_SCANOUT_H) return; if (dx + rw > VGPU_SCANOUT_W) rw = VGPU_SCANOUT_W - dx; if (dy + rh > VGPU_SCANOUT_H) rh = VGPU_SCANOUT_H - dy; if (rw == 0 || rh == 0) return; u8 *dst = (u8 *)g_gpu.surface_vaddr; u32 copy_bytes = rw * VGPU_BPP; static u8 rowbuf[4096]; if (copy_bytes > (u32)sizeof(rowbuf)) copy_bytes = (u32)sizeof(rowbuf); for (u32 row = 0; row < rh; row++) { u64 src_off = offset + (u64)(ry + row) * src_stride + (u64)rx * VGPU_BPP; u8 *d = dst + (u64)(dy + row) * dst_stride + (u64)dx * VGPU_BPP; if (backing_copy(r, src_off, copy_bytes, rowbuf) != 0) break; { u32 n = copy_bytes / 4; u32 *sp = (u32 *)rowbuf; u32 *dp = (u32 *)d; for (u32 i = 0; i < n; i++) dp[i] = sp[i]; } } } static u32 handle_transfer_to_host_2d(struct virtio_gpu_transfer_to_host_2d *cmd) { if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; struct vgpu_resource *r = res_find(cmd->resource_id); if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; composite_rect(r, &cmd->r, cmd->offset); return VIRTIO_GPU_RESP_OK_NODATA; } static u32 handle_resource_flush(struct virtio_gpu_resource_flush *cmd) { if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; /* mark scanout dirty so the desktop re-blits. We only flushed the scanout * resource (composite_rect already copied it on TRANSFER). */ if (g_gpu.scanout_enabled && g_gpu.scanout_res_id == cmd->resource_id) { g_gpu.dirty = 1; g_gpu.flush_count++; /* P9: 每 64 次 flush 打一行计数。串口噪音远小于逐次打印; * verify 用 FLUSH_N 出现判断 guest 真的在推帧。 */ if ((g_gpu.flush_count & 0x3F) == 1) { log_info("[VGPU] FLUSH_N"); log_hex64("[VGPU] flush count=", g_gpu.flush_count); } /* Phase 7: 把 flush rect(resource 坐标系)并入累积 dirty rect * (scanout 坐标系并集),desktop 据此只 blit 变化区域。 * 钳制规则与 composite_rect 一致。 */ u32 rx = cmd->r.x, ry = cmd->r.y, rw = cmd->r.width, rh = cmd->r.height; u32 dx = g_gpu.scanout_x + rx; u32 dy = g_gpu.scanout_y + ry; if (dx < VGPU_SCANOUT_W && dy < VGPU_SCANOUT_H && rw > 0 && rh > 0) { if (dx + rw > VGPU_SCANOUT_W) rw = VGPU_SCANOUT_W - dx; if (dy + rh > VGPU_SCANOUT_H) rh = VGPU_SCANOUT_H - dy; if (!g_gpu.dirty_rect_valid) { g_gpu.dirty_rx = dx; g_gpu.dirty_ry = dy; g_gpu.dirty_rw = rw; g_gpu.dirty_rh = rh; g_gpu.dirty_rect_valid = 1; } else { /* 并集:expand 到同时包含旧矩形与新矩形 */ u32 x0 = (dx < g_gpu.dirty_rx) ? dx : g_gpu.dirty_rx; u32 y0 = (dy < g_gpu.dirty_ry) ? dy : g_gpu.dirty_ry; u32 x1 = ((dx + rw) > (g_gpu.dirty_rx + g_gpu.dirty_rw)) ? (dx + rw) : (g_gpu.dirty_rx + g_gpu.dirty_rw); u32 y1 = ((dy + rh) > (g_gpu.dirty_ry + g_gpu.dirty_rh)) ? (dy + rh) : (g_gpu.dirty_ry + g_gpu.dirty_rh); g_gpu.dirty_rx = x0; g_gpu.dirty_ry = y0; g_gpu.dirty_rw = x1 - x0; g_gpu.dirty_rh = y1 - y0; } } } return VIRTIO_GPU_RESP_OK_NODATA; } /* ===== ctrlq processing ===== */ static void gpu_ctrlq_process(void) { struct virtq_desc *desc; struct virtq_avail *avail; struct virtq_used *used; u32 qnum; if (virtio_queue_get_ptrs_by_gpa(g_gpu.gpa_base, 0, &desc, &avail, &used, &qnum) != 0) return; u16 cur = g_gpu.last_avail_ctrl; u16 used_idx = used->idx; int budget = 32; /* bound work per notify */ int filled = 0; while (budget-- > 0 && cur != avail->idx) { u16 head = avail->ring[cur % qnum]; if (head >= qnum) { cur++; continue; } struct chain_bufs cb; if (walk_chain(desc, qnum, head, &cb) != 0) { /* bad chain: return it empty so the driver recycles */ used->ring[used_idx % qnum].id = head; used->ring[used_idx % qnum].len = 0; used_idx++; cur++; filled++; continue; } struct virtio_gpu_ctrl_hdr *hdr = (struct virtio_gpu_ctrl_hdr *)cb.cmd; u32 resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC; u32 resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr); /* default NODATA */ if (cb.cmd_len < (u32)sizeof(struct virtio_gpu_ctrl_hdr)) { resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC; } else { u32 t = hdr->type; switch (t) { case VIRTIO_GPU_CMD_GET_DISPLAY_INFO: resp_type = handle_get_display_info(cb.resp, cb.resp_len); if (resp_type == VIRTIO_GPU_RESP_OK_DISPLAY_INFO) resp_len = (u32)sizeof(struct virtio_gpu_resp_display_info); break; case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_resource_create_2d)) resp_type = handle_resource_create_2d( (struct virtio_gpu_resource_create_2d *)cb.cmd); else resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; break; case VIRTIO_GPU_CMD_RESOURCE_UNREF: { u32 rid = 0; if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_ctrl_hdr) + 4) rid = *(u32 *)((u8 *)cb.cmd + sizeof(struct virtio_gpu_ctrl_hdr)); resp_type = handle_resource_destroy(rid); break; } case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_resource_attach_backing)) resp_type = handle_attach_backing( (struct virtio_gpu_resource_attach_backing *)cb.cmd, cb.cmd_len, cb.cmd_extra, cb.cmd_extra_len); else resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; break; case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING: { u32 rid = 0; if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_ctrl_hdr) + 4) rid = *(u32 *)((u8 *)cb.cmd + sizeof(struct virtio_gpu_ctrl_hdr)); resp_type = handle_detach_backing(rid); break; } case VIRTIO_GPU_CMD_SET_SCANOUT: if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_set_scanout)) resp_type = handle_set_scanout( (struct virtio_gpu_set_scanout *)cb.cmd); else resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; break; case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_transfer_to_host_2d)) resp_type = handle_transfer_to_host_2d( (struct virtio_gpu_transfer_to_host_2d *)cb.cmd); else resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; break; case VIRTIO_GPU_CMD_RESOURCE_FLUSH: if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_resource_flush)) resp_type = handle_resource_flush( (struct virtio_gpu_resource_flush *)cb.cmd); else resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; break; case VIRTIO_GPU_CMD_GET_CAPSET_INFO: case VIRTIO_GPU_CMD_GET_CAPSET: case VIRTIO_GPU_CMD_GET_EDID: case VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID: case VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB: case VIRTIO_GPU_CMD_SET_SCANOUT_BLOB: /* No virgl/blob/EDID features advertised; reject cleanly. */ resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC; break; default: { static u32 last_unknown; if (t != last_unknown) { last_unknown = t; log_warn("[VGPU] unknown ctrl cmd"); log_hex64(" type=", t); } resp_type = VIRTIO_GPU_RESP_OK_NODATA; break; } } } /* write the response (unless GET_* already wrote a richer response) */ if (resp_type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO && resp_type != VIRTIO_GPU_RESP_OK_NODATA) { write_resp_nodata(cb.resp, cb.resp_len, hdr->type, resp_type); resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr); } else if (resp_type == VIRTIO_GPU_RESP_OK_NODATA && hdr->type != VIRTIO_GPU_CMD_GET_DISPLAY_INFO) { write_resp_nodata(cb.resp, cb.resp_len, hdr->type, resp_type); resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr); } /* fence ack: if the command requested a fence, echo fence_id in resp */ if ((hdr->flags & VIRTIO_GPU_FLAG_FENCE) && cb.resp && cb.resp_len >= (u32)sizeof(struct virtio_gpu_ctrl_hdr)) { struct virtio_gpu_ctrl_hdr *rh = (struct virtio_gpu_ctrl_hdr *)cb.resp; rh->flags |= VIRTIO_GPU_FLAG_FENCE; rh->fence_id = hdr->fence_id; } used->ring[used_idx % qnum].id = head; used->ring[used_idx % qnum].len = resp_len; used_idx++; cur++; filled++; } if (filled > 0) { virtio_queue_bump_used(used, used_idx); g_gpu.last_avail_ctrl = cur; virtio_mmio_raise_irq_by_gpa(g_gpu.gpa_base); } } /* P7.6: cursorq - process UPDATE_CURSOR / MOVE_CURSOR. * UPDATE_CURSOR: if resource_id != 0, copy cursor image from resource backing * (up to 64x64 ARGB). If resource_id == 0, hide cursor. * MOVE_CURSOR: update cursor position on scanout. */ static void gpu_cursorq_process(void) { struct virtq_desc *desc; struct virtq_avail *avail; struct virtq_used *used; u32 qnum; if (virtio_queue_get_ptrs_by_gpa(g_gpu.gpa_base, 1, &desc, &avail, &used, &qnum) != 0) return; u16 cur = g_gpu.last_avail_cursor; u16 used_idx = used->idx; int budget = 16; int filled = 0; while (budget-- > 0 && cur != avail->idx) { u16 head = avail->ring[cur % qnum]; if (head >= qnum) { cur++; continue; } struct chain_bufs cb; if (walk_chain(desc, qnum, head, &cb) == 0 && cb.cmd && cb.resp) { struct virtio_gpu_ctrl_hdr *hdr = (struct virtio_gpu_ctrl_hdr *)cb.cmd; if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_update_cursor)) { struct virtio_gpu_update_cursor *cmd = (struct virtio_gpu_update_cursor *)cb.cmd; if (hdr->type == VIRTIO_GPU_CMD_UPDATE_CURSOR) { u32 rid = cmd->resource_id; g_gpu.cursor_x = cmd->pos.x; g_gpu.cursor_y = cmd->pos.y; g_gpu.cursor_hot_x = cmd->hot_x; g_gpu.cursor_hot_y = cmd->hot_y; if (rid != 0) { struct vgpu_resource *r = res_find(rid); if (r && r->has_backing && r->width > 0 && r->height > 0) { void *host = virtio_gpa_to_host(r->backing_gpa); if (host) { int cw = (int)r->width; int ch = (int)r->height; if (cw > VGPU_CURSOR_MAX) cw = VGPU_CURSOR_MAX; if (ch > VGPU_CURSOR_MAX) ch = VGPU_CURSOR_MAX; u32 *src = (u32 *)host; for (int row = 0; row < ch; row++) { for (int col = 0; col < cw; col++) { g_gpu.cursor_bitmap[row * VGPU_CURSOR_MAX + col] = src[row * (int)r->width + col]; } } g_gpu.cursor_w = (u32)cw; g_gpu.cursor_h = (u32)ch; g_gpu.cursor_visible = 1; } else { /* backing GPA unmapped: still show at pos, no image */ g_gpu.cursor_visible = 0; } } else { g_gpu.cursor_visible = 0; } } else { /* resource_id == 0: hide cursor */ g_gpu.cursor_visible = 0; } } else if (hdr->type == VIRTIO_GPU_CMD_MOVE_CURSOR) { g_gpu.cursor_x = cmd->pos.x; g_gpu.cursor_y = cmd->pos.y; /* MOVE_CURSOR does not change visibility or image */ } } write_resp_nodata(cb.resp, cb.resp_len, hdr->type, VIRTIO_GPU_RESP_OK_NODATA); } used->ring[used_idx % qnum].id = head; used->ring[used_idx % qnum].len = (u32)sizeof(struct virtio_gpu_ctrl_hdr); used_idx++; cur++; filled++; } if (filled > 0) { virtio_queue_bump_used(used, used_idx); g_gpu.last_avail_cursor = cur; virtio_mmio_raise_irq_by_gpa(g_gpu.gpa_base); } } /* ===== virtio_backend interface ===== */ static u32 gpu_read_config(u32 offset, int width) { /* config space is 16 bytes, little-endian u32 fields. Guest reads u32. */ if (width != 4) return 0; if (offset >= (u32)sizeof(struct virtio_gpu_config)) return 0; const u8 *p = (const u8 *)&g_gpu.cfg; u32 v; v = (u32)p[offset] | ((u32)p[offset+1] << 8) | ((u32)p[offset+2] << 16) | ((u32)p[offset+3] << 24); return v; } static void gpu_write_config(u32 offset, u32 value) { /* events_clear: writing a bit clears it in events_read. We have no events, * so this is effectively a no-op, but honor the w1c semantics. */ if (offset == 4) { /* events_clear */ g_gpu.cfg.events_read &= ~value; } (void)offset; } static void gpu_queue_notify(u32 queue_idx) { if (queue_idx == 0) { gpu_ctrlq_process(); } else if (queue_idx == 1) { gpu_cursorq_process(); } } static void gpu_reset(void) { g_gpu.cfg.events_read = 0; g_gpu.cfg.events_clear = 0; g_gpu.cfg.num_scanouts = 1; g_gpu.cfg.num_capsets = 0; g_gpu.scanout_enabled = 0; g_gpu.scanout_res_id = 0; g_gpu.scanout_w = 0; g_gpu.scanout_h = 0; g_gpu.dirty = 0; g_gpu.dirty_rect_valid = 0; g_gpu.dirty_rx = 0; g_gpu.dirty_ry = 0; g_gpu.dirty_rw = 0; g_gpu.dirty_rh = 0; g_gpu.cursor_visible = 0; g_gpu.cursor_x = 0; g_gpu.cursor_y = 0; g_gpu.cursor_hot_x = 0; g_gpu.cursor_hot_y = 0; g_gpu.cursor_w = 0; g_gpu.cursor_h = 0; g_gpu.last_avail_ctrl = 0; g_gpu.last_avail_cursor = 0; g_gpu.flush_count = 0; for (int i = 0; i < VGPU_MAX_RESOURCES; i++) res_release(&g_gpu.res[i]); } static struct virtio_backend g_gpu_backend = { .device_id = VIRTIO_ID_GPU, .gpa_base = VIRTIO_MMIO_GPU_GPA, .irq = 10, /* cmdline :10, vector 0x3A */ .num_queues = 2, /* ctrlq + cursorq */ .queue_size = 64, .device_features = (1ULL << VIRTIO_F_VERSION_1), .config_len = (u32)sizeof(struct virtio_gpu_config), .read_config = gpu_read_config, .write_config = gpu_write_config, .queue_notify = gpu_queue_notify, .reset = gpu_reset, }; /* ===== public API ===== */ int virtio_gpu_get_scanout_info(struct virtio_gpu_scanout_info *out) { if (!out) return -1; if (!surface_ready()) { out->host_vaddr = 0; out->width = 0; out->height = 0; out->stride = 0; out->dirty = 0; out->enabled = 0; out->dirty_x = 0; out->dirty_y = 0; out->dirty_w = 0; out->dirty_h = 0; out->cursor_visible = 0; out->cursor_x = 0; out->cursor_y = 0; out->cursor_hot_x = 0; out->cursor_hot_y = 0; out->cursor_w = 0; out->cursor_h = 0; out->cursor_bitmap = 0; return -1; } out->host_vaddr = g_gpu.surface_vaddr; out->width = VGPU_SCANOUT_W; out->height = VGPU_SCANOUT_H; out->stride = VGPU_SCANOUT_STRIDE; out->enabled = (u32)g_gpu.scanout_enabled; out->dirty = (u32)g_gpu.dirty; /* Phase 7: 输出累积 dirty rect(查询一并清零)。 * dirty=1 但 rect 无效(理论不出现)时给全帧矩形兜底。 */ if (g_gpu.dirty && g_gpu.dirty_rect_valid) { out->dirty_x = g_gpu.dirty_rx; out->dirty_y = g_gpu.dirty_ry; out->dirty_w = g_gpu.dirty_rw; out->dirty_h = g_gpu.dirty_rh; } else if (g_gpu.dirty) { out->dirty_x = 0; out->dirty_y = 0; out->dirty_w = VGPU_SCANOUT_W; out->dirty_h = VGPU_SCANOUT_H; } else { out->dirty_x = 0; out->dirty_y = 0; out->dirty_w = 0; out->dirty_h = 0; } g_gpu.dirty = 0; /* query consumes the dirty flag */ g_gpu.dirty_rect_valid = 0; g_gpu.dirty_rw = 0; g_gpu.dirty_rh = 0; /* P7.6: cursor state (not consumed by query - desktop reads every frame) */ out->cursor_visible = (u32)g_gpu.cursor_visible; out->cursor_x = g_gpu.cursor_x; out->cursor_y = g_gpu.cursor_y; out->cursor_hot_x = g_gpu.cursor_hot_x; out->cursor_hot_y = g_gpu.cursor_hot_y; out->cursor_w = g_gpu.cursor_w; out->cursor_h = g_gpu.cursor_h; out->cursor_bitmap = g_gpu.cursor_visible ? g_gpu.cursor_bitmap : 0; return 0; } void virtio_gpu_backend_init(void) { g_gpu.gpa_base = VIRTIO_MMIO_GPU_GPA; g_gpu.irq = 10; gpu_reset(); /* sets cfg + clears resource table */ /* Bind the surface pool as the scanout shadow buffer. */ g_gpu.surface_vaddr = linux_get_surface_vaddr(); u64 hpa = 0, gpa = 0, size = 0; if (linux_get_surface_info(&hpa, &gpa, &size) == 0) { g_gpu.surface_size = size; } else { g_gpu.surface_size = 0; } if (!surface_ready()) { log_warn("[VGPU] surface pool unavailable — scanout disabled"); } else { log_info("[VGPU] virtio-gpu 2D backend ready (1024x768 XRGB8888)"); } virtio_mmio_register(&g_gpu_backend); }