Files
Deshab-OS/CODE/UTSM/vmm/virtio_gpu.c
T
2026-08-09 22:20:01 +08:00

919 lines
35 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <utsm/virtio_mmio.h>
#include <utsm/linux_loader.h>
#include <utsm/log.h>
#include <utsm/types.h>
/* ===== virtio-gpu protocol structs (must match <uapi/linux/virtio_gpu.h>) ===== */
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);
}