/* 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 (Virtio 1.1 §5.4.4): * GET_NUM_SCANOUTS, GET_DISPLAY_INFO, RESOURCE_CREATE_2D, RESOURCE_DESTROY, * RESOURCE_ATTACH_BACKING, RESOURCE_DETACH_BACKING, SET_SCANOUT, * TRANSFER_TO_HOST_2D, RESOURCE_FLUSH. * * 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)); /* P7.6: cursor queue commands (UPDATE_CURSOR / MOVE_CURSOR share struct) */ struct virtio_gpu_update_cursor { struct virtio_gpu_ctrl_hdr hdr; struct virtio_gpu_rect pos; /* x, y on scanout */ u32 resource_id; /* 0 = hide cursor */ u32 hot_x; u32 hot_y; } __attribute__((packed)); #define VGPU_CURSOR_MAX 64 /* max cursor image dimension (pixels) */ /* command types */ #define VIRTIO_GPU_CMD_GET_NUM_SCANOUTS 0x0102 #define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0103 #define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0104 #define VIRTIO_GPU_CMD_RESOURCE_DESTROY 0x0105 #define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106 #define VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING 0x0107 #define VIRTIO_GPU_CMD_SET_SCANOUT 0x0108 #define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0109 #define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x010a #define VIRTIO_GPU_CMD_UPDATE_CURSOR 0x0110 #define VIRTIO_GPU_CMD_MOVE_CURSOR 0x0111 /* 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). Copied raw — no endian swap. */ #define VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM 1 #define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2 #define VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM 3 #define VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM 4 /* 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 #define VGPU_MAX_BACKING_ENTRIES 8 struct vgpu_resource { int in_use; u32 resource_id; u32 format; u32 width; u32 height; int has_backing; /* Backing memory (contiguous assumption: first entry GPA + total len). * Multi-entry attach is summed; guest framebuffer allocations are * physically contiguous so this holds in practice. */ u64 backing_gpa; u64 backing_len; }; /* 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; 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; } /* ===== 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 *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->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; } } 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; reject exotic sizes */ if (cmd->format < VIRTIO_GPU_FORMAT_B8G8R8A8_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) { 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; /* entries immediately follow the attach_backing command in the same buffer */ u32 hdr_off = (u32)sizeof(struct virtio_gpu_resource_attach_backing); if (cmd_len < hdr_off + n * (u32)sizeof(struct virtio_gpu_mem_entry)) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; struct virtio_gpu_mem_entry *entries = (struct virtio_gpu_mem_entry *)((u8 *)cmd + hdr_off); /* MVP: record first entry GPA + summed length (contiguous assumption). */ u64 base = entries[0].addr; u64 total = 0; for (u32 i = 0; i < n; i++) total += entries[i].length; r->has_backing = 1; r->backing_gpa = base; 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; 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; return VIRTIO_GPU_RESP_OK_NODATA; } /* 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; void *backing_host = virtio_gpa_to_host(r->backing_gpa); if (!backing_host) 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; u8 *src = (u8 *)backing_host + offset; u32 copy_bytes = rw * VGPU_BPP; /* guard against backing_len overflow */ u64 src_base = offset + (u64)ry * src_stride + (u64)rx * VGPU_BPP; u64 src_need = src_base + (u64)rh * src_stride; if (src_need > r->backing_len) { /* partial: clamp rows to what the backing covers */ u64 avail_rows = (r->backing_len > offset) ? (r->backing_len - offset) / src_stride : 0; if (ry >= avail_rows) return; if (ry + rh > avail_rows) rh = (u32)(avail_rows - ry); if (rh == 0) return; } for (u32 row = 0; row < rh; row++) { u8 *s = src + (u64)(ry + row) * src_stride + (u64)rx * VGPU_BPP; u8 *d = dst + (u64)(dy + row) * dst_stride + (u64)dx * VGPU_BPP; /* word-copy 4-byte pixels */ u32 n = copy_bytes / 4; u32 *sp = (u32 *)s; u32 *dp = (u32 *)d; for (u32 i = 0; i < n; i++) dp[i] = sp[i]; /* trailing bytes (shouldn't happen for 32bpp) */ for (u32 i = n * 4; i < copy_bytes; i++) d[i] = s[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; /* 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_NUM_SCANOUTS: if (cb.resp && cb.resp_len >= (u32)sizeof(struct virtio_gpu_resp_num_scanouts)) { struct virtio_gpu_resp_num_scanouts *rn = (struct virtio_gpu_resp_num_scanouts *)cb.resp; rn->hdr.type = VIRTIO_GPU_RESP_OK_NODATA; rn->hdr.flags = 0; rn->hdr.fence_id = 0; rn->hdr.ctx_id = 0; rn->hdr.padding = 0; rn->num_scanouts = 1; rn->padding = 0; resp_type = VIRTIO_GPU_RESP_OK_NODATA; resp_len = (u32)sizeof(struct virtio_gpu_resp_num_scanouts); } else { resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC; } break; 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_DESTROY: { /* DESTROY has just hdr + resource_id after */ 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); 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; default: /* unknown / 3D / cursor commands on ctrlq: ack NODATA */ 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) { /* error: write NODATA with error type if we haven't already */ if (hdr->type != VIRTIO_GPU_CMD_GET_NUM_SCANOUTS) 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_NUM_SCANOUTS && hdr->type != VIRTIO_GPU_CMD_GET_DISPLAY_INFO) { /* plain NODATA success: write header */ 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; 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); }