985 lines
37 KiB
C
985 lines
37 KiB
C
/* 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 <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));
|
||
|
||
/* 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);
|
||
}
|