FUCKKKK
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/linux_compat.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <ipc_proto.h>
|
||||
@@ -122,6 +123,35 @@ static long hcall_shm_info(u64 a0, u64 a1, u64 a2) {
|
||||
return UTSM_HCALL_OK;
|
||||
}
|
||||
|
||||
/* VSCode integration Phase 1: query graphics surface pool.
|
||||
* Mirrors hcall_shm_info: writes gpa + size into guest-provided out-pointers.
|
||||
* Linux user-space then mmaps /dev/utsm at the returned GPA to obtain a
|
||||
* writable scanout buffer (Phase 3 virtio-gpu shadow buffer). */
|
||||
static long hcall_surface_info(u64 a0, u64 a1, u64 a2) {
|
||||
/* a0 = GPA of gpa_out (u64), a1 = GPA of size_out (u64) */
|
||||
(void)a2;
|
||||
|
||||
u64 surface_gpa = 0, surface_size = 0;
|
||||
if (linux_get_surface_info(0, &surface_gpa, &surface_size) != 0) {
|
||||
/* Surface pool not initialized (Linux guest not loaded, or alloc failed) */
|
||||
return UTSM_HCALL_NOMEM;
|
||||
}
|
||||
|
||||
/* Write results to guest memory */
|
||||
if (a0 != 0 && guest_write_u64(a0, surface_gpa) != 0) {
|
||||
log_error("[HCALL] SURFACE_INFO: failed to write gpa to guest");
|
||||
return UTSM_HCALL_INVAL;
|
||||
}
|
||||
if (a1 != 0 && guest_write_u64(a1, surface_size) != 0) {
|
||||
log_error("[HCALL] SURFACE_INFO: failed to write size to guest");
|
||||
return UTSM_HCALL_INVAL;
|
||||
}
|
||||
|
||||
log_hex64("[HCALL] SURFACE_INFO: gpa=", surface_gpa);
|
||||
log_hex64("[HCALL] SURFACE_INFO: size=", surface_size);
|
||||
return UTSM_HCALL_OK;
|
||||
}
|
||||
|
||||
static long hcall_cap_validate(u64 a0, u64 a1, u64 a2) {
|
||||
/* Phase 1.3: stub — capability validation not yet implemented */
|
||||
(void)a0; (void)a1; (void)a2;
|
||||
@@ -284,6 +314,9 @@ int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx,
|
||||
case UTSM_HCALL_SHM_INFO:
|
||||
result = hcall_shm_info(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
case UTSM_HCALL_SURFACE_INFO:
|
||||
result = hcall_surface_info(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
case UTSM_HCALL_CAP_VALIDATE:
|
||||
result = hcall_cap_validate(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
vmm/hypercall.o: vmm\hypercall.c include\utsm\hypercall.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h \
|
||||
include\utsm\linux_compat.h include\utsm\log.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\linux_compat.h include\utsm\linux_loader.h \
|
||||
include\utsm\log.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\hypercall.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
@@ -11,5 +11,6 @@ include\utsm\vmm.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
|
||||
@@ -16,14 +16,20 @@
|
||||
|
||||
#include <utsm/linux_compat.h>
|
||||
#include <utsm/linux_resume.h>
|
||||
#include <utsm/linux_loader.h> /* VSCode Phase 5: linux_find_vscode_module */
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/virtio_mmio.h> /* VSCode Phase 2: virtio_input_push_* */
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/block.h>
|
||||
#include <ipc_proto.h>
|
||||
#include "../../tools/fat32_io.h"
|
||||
|
||||
/* 兼容层全局配置(由 UTSM kernel/main.c 定义) */
|
||||
extern char g_compat_lib_path[];
|
||||
extern char g_compat_linux_guest_path[];
|
||||
|
||||
/* ===== 内部辅助 ===== */
|
||||
|
||||
static u64 my_strlen(const char *s) {
|
||||
@@ -64,9 +70,17 @@ static int lxc_is_available(void) {
|
||||
return (g_compat_ready && linux_is_parked() && ipc_shm_is_ready()) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* 前向声明:payload_pool 拷出助手(定义在文件传输段,exec 新协议复用) */
|
||||
static u64 lxc_pool_copyout(char *buf, u64 cap, u32 data_len);
|
||||
|
||||
/* 等待 Linux daemon 的响应消息。
|
||||
* linux_resume() 返回后,daemon 已 park,所有响应消息应已在 ring 中。
|
||||
* 我们循环 pop 直到拿到 EXEC_EXIT 或 ring 空(超时保护)。 */
|
||||
* 我们循环 pop 直到拿到 EXEC_EXIT 或 ring 空(超时保护)。
|
||||
*
|
||||
* 新协议(P3):daemon 把 stdout 直写 payload_pool,EXEC_EXIT 为 16B
|
||||
* (exit_code + flags + stdout_len + stdout_total),flags&F_POOL 时
|
||||
* 从 pool 拷出;F_TRUNC/F_TIMEOUT 记串口日志。旧协议(8B exit_code)
|
||||
* 输出仍走 EXEC_STDOUT 内联,下方分支兼容。 */
|
||||
static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
u64 *exit_code) {
|
||||
u64 written = 0;
|
||||
@@ -84,7 +98,7 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
switch (msg.type) {
|
||||
case UTSM_MSG_EXEC_STDOUT:
|
||||
case UTSM_MSG_EXEC_STDERR: {
|
||||
/* 拼接到 stdout 缓冲区(stderr 也并入,简化调用方处理) */
|
||||
/* 旧式内联输出(新协议下 stdout 走 pool,正常不会再收到) */
|
||||
u32 n = msg.data_len;
|
||||
if (out_buf && out_cap > written) {
|
||||
u32 space = (u32)(out_cap - written);
|
||||
@@ -99,6 +113,22 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
struct ipc_exec_exit ex;
|
||||
my_memcpy(&ex, msg.data, sizeof(ex));
|
||||
exit_val = ex.exit_code;
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_POOL) {
|
||||
/* stdout 已 staged 在 payload_pool[0..stdout_len) */
|
||||
written = lxc_pool_copyout(out_buf, out_cap,
|
||||
ex.stdout_len);
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_TRUNC) {
|
||||
log_warn("[LNXC] exec: output truncated at pool cap");
|
||||
log_hex64("[LNXC] exec: total bytes=", ex.stdout_total);
|
||||
}
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_TIMEOUT)
|
||||
log_warn("[LNXC] exec: child timed out, SIGKILLed");
|
||||
}
|
||||
} else if (msg.data_len >= 8) {
|
||||
/* 旧式 8B:exit_code + reserved */
|
||||
u32 ec = 0;
|
||||
my_memcpy(&ec, msg.data, 4);
|
||||
exit_val = ec;
|
||||
}
|
||||
got_exit = 1;
|
||||
break;
|
||||
@@ -117,9 +147,13 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
return got_exit ? 0 : -4; /* -4 = 超时无 exit 响应 */
|
||||
}
|
||||
|
||||
static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
char *stdout_buf, u64 stdout_cap, u64 *stdout_len,
|
||||
u64 *exit_code) {
|
||||
/* 构建并发送 EXEC_REQUEST,唤醒 guest,然后 drain 响应。
|
||||
* flags 取 0(同步,捕获 stdout)或 UTSM_EXEC_FLAG_ASYNC(detach 立即返回)。
|
||||
* 返回 0 = 收到 EXEC_EXIT(exit_code 已写出),负值失败。 */
|
||||
static int lxc_exec_internal(const char *path, int argc,
|
||||
const char *const *argv, u32 flags,
|
||||
char *stdout_buf, u64 stdout_cap, u64 *stdout_len,
|
||||
u64 *exit_code) {
|
||||
if (!lxc_is_available()) {
|
||||
return -1;
|
||||
}
|
||||
@@ -131,7 +165,7 @@ static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
struct ipc_exec_request req;
|
||||
my_memzero(&req, sizeof(req));
|
||||
req.argc = (u32)argc;
|
||||
req.flags = 0;
|
||||
req.flags = flags;
|
||||
|
||||
u64 plen = my_strlen(path);
|
||||
if (plen >= UTSM_EXEC_PATH_MAX) plen = UTSM_EXEC_PATH_MAX - 1;
|
||||
@@ -167,6 +201,29 @@ static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
return lxc_drain_responses(stdout_buf, stdout_cap, stdout_len, exit_code);
|
||||
}
|
||||
|
||||
static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
char *stdout_buf, u64 stdout_cap, u64 *stdout_len,
|
||||
u64 *exit_code) {
|
||||
return lxc_exec_internal(path, argc, argv, 0,
|
||||
stdout_buf, stdout_cap, stdout_len, exit_code);
|
||||
}
|
||||
|
||||
/* VSCode Phase 5: 异步执行——daemon fork+setsid+detach 后立即回
|
||||
* EXEC_EXIT(0),不捕获输出、不等待退出。用于 X server / VSCode 等长
|
||||
* 生命周期 GUI 进程。返回 0 = spawn 成功。 */
|
||||
static int lxc_exec_async(const char *path, int argc,
|
||||
const char *const *argv) {
|
||||
u64 exit_code = 1;
|
||||
int rc = lxc_exec_internal(path, argc, argv, UTSM_EXEC_FLAG_ASYNC,
|
||||
0, 0, 0, &exit_code);
|
||||
if (rc != 0) return rc;
|
||||
if (exit_code != 0) {
|
||||
log_hex64("[LNXC] exec_async: daemon spawn failed, code=", exit_code);
|
||||
return -5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxc_status(char *buf, u64 cap) {
|
||||
if (!buf || cap == 0) return 0;
|
||||
const char *s;
|
||||
@@ -445,6 +502,73 @@ static int lxc_file_push(const char *fat32_name, const char *linux_path,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== VSCode Phase 2: 输入转发(PS/2 → virtio-input) ===== */
|
||||
|
||||
/* 上次鼠标按钮状态,用于边沿检测(press/release)。 */
|
||||
static u8 g_prev_mouse_buttons = 0;
|
||||
|
||||
static void lxc_input_forward_keyboard(u16 code, u32 value) {
|
||||
virtio_input_push_keyboard(code, value);
|
||||
}
|
||||
|
||||
static void lxc_input_forward_mouse(i32 dx, i32 dy, u8 buttons) {
|
||||
/* 相对位移 */
|
||||
if (dx != 0) virtio_input_push_mouse_rel(VIO_INPUT_REL_X, dx);
|
||||
if (dy != 0) virtio_input_push_mouse_rel(VIO_INPUT_REL_Y, dy);
|
||||
|
||||
/* 按钮变化检测:XOR 找出变化的位 */
|
||||
u8 changed = buttons ^ g_prev_mouse_buttons;
|
||||
if (changed & 0x01)
|
||||
virtio_input_push_mouse_button(VIO_INPUT_BTN_LEFT, (buttons & 0x01) ? 1 : 0);
|
||||
if (changed & 0x02)
|
||||
virtio_input_push_mouse_button(VIO_INPUT_BTN_RIGHT, (buttons & 0x02) ? 1 : 0);
|
||||
if (changed & 0x04)
|
||||
virtio_input_push_mouse_button(VIO_INPUT_BTN_MIDDLE, (buttons & 0x04) ? 1 : 0);
|
||||
g_prev_mouse_buttons = buttons;
|
||||
|
||||
/* SYN_REPORT:libinput 依赖此帧结束标记 */
|
||||
virtio_input_push_mouse_syn();
|
||||
}
|
||||
|
||||
/* ===== VSCode Phase 3: virtio-gpu scanout 查询转发 =====
|
||||
* desktop 在 IDE attached 时周期调用,把 guest 合成的帧 blit 到 framebuffer。
|
||||
* 我们只做字段拷贝(linux_compat_scanout_info 与 virtio_gpu_scanout_info 布局一致)。 */
|
||||
static int lxc_gpu_get_scanout_info(struct linux_compat_scanout_info *out) {
|
||||
if (!out) return -1;
|
||||
struct virtio_gpu_scanout_info vi;
|
||||
int rc = virtio_gpu_get_scanout_info(&vi);
|
||||
if (rc != 0) {
|
||||
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 rc;
|
||||
}
|
||||
out->host_vaddr = vi.host_vaddr;
|
||||
out->width = vi.width;
|
||||
out->height = vi.height;
|
||||
out->stride = vi.stride;
|
||||
out->dirty = vi.dirty;
|
||||
out->enabled = vi.enabled;
|
||||
out->dirty_x = vi.dirty_x;
|
||||
out->dirty_y = vi.dirty_y;
|
||||
out->dirty_w = vi.dirty_w;
|
||||
out->dirty_h = vi.dirty_h;
|
||||
out->cursor_visible = vi.cursor_visible;
|
||||
out->cursor_x = vi.cursor_x;
|
||||
out->cursor_y = vi.cursor_y;
|
||||
out->cursor_hot_x = vi.cursor_hot_x;
|
||||
out->cursor_hot_y = vi.cursor_hot_y;
|
||||
out->cursor_w = vi.cursor_w;
|
||||
out->cursor_h = vi.cursor_h;
|
||||
out->cursor_bitmap = vi.cursor_bitmap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== 服务表 ===== */
|
||||
|
||||
static const linux_compat_service g_lxc_service = {
|
||||
@@ -457,8 +581,376 @@ static const linux_compat_service g_lxc_service = {
|
||||
lxc_file_write,
|
||||
lxc_file_pull,
|
||||
lxc_file_push,
|
||||
lxc_input_forward_keyboard,
|
||||
lxc_input_forward_mouse,
|
||||
lxc_gpu_get_scanout_info,
|
||||
lxc_exec_async,
|
||||
};
|
||||
|
||||
const linux_compat_service *linux_compat_get_service(void) {
|
||||
return &g_lxc_service;
|
||||
}
|
||||
|
||||
/* ===== Phase 2: SYSTEM/lib .so 全量推送到 Linux guest =====
|
||||
*
|
||||
* 启动时把 FAT32 根目录下 lib/ 子目录(g_compat_lib_path)中所有 .so
|
||||
* 文件流式推送到 Linux guest 的 g_compat_linux_guest_path 目录。
|
||||
* 流式推送:按簇(4KB)读取 FAT32 文件 → 立即 lxc_file_write 写入 guest,
|
||||
* 不依赖 fat32_io.h 的整文件 256KB 缓冲,可处理 MB 级 .so。
|
||||
*
|
||||
* 路径假设:build.ps1 把 SYSTEM/ 打包为 FAT32 根,因此 Windows
|
||||
* D:\Code\Deshab\SYSTEM\lib\ 在 FAT32 上对应根目录下的 "lib/" 子目录。
|
||||
*/
|
||||
|
||||
/* 把内存中已有数据按 pool 容量分块推送到 Linux 文件。
|
||||
* 用于整文件内存数据的批量上传。 */
|
||||
__attribute__((unused))
|
||||
static int lxc_file_push_mem(const char *linux_path, const u8 *data, u32 size) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!linux_path || (!data && size > 0)) return -1;
|
||||
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
u64 off = 0;
|
||||
for (;;) {
|
||||
u64 remain = (u64)size - off;
|
||||
u64 chunk = remain > pool_cap ? pool_cap : remain;
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write(linux_path, off, (const char *)data + off,
|
||||
chunk, &written);
|
||||
if (rc != 0) return rc;
|
||||
if (written != chunk) return -5; /* 短写 */
|
||||
off += written;
|
||||
if (off >= size) break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 流式推送:按簇读取 FAT32 文件,立即写入 Linux guest。
|
||||
* first_clus : FAT32 文件首簇号
|
||||
* file_size : 文件字节大小
|
||||
* linux_path : guest 端目标绝对路径
|
||||
* 返回 0 成功,负值失败。
|
||||
* 复用 lxc_f32_ensure() 已初始化的 FAT32 库(f32_blk_read)。 */
|
||||
static int lxc_push_fat32_file_streaming(u32 first_clus, u32 file_size,
|
||||
const char *linux_path) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!linux_path) return -1;
|
||||
if (f32_disk_load() != 0) return -2;
|
||||
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
u32 cluster_bytes = spc * 512;
|
||||
if (cluster_bytes > sizeof(f32_cluster)) cluster_bytes = sizeof(f32_cluster);
|
||||
|
||||
/* 用 f32_data 作为单簇中转缓冲(每次最多 pool_cap 字节) */
|
||||
u32 buf_cap = pool_cap;
|
||||
if (buf_cap > sizeof(f32_data)) buf_cap = sizeof(f32_data);
|
||||
|
||||
u64 off = 0;
|
||||
u32 fc = first_clus;
|
||||
u32 buf_used = 0;
|
||||
while (fc >= 2 && fc < 0x0FFFFFF8 && off < file_size) {
|
||||
u32 fc_lba = data_lba + (fc - 2) * spc;
|
||||
u32 fc_bytes = spc * 512;
|
||||
if (fc_bytes > sizeof(f32_cluster)) fc_bytes = sizeof(f32_cluster);
|
||||
if (f32_read_sectors(fc_lba, spc, f32_cluster) != 0) return -3;
|
||||
|
||||
/* 把当前簇数据按 buf_cap 分批写入 */
|
||||
u32 ci = 0;
|
||||
while (ci < fc_bytes && off + ci < file_size) {
|
||||
u32 space = buf_cap - buf_used;
|
||||
u32 copy = fc_bytes - ci;
|
||||
if (copy > space) copy = space;
|
||||
if (copy > file_size - (u32)(off + ci)) copy = (u32)(file_size - (off + ci));
|
||||
for (u32 b = 0; b < copy; b++) {
|
||||
f32_data[buf_used + b] = f32_cluster[ci + b];
|
||||
}
|
||||
buf_used += copy;
|
||||
ci += copy;
|
||||
/* 缓冲满 → 写入 guest */
|
||||
if (buf_used >= buf_cap || off + ci >= file_size) {
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write(linux_path, off, (const char *)f32_data,
|
||||
buf_used, &written);
|
||||
if (rc != 0) return rc;
|
||||
if (written != buf_used) return -5;
|
||||
off += written;
|
||||
buf_used = 0;
|
||||
}
|
||||
}
|
||||
/* 跟随 FAT 链 */
|
||||
u32 fo = fat_byte_off + fc * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
/* 处理剩余缓冲 */
|
||||
if (buf_used > 0 && off < file_size) {
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write(linux_path, off, (const char *)f32_data,
|
||||
buf_used, &written);
|
||||
if (rc != 0) return rc;
|
||||
off += written;
|
||||
}
|
||||
return (off >= file_size) ? 0 : -6;
|
||||
}
|
||||
|
||||
/* .so 文件收集回调上下文 */
|
||||
typedef struct {
|
||||
u32 count; /* 已收集的 .so 数量 */
|
||||
u32 max_count; /* 上限 */
|
||||
int error; /* 0=正常, 非 0=出错 */
|
||||
} lxc_so_collect_ctx;
|
||||
|
||||
/* f32_list_dir 回调:过滤 .so 后缀的文件,记录发现 */
|
||||
static int lxc_so_collect_cb(const f32_entry *e, void *user_data) {
|
||||
lxc_so_collect_ctx *ctx = (lxc_so_collect_ctx *)user_data;
|
||||
if (!e || !ctx) return 0;
|
||||
if (e->is_dir) return 0; /* 跳过子目录 */
|
||||
|
||||
/* 判断 .so 后缀(大小写不敏感) */
|
||||
const char *name = e->has_lfn ? e->long_name : e->name;
|
||||
if (!fat32_lfn_ends_with_ci(name, ".so")) return 0;
|
||||
|
||||
if (ctx->count < ctx->max_count) {
|
||||
ctx->count++;
|
||||
} else {
|
||||
ctx->error = 1; /* 超过上限 */
|
||||
return 1; /* 停止遍历 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 单文件推送回调上下文(用于流式推送所有 .so 到 guest) */
|
||||
typedef struct {
|
||||
const char *guest_path; /* guest 端目录绝对路径 */
|
||||
u32 pushed; /* 已推送文件数 */
|
||||
u32 failed; /* 推送失败文件数 */
|
||||
} lxc_push_ctx;
|
||||
|
||||
/* f32_list_dir 回调:流式推送每个 .so 到 guest */
|
||||
static int lxc_push_so_cb(const f32_entry *e, void *user_data) {
|
||||
lxc_push_ctx *ctx = (lxc_push_ctx *)user_data;
|
||||
if (!e || !ctx) return 0;
|
||||
if (e->is_dir) return 0;
|
||||
|
||||
const char *name = e->has_lfn ? e->long_name : e->name;
|
||||
if (!fat32_lfn_ends_with_ci(name, ".so")) return 0;
|
||||
|
||||
/* 构造 guest 端路径: <guest_path>/<filename> */
|
||||
char linux_path[256];
|
||||
u32 pl = 0;
|
||||
while (ctx->guest_path[pl] && pl + 1 < sizeof(linux_path)) {
|
||||
linux_path[pl] = ctx->guest_path[pl];
|
||||
pl++;
|
||||
}
|
||||
if (pl + 1 >= sizeof(linux_path)) {
|
||||
ctx->failed++;
|
||||
return 0; /* 路径过长,跳过 */
|
||||
}
|
||||
if (linux_path[pl - 1] != '/') {
|
||||
linux_path[pl++] = '/';
|
||||
}
|
||||
/* 追加文件名 */
|
||||
u32 nl = 0;
|
||||
while (name[nl] && pl + 1 < sizeof(linux_path)) {
|
||||
linux_path[pl++] = name[nl++];
|
||||
}
|
||||
linux_path[pl] = 0;
|
||||
|
||||
log_info("[LNXC] pushing .so:");
|
||||
log_info(name);
|
||||
log_info(" -> ");
|
||||
log_info(linux_path);
|
||||
|
||||
int rc = lxc_push_fat32_file_streaming(e->clus, e->size, linux_path);
|
||||
if (rc == 0) {
|
||||
ctx->pushed++;
|
||||
log_hex64("[LNXC] push ok size=", e->size);
|
||||
} else {
|
||||
ctx->failed++;
|
||||
log_warn("[LNXC] push failed");
|
||||
log_hex64("[LNXC] push rc=", (u64)(i64)rc);
|
||||
}
|
||||
return 0; /* 继续遍历下一个 */
|
||||
}
|
||||
|
||||
/* 同步 FAT32 lib 目录下所有 .so 到 Linux guest。
|
||||
* 调用时机:linux_compat_init 成功后。失败不阻断启动。
|
||||
* guest_lib_path : guest 端库目录路径(如 "/usr/lib/deshab")
|
||||
* 返回 0 成功,负值失败。 */
|
||||
int lxc_sync_lib_dir(const char *guest_lib_path) {
|
||||
if (!guest_lib_path) return -1;
|
||||
if (!lxc_is_available()) {
|
||||
log_warn("[LNXC] sync_lib_dir: service not available");
|
||||
return -1;
|
||||
}
|
||||
int rc = lxc_f32_ensure();
|
||||
if (rc != 0) {
|
||||
log_warn("[LNXC] sync_lib_dir: FAT32 not available");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 定位 lib 目录簇(lib 在 FAT32 根目录下,不是 SYSTEM/lib!) */
|
||||
u32 lib_clus = 0;
|
||||
if (f32_find_path_dir_lfn(g_compat_lib_path, &lib_clus) != 0) {
|
||||
log_warn("[LNXC] sync_lib_dir: lib dir not found:");
|
||||
log_warn(g_compat_lib_path);
|
||||
return -2; /* 目录不存在(容忍:可能是首次启动未放库) */
|
||||
}
|
||||
log_info("[LNXC] sync_lib_dir: lib dir found");
|
||||
log_hex64("[LNXC] lib_clus=", lib_clus);
|
||||
|
||||
/* 先枚举确认有 .so 文件(用于日志) */
|
||||
lxc_so_collect_ctx collect = {0, 256, 0};
|
||||
f32_list_dir(lib_clus, lxc_so_collect_cb, &collect);
|
||||
if (collect.error) {
|
||||
log_warn("[LNXC] sync_lib_dir: too many .so files, capped at 256");
|
||||
}
|
||||
log_hex64("[LNXC] sync_lib_dir: .so count=", collect.count);
|
||||
if (collect.count == 0) {
|
||||
log_info("[LNXC] sync_lib_dir: no .so files, nothing to push");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 流式推送每个 .so */
|
||||
lxc_push_ctx pctx;
|
||||
pctx.guest_path = guest_lib_path;
|
||||
pctx.pushed = 0;
|
||||
pctx.failed = 0;
|
||||
f32_list_dir(lib_clus, lxc_push_so_cb, &pctx);
|
||||
|
||||
log_hex64("[LNXC] sync_lib_dir: pushed=", pctx.pushed);
|
||||
log_hex64("[LNXC] sync_lib_dir: failed=", pctx.failed);
|
||||
return (pctx.failed == 0) ? 0 : -3;
|
||||
}
|
||||
|
||||
/* ===== VSCode Phase 5: VSCode tarball → guest /opt/vscode 安装 =====
|
||||
*
|
||||
* tarball 作为 Limine boot module(路径含 "vscode")随镜像预加载到主机
|
||||
* 内存;本函数把它流式推入 guest /tmp/vscode.tar.gz,再在 guest 内解包到
|
||||
* /opt/vscode(/opt 位于 extra-rootfs overlay,持久可写,二次启动经
|
||||
* 标记探测直接跳过)。 */
|
||||
|
||||
/* guest 内文件可执行性探测:同步 exec /bin/test -e <path>。
|
||||
* 返回 1 = 存在,0 = 不存在或探测失败。 */
|
||||
static int lxc_guest_path_exists(const char *path) {
|
||||
const char *argv[3];
|
||||
argv[0] = "test";
|
||||
argv[1] = "-e";
|
||||
argv[2] = path;
|
||||
u64 exit_code = 1;
|
||||
int rc = lxc_exec("/bin/test", 3, argv, 0, 0, 0, &exit_code);
|
||||
return (rc == 0 && exit_code == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
int lxc_vscode_install(void) {
|
||||
if (!lxc_is_available()) {
|
||||
log_warn("[LNXC] vscode install: service not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 1. 已安装标记:overlay 持久化后二次启动直接跳过 */
|
||||
if (lxc_guest_path_exists("/opt/vscode/bin/code")) {
|
||||
log_info("[LNXC] vscode: already installed, skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 2. 查找 tarball boot module(缺失属正常——未投放则跳过安装) */
|
||||
u64 tar_size = 0;
|
||||
const u8 *tar = (const u8 *)linux_find_vscode_module(&tar_size);
|
||||
if (!tar || tar_size == 0) {
|
||||
return -11;
|
||||
}
|
||||
|
||||
log_info("[LNXC] vscode: streaming tarball to guest /tmp ...");
|
||||
log_hex64("[LNXC] vscode: tarball size=", tar_size);
|
||||
|
||||
/* 3. 流式推送:module 内存 → /tmp/vscode.tar.gz(payload pool 分块) */
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
u64 off = 0;
|
||||
while (off < tar_size) {
|
||||
u64 chunk = tar_size - off;
|
||||
if (chunk > pool_cap) chunk = pool_cap;
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write("/tmp/vscode.tar.gz", off,
|
||||
(const char *)tar + off, chunk, &written);
|
||||
if (rc != 0 || written != chunk) {
|
||||
log_warn("[LNXC] vscode: tarball push failed");
|
||||
log_hex64("[LNXC] vscode: push off=", off);
|
||||
return -12;
|
||||
}
|
||||
off += written;
|
||||
}
|
||||
log_info("[LNXC] vscode: tarball pushed ok");
|
||||
|
||||
/* 4. mkdir -p /opt/vscode */
|
||||
{
|
||||
const char *argv[3];
|
||||
argv[0] = "mkdir";
|
||||
argv[1] = "-p";
|
||||
argv[2] = "/opt/vscode";
|
||||
u64 ec = 1;
|
||||
int rc = lxc_exec("/bin/mkdir", 3, argv, 0, 0, 0, &ec);
|
||||
if (rc != 0 || ec != 0) {
|
||||
log_warn("[LNXC] vscode: mkdir /opt/vscode failed");
|
||||
return -13;
|
||||
}
|
||||
}
|
||||
|
||||
/* 5. 解包:VSCode 官方 tarball 顶层为 VSCode-linux-x64/,
|
||||
* --strip-components=1 把内容直接落入 /opt/vscode;
|
||||
* 若 tarball 是平铺结构(无顶层目录)则回退不解层重试。 */
|
||||
int unpacked = 0;
|
||||
{
|
||||
const char *argv[7];
|
||||
argv[0] = "tar";
|
||||
argv[1] = "-xzf";
|
||||
argv[2] = "/tmp/vscode.tar.gz";
|
||||
argv[3] = "-C";
|
||||
argv[4] = "/opt/vscode";
|
||||
argv[5] = "--strip-components=1";
|
||||
argv[6] = 0;
|
||||
u64 ec = 1;
|
||||
int rc = lxc_exec("/bin/tar", 6, argv, 0, 0, 0, &ec);
|
||||
if (rc == 0 && ec == 0) unpacked = 1;
|
||||
}
|
||||
if (!unpacked) {
|
||||
log_warn("[LNXC] vscode: strip-components extract failed, trying flat");
|
||||
const char *argv[5];
|
||||
argv[0] = "tar";
|
||||
argv[1] = "-xzf";
|
||||
argv[2] = "/tmp/vscode.tar.gz";
|
||||
argv[3] = "-C";
|
||||
argv[4] = "/opt/vscode";
|
||||
u64 ec = 1;
|
||||
int rc = lxc_exec("/bin/tar", 5, argv, 0, 0, 0, &ec);
|
||||
if (rc == 0 && ec == 0) unpacked = 1;
|
||||
}
|
||||
|
||||
/* 6. 清理 guest 端 tarball(成败都清,/tmp 是 tmpfs 占 guest 内存) */
|
||||
{
|
||||
const char *argv[3];
|
||||
argv[0] = "rm";
|
||||
argv[1] = "-f";
|
||||
argv[2] = "/tmp/vscode.tar.gz";
|
||||
u64 ec = 0;
|
||||
lxc_exec("/bin/rm", 3, argv, 0, 0, 0, &ec);
|
||||
}
|
||||
|
||||
if (!unpacked) {
|
||||
log_warn("[LNXC] vscode: extract failed");
|
||||
return -13;
|
||||
}
|
||||
|
||||
/* 7. 验证:/opt/vscode/bin/code 存在即可启动 */
|
||||
if (!lxc_guest_path_exists("/opt/vscode/bin/code")) {
|
||||
log_warn("[LNXC] vscode: verify failed (bin/code missing)");
|
||||
return -14;
|
||||
}
|
||||
|
||||
log_info("[LNXC] vscode: install ok (/opt/vscode/bin/code)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
vmm/linux_compat.o: vmm\linux_compat.c include\utsm\linux_compat.h \
|
||||
include\utsm\types.h include\utsm\linux_resume.h \
|
||||
include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h include\utsm\vmm.h \
|
||||
include\utsm\log.h include\utsm\block.h vmm\..\..\tools\fat32_io.h
|
||||
include\utsm\linux_loader.h include\utsm\ipc_shm.h \
|
||||
..\utsm-ipc\ipc_proto.h include\utsm\vmm.h include\utsm\virtio_mmio.h \
|
||||
include\utsm\log.h include\utsm\block.h vmm\..\..\tools\fat32_io.h \
|
||||
vmm\..\..\tools\fat32_lfn.h
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_resume.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\block.h:
|
||||
vmm\..\..\tools\fat32_io.h:
|
||||
vmm\..\..\tools\fat32_lfn.h:
|
||||
|
||||
+171
-13
@@ -49,6 +49,11 @@ static u64 str_len(const char *s) {
|
||||
static struct linux_guest_info g_guest;
|
||||
static const char *g_default_cmdline = LINUX_DEFAULT_CMDLINE;
|
||||
|
||||
/* VSCode Phase 3: host virtual address of the graphics surface pool.
|
||||
* The virtio-gpu 2D backend composites the scanout into this buffer; the
|
||||
* desktop reads it to blit guest graphics onto the IDE host area. 0 = unused. */
|
||||
static void *g_surface_vaddr = 0;
|
||||
|
||||
/* ===== Guest page table helpers =====
|
||||
*
|
||||
* Build identity-mapped 4-level page tables for the guest.
|
||||
@@ -220,9 +225,19 @@ static int setup_e820(void *bootparams_virt) {
|
||||
table[n].type = E820_TYPE_RESERVED;
|
||||
n++;
|
||||
|
||||
/* Entry 5: 0x04100000-0x0FFFFFFF: usable RAM (general RAM, ~188MB) */
|
||||
table[n].addr = 0x04100000;
|
||||
table[n].size = 0x0BF00000; /* 191MB */
|
||||
/* Entry 5: 0x04100000-0x04FFFFFF: reserved (Graphics surface pool, 16MB)
|
||||
* VSCode integration Phase 1: separate DMA region EPT-mapped into the
|
||||
* guest; Linux user-space mmaps it via /dev/utsm as the scanout buffer. */
|
||||
table[n].addr = LINUX_GUEST_SURFACE_GPA;
|
||||
table[n].size = LINUX_GUEST_SURFACE_SIZE; /* 16MB */
|
||||
table[n].type = E820_TYPE_RESERVED;
|
||||
n++;
|
||||
|
||||
/* Entry 6: 0x05000000-0x5FFFFFFF: usable RAM (general RAM, ~1.4GB)
|
||||
* VSCode integration Phase 1: expanded from 191MB to 1.4GB to fit
|
||||
* VSCode + Electron + GUI stack (Xorg/modesetting + GTK + mesa). */
|
||||
table[n].addr = LINUX_GUEST_RAM_GPA;
|
||||
table[n].size = LINUX_GUEST_RAM_SIZE; /* 1.4GB */
|
||||
table[n].type = E820_TYPE_RAM;
|
||||
n++;
|
||||
|
||||
@@ -349,6 +364,86 @@ void *linux_find_rootfs_module(u64 *size_out) {
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== extra rootfs module finder =====
|
||||
*
|
||||
* VSCode Phase 4:在 Limine boot module 中查找持久 rw 卷镜像
|
||||
* (路径含 "extra",如 linux-extra-rootfs.img)。
|
||||
* 通过 virtio-blk slot6(可写)暴露给 Linux guest 作为 /dev/vdc。 */
|
||||
void *linux_find_extra_rootfs_module(u64 *size_out) {
|
||||
if (!g_module_request.response) return (void *)0;
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
/* 匹配 "extra"(如 linux-extra-rootfs.img) */
|
||||
if ((s[0] == 'e' || s[0] == 'E') &&
|
||||
(s[1] == 'x' || s[1] == 'X') &&
|
||||
(s[2] == 't' || s[2] == 'T') &&
|
||||
(s[3] == 'r' || s[3] == 'R') &&
|
||||
(s[4] == 'a' || s[4] == 'A')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[LINUX] found extra-rootfs module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[LINUX] extra-rootfs size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_warn("[LINUX] no extra-rootfs module found");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== VSCode tarball module finder =====
|
||||
*
|
||||
* VSCode Phase 5:在 Limine boot module 中查找 VSCode tarball
|
||||
* (路径含 "vscode",如 linux-vscode.tar.gz)。
|
||||
* 由 lxc_vscode_install() 流式推入 guest 并解压到 /opt/vscode。
|
||||
* 模块缺失属正常情况(未投放 tarball),返回 NULL 由调用方跳过。 */
|
||||
void *linux_find_vscode_module(u64 *size_out) {
|
||||
if (!g_module_request.response) return (void *)0;
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
/* 匹配 "vscode"(如 linux-vscode.tar.gz) */
|
||||
if ((s[0] == 'v' || s[0] == 'V') &&
|
||||
(s[1] == 's' || s[1] == 'S') &&
|
||||
(s[2] == 'c' || s[2] == 'C') &&
|
||||
(s[3] == 'o' || s[3] == 'O') &&
|
||||
(s[4] == 'd' || s[4] == 'D') &&
|
||||
(s[5] == 'e' || s[5] == 'E')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[LINUX] found vscode tarball module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[LINUX] vscode tarball size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_info("[LINUX] no vscode tarball module (install skipped)");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== bzImage parser ===== */
|
||||
|
||||
int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
@@ -552,16 +647,23 @@ int linux_loader_init(void) {
|
||||
return -8;
|
||||
}
|
||||
|
||||
/* General RAM: 64MB for Linux, mapped at GPA 0x05000000 (after IPC region) */
|
||||
u64 ram_size = 64 * 1024 * 1024;
|
||||
/* General RAM: VSCode integration Phase 1 expansion.
|
||||
* 1.4GB (LINUX_GUEST_RAM_SIZE) accommodates VSCode + Electron + GUI stack
|
||||
* (Xorg/modesetting + GTK + mesa softpipe). Falls back to 1GB / 512MB if
|
||||
* the host cannot allocate the full amount; 512MB is the floor for the
|
||||
* Xfbdev + xterm MVP (Phase 3). Original allocation was 64MB. */
|
||||
u64 ram_size = LINUX_GUEST_RAM_SIZE; /* 1.4GB */
|
||||
dkm_dma_buffer ram_buf;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc guest RAM (64MB)");
|
||||
/* Try smaller: 32MB */
|
||||
ram_size = 32 * 1024 * 1024;
|
||||
log_warn("[LINUX] failed to alloc guest RAM (1.4GB), trying 1GB");
|
||||
ram_size = 1ULL * 1024 * 1024 * 1024; /* 1GB */
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc guest RAM (32MB)");
|
||||
return -9;
|
||||
log_warn("[LINUX] failed to alloc guest RAM (1GB), trying 512MB");
|
||||
ram_size = 512 * 1024 * 1024; /* 512MB floor for Phase 3 MVP */
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc guest RAM (512MB minimum)");
|
||||
return -9;
|
||||
}
|
||||
}
|
||||
}
|
||||
log_hex64("[LINUX] guest RAM HPA=", ram_buf.phys);
|
||||
@@ -680,13 +782,14 @@ int linux_loader_init(void) {
|
||||
return -15;
|
||||
}
|
||||
|
||||
/* General RAM: map the allocated RAM at GPA 0x05000000 (after IPC region).
|
||||
* GPA layout (matches e820):
|
||||
/* General RAM: map the allocated RAM at GPA 0x05000000 (after surface pool).
|
||||
* GPA layout (matches e820, VSCode integration Phase 1):
|
||||
* 0x00400000-0x009FFFFF: fixed regions (boot_params, GDT, pgt, stack, cmdline)
|
||||
* 0x01000000-0x02FFFFFF: kernel
|
||||
* 0x03000000-0x03FFFFFF: initrd (if any)
|
||||
* 0x04000000-0x040FFFFF: IPC shared memory (mapped by ipc_shm_init)
|
||||
* 0x05000000-0x08FFFFFF: general RAM (64MB)
|
||||
* 0x04100000-0x04FFFFFF: Graphics surface pool (mapped below)
|
||||
* 0x05000000-0x5FFFFFFF: general RAM (~1.4GB, expanded Phase 1)
|
||||
* Linux sees this as usable RAM in e820. */
|
||||
u64 ram_gpa = LINUX_GUEST_RAM_GPA;
|
||||
if (ept_map_range(ram_gpa, ram_buf.phys, ram_size, EPT_RWX) != 0) {
|
||||
@@ -695,6 +798,41 @@ int linux_loader_init(void) {
|
||||
}
|
||||
log_hex64("[LINUX] guest RAM mapped GPA=", ram_gpa);
|
||||
|
||||
/* VSCode integration Phase 1: allocate graphics surface pool and EPT-map
|
||||
* it into the Linux guest at LINUX_GUEST_SURFACE_GPA. Linux user-space
|
||||
* obtains a writable pointer to this region via UTSM_HCALL_SURFACE_INFO +
|
||||
* /dev/utsm mmap; UTSM reads it to blit guest graphics to the Deshab
|
||||
* framebuffer (Phase 3 virtio-gpu shadow scanout). Non-fatal on failure:
|
||||
* Linux guest still runs, VSCode integration phases 2+ will degrade. */
|
||||
{
|
||||
dkm_dma_buffer surface_buf;
|
||||
u64 surface_size = LINUX_GUEST_SURFACE_SIZE; /* 16MB */
|
||||
if (dma_alloc_pages(surface_size / 4096, 2 * 1024 * 1024, 0, &surface_buf) != 0) {
|
||||
log_warn("[LINUX] failed to alloc surface pool (16MB) — VSCode integration degraded");
|
||||
g_guest.surface_hpa = 0;
|
||||
g_guest.surface_gpa = 0;
|
||||
g_guest.surface_size = 0;
|
||||
} else if (ept_map_range(LINUX_GUEST_SURFACE_GPA, surface_buf.phys,
|
||||
surface_size, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_warn("[LINUX] EPT map surface pool failed — VSCode integration degraded");
|
||||
g_guest.surface_hpa = 0;
|
||||
g_guest.surface_gpa = 0;
|
||||
g_guest.surface_size = 0;
|
||||
} else {
|
||||
/* Zero the surface pool so guest reads deterministic data before
|
||||
* the X server writes a real scanout. */
|
||||
u8 *surface_virt = (u8 *)surface_buf.virt;
|
||||
for (u64 i = 0; i < surface_size; i++) surface_virt[i] = 0;
|
||||
g_guest.surface_hpa = surface_buf.phys;
|
||||
g_guest.surface_gpa = LINUX_GUEST_SURFACE_GPA;
|
||||
g_guest.surface_size = surface_size;
|
||||
g_surface_vaddr = surface_buf.virt; /* VSCode Phase 3: for virtio-gpu + desktop blit */
|
||||
log_hex64("[LINUX] surface pool HPA=", surface_buf.phys);
|
||||
log_hex64("[LINUX] surface pool GPA=", g_guest.surface_gpa);
|
||||
log_hex64("[LINUX] surface pool size=", surface_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Identity-map the first 1MB (BIOS area, needed by some Linux code paths) */
|
||||
if (ept_identity_map(0, 0x100000, EPT_RWX) != 0) {
|
||||
log_warn("[LINUX] EPT identity map first 1MB failed (non-critical)");
|
||||
@@ -715,3 +853,23 @@ int linux_loader_init(void) {
|
||||
const struct linux_guest_info *linux_get_guest_info(void) {
|
||||
return g_guest.loaded ? &g_guest : (struct linux_guest_info *)0;
|
||||
}
|
||||
|
||||
/* VSCode integration Phase 1: query graphics surface pool.
|
||||
* Returns 0 on success (surface pool allocated and mapped), -1 if not
|
||||
* initialized. Caller may pass NULL for any output it doesn't need. */
|
||||
int linux_get_surface_info(u64 *hpa_out, u64 *gpa_out, u64 *size_out) {
|
||||
if (!g_guest.loaded || g_guest.surface_hpa == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (hpa_out) *hpa_out = g_guest.surface_hpa;
|
||||
if (gpa_out) *gpa_out = g_guest.surface_gpa;
|
||||
if (size_out) *size_out = g_guest.surface_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* VSCode Phase 3: return host virtual address of the surface pool.
|
||||
* The virtio-gpu 2D backend composites the scanout here; the desktop reads it
|
||||
* for blitting to the framebuffer. Returns NULL if the pool is not allocated. */
|
||||
void *linux_get_surface_vaddr(void) {
|
||||
return g_surface_vaddr;
|
||||
}
|
||||
|
||||
+199
-2
@@ -389,8 +389,10 @@ static void rootfs_blk_queue_notify(u32 queue_idx) {
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_BLOCK, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
/* 必须按 gpa_base 定位:slot0 SATA blk 与本后端 device_id 同为 2,
|
||||
* virtio_queue_get_ptrs(VIRTIO_ID_BLOCK) 只会命中第一个(slot0)。 */
|
||||
if (virtio_queue_get_ptrs_by_gpa(VIRTIO_MMIO_ROOTFS_GPA, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -448,3 +450,198 @@ void virtio_rootfs_blk_init(void) {
|
||||
|
||||
virtio_mmio_register(&g_rootfs_blk_backend);
|
||||
}
|
||||
|
||||
/* ===== Extra rootfs virtio-blk 后端(VSCode Phase 4,可写持久卷) =====
|
||||
*
|
||||
* 第三个 virtio-blk 设备,后端为内存中的 linux-extra-rootfs.img
|
||||
* (Limine boot module,路径含 "extra")。guest 看到 /dev/vdc,
|
||||
* 用作 overlayfs 的 rw 上层或 VSCode 数据卷。
|
||||
*
|
||||
* 与 rootfs(vdb 只读)不同,本后端支持 VIRTIO_BLK_T_OUT 真正写入内存
|
||||
* (易失:重启丢失;Phase 7 再做写回 FAT32 持久化)。
|
||||
*
|
||||
* 无 extra-rootfs module 时安全不注册(guest cmdline 无 slot6 参数)。 */
|
||||
|
||||
static u8 *g_xrootfs_data;
|
||||
static u64 g_xrootfs_size;
|
||||
static u64 g_xrootfs_capacity;
|
||||
static u16 g_xrootfs_last_avail;
|
||||
|
||||
static u32 xrootfs_blk_read_config(u32 offset, int width) {
|
||||
(void)width;
|
||||
struct virtio_blk_config cfg;
|
||||
u8 *c = (u8 *)&cfg;
|
||||
for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0;
|
||||
cfg.capacity = g_xrootfs_capacity;
|
||||
cfg.size_max = 128;
|
||||
cfg.seg_max = 32;
|
||||
cfg.blk_size = 512;
|
||||
if (offset < sizeof(cfg)) {
|
||||
u32 val = 0;
|
||||
rf_memcpy(&val, c + offset, (offset + 4 <= sizeof(cfg)) ? 4 : (sizeof(cfg) - offset));
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 xrootfs_blk_handle_chain(struct virtq_desc *desc, u16 head, u32 qnum) {
|
||||
(void)qnum;
|
||||
struct virtio_blk_outhdr hdr;
|
||||
u8 *hptr = (u8 *)virtio_gpa_to_host(desc[head].addr);
|
||||
if (!hptr) return 0;
|
||||
rf_memcpy(&hdr, hptr, sizeof(hdr));
|
||||
|
||||
u32 type = hdr.type;
|
||||
u64 sector = hdr.sector;
|
||||
|
||||
u16 data_idx = 0xFFFF;
|
||||
u16 status_idx = 0xFFFF;
|
||||
if (desc[head].flags & VIRTQ_DESC_F_NEXT) {
|
||||
data_idx = desc[head].next;
|
||||
u16 cur = data_idx;
|
||||
int hops = 0;
|
||||
while ((desc[cur].flags & VIRTQ_DESC_F_NEXT) && hops < 16) {
|
||||
cur = desc[cur].next;
|
||||
hops++;
|
||||
}
|
||||
status_idx = cur;
|
||||
}
|
||||
|
||||
u8 *status = (u8 *)0;
|
||||
if (status_idx != 0xFFFF && (desc[status_idx].flags & VIRTQ_DESC_F_WRITE)) {
|
||||
status = (u8 *)virtio_gpa_to_host(desc[status_idx].addr);
|
||||
}
|
||||
|
||||
u32 bytes_done = 0;
|
||||
u8 result = VIRTIO_BLK_S_OK;
|
||||
|
||||
switch (type) {
|
||||
case VIRTIO_BLK_T_IN: { /* 读:镜像内存 → guest */
|
||||
if (data_idx == 0xFFFF || !g_xrootfs_data) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
u8 *dbuf = (u8 *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
u32 dlen = desc[data_idx].len;
|
||||
if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; }
|
||||
u64 offset = sector * 512ULL;
|
||||
u64 avail = (offset < g_xrootfs_size) ? (g_xrootfs_size - offset) : 0;
|
||||
u32 copied = 0;
|
||||
if (avail > 0) {
|
||||
u32 n = (dlen < avail) ? dlen : (u32)avail;
|
||||
rf_memcpy(dbuf, g_xrootfs_data + offset, n);
|
||||
copied = n;
|
||||
}
|
||||
/* 越界部分填零 */
|
||||
while (copied < dlen) dbuf[copied++] = 0;
|
||||
bytes_done = dlen;
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_OUT: { /* 写:guest → 镜像内存(可写卷) */
|
||||
if (data_idx == 0xFFFF || !g_xrootfs_data) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
const u8 *dbuf = (const u8 *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
u32 dlen = desc[data_idx].len;
|
||||
if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; }
|
||||
u64 offset = sector * 512ULL;
|
||||
if (offset + dlen > g_xrootfs_size) {
|
||||
/* 越界写:只写卷内部分,其余丢弃 */
|
||||
u64 avail = (offset < g_xrootfs_size) ? (g_xrootfs_size - offset) : 0;
|
||||
if (avail > 0) rf_memcpy(g_xrootfs_data + offset, dbuf, avail);
|
||||
} else {
|
||||
rf_memcpy(g_xrootfs_data + offset, dbuf, dlen);
|
||||
}
|
||||
bytes_done = 0; /* 写操作 used.len=0 */
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_GET_ID:
|
||||
if (data_idx != 0xFFFF) {
|
||||
char *idbuf = (char *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
if (idbuf) {
|
||||
const char *id = "DESHAB-EXTRA-ROOTFS";
|
||||
u32 i = 0;
|
||||
for (; id[i] && i < desc[data_idx].len - 1; i++) idbuf[i] = id[i];
|
||||
idbuf[i] = 0;
|
||||
bytes_done = i + 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case VIRTIO_BLK_T_FLUSH:
|
||||
result = VIRTIO_BLK_S_OK;
|
||||
break;
|
||||
default:
|
||||
result = VIRTIO_BLK_S_UNSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status) *status = result;
|
||||
return bytes_done;
|
||||
}
|
||||
|
||||
static void xrootfs_blk_queue_notify(u32 queue_idx) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
|
||||
/* 按 gpa_base 定位(三个 blk 后端 device_id 同为 2) */
|
||||
if (virtio_queue_get_ptrs_by_gpa(VIRTIO_MMIO_EXTRA_ROOTFS_GPA, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
u16 cur = g_xrootfs_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u32 len = xrootfs_blk_handle_chain(desc, head, qnum);
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = len;
|
||||
used_idx++;
|
||||
cur++;
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_xrootfs_last_avail = cur;
|
||||
}
|
||||
|
||||
static void xrootfs_blk_reset(void) {
|
||||
g_xrootfs_last_avail = 0;
|
||||
}
|
||||
|
||||
static struct virtio_backend g_xrootfs_blk_backend = {
|
||||
.device_id = VIRTIO_ID_BLOCK,
|
||||
.gpa_base = VIRTIO_MMIO_EXTRA_ROOTFS_GPA,
|
||||
.irq = 11, /* guest ISA IRQ11(cmdline :11,vector 0x3B) */
|
||||
.num_queues = 1,
|
||||
.queue_size = 128,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1) |
|
||||
(1ULL << VIRTIO_BLK_F_SIZE_MAX) |
|
||||
(1ULL << VIRTIO_BLK_F_SEG_MAX) |
|
||||
(1ULL << VIRTIO_BLK_F_BLK_SIZE) |
|
||||
(1ULL << VIRTIO_BLK_F_FLUSH),
|
||||
.config_len = sizeof(struct virtio_blk_config),
|
||||
.read_config = xrootfs_blk_read_config,
|
||||
.queue_notify = xrootfs_blk_queue_notify,
|
||||
.reset = xrootfs_blk_reset,
|
||||
};
|
||||
|
||||
void virtio_extra_rootfs_blk_init(void) {
|
||||
u64 size = 0;
|
||||
void *img = linux_find_extra_rootfs_module(&size);
|
||||
if (!img || size == 0) {
|
||||
log_warn("[VBLK] no extra-rootfs module, slot6 backend not registered");
|
||||
return;
|
||||
}
|
||||
|
||||
g_xrootfs_data = (u8 *)img;
|
||||
g_xrootfs_size = size;
|
||||
g_xrootfs_capacity = size / 512;
|
||||
if (size % 512) g_xrootfs_capacity++;
|
||||
|
||||
log_info("[VBLK] extra-rootfs-blk backend bound (rw)");
|
||||
log_hex64("[VBLK] extra-rootfs size=", size);
|
||||
|
||||
virtio_mmio_register(&g_xrootfs_blk_backend);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,918 @@
|
||||
/* 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);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
vmm/virtio_gpu.o: vmm\virtio_gpu.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
@@ -0,0 +1,392 @@
|
||||
/* virtio_input.c — UTSM virtio-input backend (keyboard + mouse).
|
||||
*
|
||||
* VSCode integration Phase 2: forward Deshab desktop input events to the
|
||||
* Linux guest via two virtio-input devices (device_id=18):
|
||||
* slot 3 @ 0xF4003000 IRQ8 — keyboard (evdev /dev/input/event0)
|
||||
* slot 4 @ 0xF4004000 IRQ9 — mouse (evdev /dev/input/event1)
|
||||
*
|
||||
* Data flow:
|
||||
* 1. Deshab desktop (DSK) calls virtio_input_push_keyboard/push_mouse_* on
|
||||
* a PS/2 scancode / mouse packet. The event lands in a pending ring.
|
||||
* 2. vmexit_before_resume() calls virtio_input_poll() every ~1ms.
|
||||
* 3. poll drains pending events into guest eventq buffers the driver posted
|
||||
* (device→driver direction), bumps the used ring, and raises IRQ8/IRQ9.
|
||||
* 4. Linux evdev delivers the events to userspace (Xorg/libinput).
|
||||
*
|
||||
* The statusq (queue 1, driver→device) is consumed but ignored — we only
|
||||
* acknowledge status events (e.g. keyboard LED state) with len=0.
|
||||
*
|
||||
* Config space implements the select/subsel state machine per virtio-input
|
||||
* spec: driver writes select+subsel, then reads the 128-byte payload.
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* ===== Config space layout (virtio-input spec §5) =====
|
||||
* [0] select — which config item
|
||||
* [1] subsel — sub-selector (e.g. event type for EV_BITS)
|
||||
* [2] size — valid payload bytes (0..128)
|
||||
* [3..7] reserved
|
||||
* [8..135] payload (string or bitmap) */
|
||||
#define VIO_CFG_LEN 136
|
||||
#define VIO_CFG_PAYLOAD 128
|
||||
|
||||
/* select values (virtio_input_config select field) */
|
||||
#define VIO_CFG_ID_NAME 1
|
||||
#define VIO_CFG_ID_SERIAL 2
|
||||
#define VIO_CFG_ID_DEVIDS 3
|
||||
#define VIO_CFG_PROP_BITS 4
|
||||
#define VIO_CFG_EV_BITS 5
|
||||
|
||||
/* Pending event ring depth. 256 events is generous headroom over the
|
||||
* ~1kHz poll rate — if the guest hasn't posted eventq buffers for 256ms
|
||||
* of input, events start dropping (oldest first). */
|
||||
#define VIO_PEND_MAX 256
|
||||
|
||||
struct virtio_input_dev {
|
||||
u64 gpa_base;
|
||||
u32 irq;
|
||||
const char *name;
|
||||
|
||||
/* Config select/subsel state (written by guest, read back as payload) */
|
||||
u8 cfg_select;
|
||||
u8 cfg_subsel;
|
||||
u8 cfg_buf[VIO_CFG_LEN];
|
||||
|
||||
/* Pending event ring (SPSC): push_* produces, poll consumes.
|
||||
* No lock — UTSM is cooperative: push runs in DSK context (guest
|
||||
* parked), poll runs in vmexit_before_resume (just before resume).
|
||||
* They never execute concurrently. */
|
||||
struct virtio_input_event pending[VIO_PEND_MAX];
|
||||
u32 pend_head;
|
||||
u32 pend_tail;
|
||||
u32 pend_count;
|
||||
|
||||
/* Per-queue avail cursor (last processed avail->idx). */
|
||||
u16 last_avail_event;
|
||||
u16 last_avail_status;
|
||||
|
||||
struct virtio_backend backend;
|
||||
};
|
||||
|
||||
static struct virtio_input_dev g_kbd;
|
||||
static struct virtio_input_dev g_mouse;
|
||||
|
||||
/* ===== Config space rebuild =====
|
||||
* Called whenever the guest writes select/subsel. Fills cfg_buf with the
|
||||
* full 136-byte config image (select, subsel, size, reserved, payload). */
|
||||
static void input_rebuild_cfg(struct virtio_input_dev *dev) {
|
||||
u8 *buf = dev->cfg_buf;
|
||||
buf[0] = dev->cfg_select;
|
||||
buf[1] = dev->cfg_subsel;
|
||||
buf[2] = 0; /* size, set below */
|
||||
for (int i = 3; i < 8; i++) buf[i] = 0;
|
||||
|
||||
u8 *payload = buf + 8;
|
||||
for (int i = 0; i < VIO_CFG_PAYLOAD; i++) payload[i] = 0;
|
||||
u8 size = 0;
|
||||
|
||||
switch (dev->cfg_select) {
|
||||
case VIO_CFG_ID_NAME: {
|
||||
const char *s = dev->name;
|
||||
u32 i = 0;
|
||||
while (s[i] && i < VIO_CFG_PAYLOAD) { payload[i] = (u8)s[i]; i++; }
|
||||
size = (u8)i;
|
||||
break;
|
||||
}
|
||||
case VIO_CFG_ID_SERIAL:
|
||||
size = 0; /* no serial */
|
||||
break;
|
||||
case VIO_CFG_ID_DEVIDS: {
|
||||
/* Bitmap of supported event types (bit N = EV_TYPE N).
|
||||
* keyboard: EV_KEY only; mouse: EV_KEY + EV_REL. */
|
||||
payload[VIO_INPUT_EV_KEY / 8] |= (u8)(1u << (VIO_INPUT_EV_KEY % 8));
|
||||
if (dev == &g_mouse) {
|
||||
payload[VIO_INPUT_EV_REL / 8] |= (u8)(1u << (VIO_INPUT_EV_REL % 8));
|
||||
}
|
||||
u32 max_ev = (dev == &g_mouse) ? VIO_INPUT_EV_REL : VIO_INPUT_EV_KEY;
|
||||
size = (u8)(max_ev / 8 + 1);
|
||||
break;
|
||||
}
|
||||
case VIO_CFG_PROP_BITS:
|
||||
size = 0;
|
||||
break;
|
||||
case VIO_CFG_EV_BITS: {
|
||||
/* subsel = event type; payload = bitmap of supported event codes. */
|
||||
u16 ev = dev->cfg_subsel;
|
||||
if (dev == &g_kbd && ev == VIO_INPUT_EV_KEY) {
|
||||
/* Keyboard: advertise keys 1..248 (ESCAPE..KEY_WWW). The driver
|
||||
* enumerates all set bits; only forwarded keys will ever arrive. */
|
||||
for (u32 i = 1; i <= 248; i++) {
|
||||
payload[i / 8] |= (u8)(1u << (i % 8));
|
||||
}
|
||||
size = 32; /* 256 bits = 32 bytes */
|
||||
} else if (dev == &g_mouse) {
|
||||
if (ev == VIO_INPUT_EV_KEY) {
|
||||
payload[VIO_INPUT_BTN_LEFT / 8] |= (u8)(1u << (VIO_INPUT_BTN_LEFT % 8));
|
||||
payload[VIO_INPUT_BTN_RIGHT / 8] |= (u8)(1u << (VIO_INPUT_BTN_RIGHT % 8));
|
||||
payload[VIO_INPUT_BTN_MIDDLE / 8] |= (u8)(1u << (VIO_INPUT_BTN_MIDDLE % 8));
|
||||
size = (u8)(VIO_INPUT_BTN_MIDDLE / 8 + 1);
|
||||
} else if (ev == VIO_INPUT_EV_REL) {
|
||||
payload[VIO_INPUT_REL_X / 8] |= (u8)(1u << (VIO_INPUT_REL_X % 8));
|
||||
payload[VIO_INPUT_REL_Y / 8] |= (u8)(1u << (VIO_INPUT_REL_Y % 8));
|
||||
size = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
size = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
buf[2] = size;
|
||||
}
|
||||
|
||||
/* Read width bytes from the config buffer at offset (little-endian u32). */
|
||||
static u32 input_read_config_at(struct virtio_input_dev *dev, u32 offset, int width) {
|
||||
if (offset >= VIO_CFG_LEN) return 0;
|
||||
u32 val = 0;
|
||||
for (int i = 0; i < width && offset + i < VIO_CFG_LEN; i++) {
|
||||
val |= (u32)dev->cfg_buf[offset + i] << (8 * i);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/* ===== Per-device thin wrappers (backend callbacks have no ctx) ===== */
|
||||
|
||||
static u32 kbd_read_config(u32 offset, int width) { return input_read_config_at(&g_kbd, offset, width); }
|
||||
static u32 mouse_read_config(u32 offset, int width) { return input_read_config_at(&g_mouse, offset, width); }
|
||||
|
||||
static void kbd_write_config(u32 offset, u32 value) {
|
||||
if (offset == 0) { g_kbd.cfg_select = (u8)value; input_rebuild_cfg(&g_kbd); }
|
||||
else if (offset == 1) { g_kbd.cfg_subsel = (u8)value; input_rebuild_cfg(&g_kbd); }
|
||||
}
|
||||
static void mouse_write_config(u32 offset, u32 value) {
|
||||
if (offset == 0) { g_mouse.cfg_select = (u8)value; input_rebuild_cfg(&g_mouse); }
|
||||
else if (offset == 1) { g_mouse.cfg_subsel = (u8)value; input_rebuild_cfg(&g_mouse); }
|
||||
}
|
||||
|
||||
static void input_reset_dev(struct virtio_input_dev *dev) {
|
||||
dev->cfg_select = 0;
|
||||
dev->cfg_subsel = 0;
|
||||
dev->pend_head = 0;
|
||||
dev->pend_tail = 0;
|
||||
dev->pend_count = 0;
|
||||
dev->last_avail_event = 0;
|
||||
dev->last_avail_status = 0;
|
||||
input_rebuild_cfg(dev);
|
||||
}
|
||||
static void kbd_reset(void) { input_reset_dev(&g_kbd); }
|
||||
static void mouse_reset(void) { input_reset_dev(&g_mouse); }
|
||||
|
||||
/* ===== Eventq drain (device → driver) =====
|
||||
* Walk the eventq avail ring: for each empty buffer the driver posted, pop
|
||||
* one pending event, write it into the buffer, complete the used entry.
|
||||
* Returns the number of buffers filled. */
|
||||
static int input_drain_eventq(struct virtio_input_dev *dev) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs_by_gpa(dev->gpa_base, 0, &desc, &avail, &used, &qnum) != 0)
|
||||
return 0;
|
||||
|
||||
int filled = 0;
|
||||
u16 cur = dev->last_avail_event;
|
||||
u16 used_idx = used->idx;
|
||||
int budget = 32; /* cap per poll to bound vmexit latency */
|
||||
|
||||
while (budget-- > 0 && dev->pend_count > 0) {
|
||||
if (cur == avail->idx) break; /* no empty buffers posted */
|
||||
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
if (head >= qnum) { cur++; continue; } /* bad index, skip */
|
||||
|
||||
struct virtq_desc *d = &desc[head];
|
||||
struct virtio_input_event *buf = (struct virtio_input_event *)virtio_gpa_to_host(d->addr);
|
||||
if (!buf || d->len < (u32)sizeof(struct virtio_input_event)) {
|
||||
/* buffer too small or unmapped: return it empty so guest recycles */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Pop one pending event into the guest buffer */
|
||||
u32 idx = dev->pend_tail;
|
||||
buf->type = dev->pending[idx].type;
|
||||
buf->code = dev->pending[idx].code;
|
||||
buf->value = dev->pending[idx].value;
|
||||
dev->pend_tail = (idx + 1) % VIO_PEND_MAX;
|
||||
dev->pend_count--;
|
||||
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = sizeof(struct virtio_input_event);
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
}
|
||||
|
||||
if (filled > 0) {
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
dev->last_avail_event = cur;
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
/* ===== Statusq drain (driver → device) =====
|
||||
* The driver occasionally posts status events (e.g. keyboard LED state).
|
||||
* We don't act on them — just complete the buffers with len=0 so the driver
|
||||
* can recycle them and the avail ring doesn't fill up. */
|
||||
static int input_drain_statusq(struct virtio_input_dev *dev) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs_by_gpa(dev->gpa_base, 1, &desc, &avail, &used, &qnum) != 0)
|
||||
return 0;
|
||||
|
||||
int filled = 0;
|
||||
u16 cur = dev->last_avail_status;
|
||||
u16 used_idx = used->idx;
|
||||
int budget = 8;
|
||||
|
||||
while (budget-- > 0 && cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
if (head < qnum) {
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
filled++;
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
|
||||
if (filled > 0) {
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
dev->last_avail_status = cur;
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
/* ===== Backend queue_notify callbacks =====
|
||||
* eventq notify (queue 0): the driver just posted empty buffers — try to
|
||||
* drain pending events now. IRQ is raised by the framework after notify.
|
||||
* statusq notify (queue 1): the driver sent a status event — consume it. */
|
||||
static void kbd_queue_notify(u32 queue_idx) {
|
||||
if (queue_idx == 0) {
|
||||
if (input_drain_eventq(&g_kbd) > 0)
|
||||
virtio_mmio_raise_irq_by_gpa(g_kbd.gpa_base);
|
||||
} else if (queue_idx == 1) {
|
||||
input_drain_statusq(&g_kbd);
|
||||
}
|
||||
}
|
||||
static void mouse_queue_notify(u32 queue_idx) {
|
||||
if (queue_idx == 0) {
|
||||
if (input_drain_eventq(&g_mouse) > 0)
|
||||
virtio_mmio_raise_irq_by_gpa(g_mouse.gpa_base);
|
||||
} else if (queue_idx == 1) {
|
||||
input_drain_statusq(&g_mouse);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Pending event push API (called by DSK / desktop input forwarding) ===== */
|
||||
|
||||
static void input_push(struct virtio_input_dev *dev, u16 type, u16 code, u32 value) {
|
||||
if (dev->pend_count >= VIO_PEND_MAX) {
|
||||
/* Ring full: drop the oldest event to make room for the newest.
|
||||
* Dropping old (not new) preserves the most recent input state,
|
||||
* which is what matters for interactive feel. */
|
||||
dev->pend_tail = (dev->pend_tail + 1) % VIO_PEND_MAX;
|
||||
dev->pend_count--;
|
||||
}
|
||||
u32 idx = dev->pend_head;
|
||||
dev->pending[idx].type = type;
|
||||
dev->pending[idx].code = code;
|
||||
dev->pending[idx].value = value;
|
||||
dev->pend_head = (idx + 1) % VIO_PEND_MAX;
|
||||
dev->pend_count++;
|
||||
}
|
||||
|
||||
void virtio_input_push_keyboard(u16 code, u32 value) {
|
||||
/* code = linux keycode (KEY_A..), value = 0=release, 1=press, 2=repeat */
|
||||
input_push(&g_kbd, VIO_INPUT_EV_KEY, code, value);
|
||||
}
|
||||
|
||||
void virtio_input_push_mouse_rel(u16 axis, i32 value) {
|
||||
/* axis = VIO_INPUT_REL_X or VIO_INPUT_REL_Y */
|
||||
input_push(&g_mouse, VIO_INPUT_EV_REL, axis, (u32)value);
|
||||
}
|
||||
|
||||
void virtio_input_push_mouse_button(u16 btn, u32 value) {
|
||||
/* btn = VIO_INPUT_BTN_LEFT/RIGHT/MIDDLE, value = 0=release, 1=press */
|
||||
input_push(&g_mouse, VIO_INPUT_EV_KEY, btn, value);
|
||||
}
|
||||
|
||||
void virtio_input_push_mouse_syn(void) {
|
||||
/* SYN_REPORT marks the end of a logical input frame (e.g. a complete
|
||||
* mouse movement + button change). libinput relies on this to deliver
|
||||
* a single consolidated event to clients. */
|
||||
input_push(&g_mouse, VIO_INPUT_EV_SYN, 0, 0);
|
||||
}
|
||||
|
||||
/* ===== Periodic poll (called from vmexit_before_resume) ===== */
|
||||
void virtio_input_poll(void) {
|
||||
int kbd_filled = input_drain_eventq(&g_kbd);
|
||||
int mouse_filled = input_drain_eventq(&g_mouse);
|
||||
if (kbd_filled > 0) virtio_mmio_raise_irq_by_gpa(g_kbd.gpa_base);
|
||||
if (mouse_filled > 0) virtio_mmio_raise_irq_by_gpa(g_mouse.gpa_base);
|
||||
}
|
||||
|
||||
/* ===== Backend init ===== */
|
||||
|
||||
static struct virtio_backend g_kbd_backend = {
|
||||
.device_id = VIRTIO_ID_INPUT,
|
||||
.gpa_base = VIRTIO_MMIO_KBD_GPA,
|
||||
.irq = 8, /* cmdline :8, vector 0x38 */
|
||||
.num_queues = 2, /* eventq + statusq */
|
||||
.queue_size = 64,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1),
|
||||
.config_len = VIO_CFG_LEN,
|
||||
.read_config = kbd_read_config,
|
||||
.write_config = kbd_write_config,
|
||||
.queue_notify = kbd_queue_notify,
|
||||
.reset = kbd_reset,
|
||||
};
|
||||
|
||||
static struct virtio_backend g_mouse_backend = {
|
||||
.device_id = VIRTIO_ID_INPUT,
|
||||
.gpa_base = VIRTIO_MMIO_MOUSE_GPA,
|
||||
.irq = 9, /* cmdline :9, vector 0x39 */
|
||||
.num_queues = 2,
|
||||
.queue_size = 64,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1),
|
||||
.config_len = VIO_CFG_LEN,
|
||||
.read_config = mouse_read_config,
|
||||
.write_config = mouse_write_config,
|
||||
.queue_notify = mouse_queue_notify,
|
||||
.reset = mouse_reset,
|
||||
};
|
||||
|
||||
void virtio_input_backend_init(void) {
|
||||
g_kbd.gpa_base = VIRTIO_MMIO_KBD_GPA;
|
||||
g_kbd.irq = 8;
|
||||
g_kbd.name = "Deshab Virtual Keyboard";
|
||||
input_reset_dev(&g_kbd);
|
||||
|
||||
g_mouse.gpa_base = VIRTIO_MMIO_MOUSE_GPA;
|
||||
g_mouse.irq = 9;
|
||||
g_mouse.name = "Deshab Virtual Mouse";
|
||||
input_reset_dev(&g_mouse);
|
||||
|
||||
virtio_mmio_register(&g_kbd_backend);
|
||||
virtio_mmio_register(&g_mouse_backend);
|
||||
|
||||
log_info("[VINPUT] keyboard + mouse backends registered");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
vmm/virtio_input.o: vmm\virtio_input.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
@@ -20,7 +20,8 @@ extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
#define VIRTIO_MMIO_MAGIC 0x74726976 /* "virt" */
|
||||
#define VIRTIO_MMIO_VERSION_MODERN 2
|
||||
#define MAX_VIRTIO_DEVICES 4
|
||||
/* VSCode Phase 1: 3 (blk/net/rootfs) → 8 slots for input/gpu/extra-rootfs. */
|
||||
#define MAX_VIRTIO_DEVICES 8
|
||||
|
||||
/* 每个 virtio 设备的运行状态(框架层维护,后端无关) */
|
||||
struct virtio_dev_state {
|
||||
@@ -227,7 +228,13 @@ void virtio_mmio_write(u64 gpa, u64 value, int width) {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* device config 空间写(多数设备 config 只读,忽略) */
|
||||
/* VSCode Phase 2: device config 空间写。virtio-input 用此通道设置
|
||||
* select/subsel 后再读结果;其它设备 config 只读(write_config=NULL)。 */
|
||||
if (off >= VIRTIO_MMIO_CONFIG && off < VIRTIO_MMIO_CONFIG + s->be->config_len) {
|
||||
if (s->be->write_config) {
|
||||
s->be->write_config(off - VIRTIO_MMIO_CONFIG, v32);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -264,6 +271,35 @@ int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: 按 gpa_base 定位队列。用于两个 virtio-input(kbd+mouse),
|
||||
* 它们 device_id 相同(VIRTIO_ID_INPUT=18),只能靠 gpa_base 区分。 */
|
||||
int virtio_queue_get_ptrs_by_gpa(u64 gpa_base, u32 queue_idx,
|
||||
struct virtq_desc **desc_out,
|
||||
struct virtq_avail **avail_out,
|
||||
struct virtq_used **used_out,
|
||||
u32 *queue_num_out) {
|
||||
struct virtio_dev_state *s = (struct virtio_dev_state *)0;
|
||||
for (int i = 0; i < g_vdev_count; i++) {
|
||||
if (g_vdevs[i].be && g_vdevs[i].be->gpa_base == gpa_base) {
|
||||
s = &g_vdevs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!s || queue_idx >= 4) return -1;
|
||||
if (!s->q[queue_idx].ready) return -2;
|
||||
|
||||
struct virtq_desc *desc = (struct virtq_desc *)virtio_gpa_to_host(s->q[queue_idx].desc_gpa);
|
||||
struct virtq_avail *avail = (struct virtq_avail *)virtio_gpa_to_host(s->q[queue_idx].driver_gpa);
|
||||
struct virtq_used *used = (struct virtq_used *)virtio_gpa_to_host(s->q[queue_idx].device_gpa);
|
||||
if (!desc || !avail || !used) return -3;
|
||||
|
||||
if (desc_out) *desc_out = desc;
|
||||
if (avail_out) *avail_out = avail;
|
||||
if (used_out) *used_out = used;
|
||||
if (queue_num_out) *queue_num_out = s->q[queue_idx].num ? s->q[queue_idx].num : s->be->queue_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx) {
|
||||
if (!used) return;
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
@@ -283,17 +319,34 @@ void virtio_mmio_raise_irq(u32 device_id) {
|
||||
}
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: 按 gpa_base 触发中断(两个 virtio-input 同 device_id=18
|
||||
* 但不同 gpa_base / irq,需要分别注入)。 */
|
||||
void virtio_mmio_raise_irq_by_gpa(u64 gpa_base) {
|
||||
for (int i = 0; i < g_vdev_count; i++) {
|
||||
struct virtio_dev_state *s = &g_vdevs[i];
|
||||
if (!s->be || s->be->gpa_base != gpa_base) continue;
|
||||
s->interrupt_status |= 1;
|
||||
if (s->be->irq != 0) {
|
||||
vmx_guest_queue_irq(0x30 + s->be->irq);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 框架初始化 =====
|
||||
*
|
||||
* 注册 block 与 net 后端。后端结构体由 virtio_blk.c / virtio_net.c 提供。 */
|
||||
extern struct virtio_backend g_virtio_blk_backend;
|
||||
extern struct virtio_backend g_virtio_net_backend;
|
||||
|
||||
/* 后端初始化符号(定义于 virtio_blk.c / virtio_net.c)。
|
||||
* 这里声明以便链接;具体注册在各自文件完成。 */
|
||||
/* 后端初始化符号(定义于 virtio_blk.c / virtio_net.c / virtio_input.c /
|
||||
* virtio_gpu.c)。这里声明以便链接;具体注册在各自文件完成。 */
|
||||
void virtio_blk_backend_init(void);
|
||||
void virtio_net_backend_init(void);
|
||||
void virtio_rootfs_blk_init(void); /* memory-backed Arch rootfs (/dev/vdb) */
|
||||
void virtio_input_backend_init(void); /* VSCode Phase 2: keyboard + mouse */
|
||||
void virtio_gpu_backend_init(void); /* VSCode Phase 3: virtio-gpu 2D */
|
||||
void virtio_extra_rootfs_blk_init(void); /* VSCode Phase 4: rw volume (/dev/vdc) */
|
||||
|
||||
void virtio_mmio_init(void) {
|
||||
if (g_vio_ready) return;
|
||||
@@ -305,6 +358,17 @@ void virtio_mmio_init(void) {
|
||||
* Must come after blk/net so /dev/vda (SATA) and /dev/vdb (rootfs) order
|
||||
* is deterministic. */
|
||||
virtio_rootfs_blk_init();
|
||||
/* VSCode Phase 2: virtio-input (keyboard + mouse). Safe no-op until the
|
||||
* desktop enables input forwarding via the push_* API. */
|
||||
virtio_input_backend_init();
|
||||
/* VSCode Phase 3: virtio-gpu 2D (scanout compositing into surface pool).
|
||||
* Safe no-op if the surface pool was not allocated (Linux guest not loaded
|
||||
* or alloc failed) — scanout stays disabled. */
|
||||
virtio_gpu_backend_init();
|
||||
/* VSCode Phase 4: writable extra-rootfs volume (/dev/vdc, overlay upper).
|
||||
* Safe no-op if no extra-rootfs boot module — guest probe sees bad magic
|
||||
* on slot6 and skips it. */
|
||||
virtio_extra_rootfs_blk_init();
|
||||
|
||||
g_vio_ready = 1;
|
||||
log_info("[VIO] virtio-mmio backend init ok");
|
||||
|
||||
@@ -60,12 +60,16 @@ static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len
|
||||
* - Linux guest:park(推进 RIP 越过 HLT,exit-to-host,设 g_guest_parked=1)
|
||||
* host 调用 linux_resume() 时 vmresume 唤醒 guest 从 HLT 之后继续执行。 */
|
||||
static int handle_hlt(u64 rip, u64 instr_len, int *out_resume) {
|
||||
*out_resume = 0; /* 两种情况都 exit-to-host */
|
||||
*out_resume = 0; /* 三种情况默认 exit-to-host */
|
||||
if (g_linux_guest_active) {
|
||||
/* Linux guest: park — 推进 RIP 越过 HLT 指令 */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
g_guest_parked = 1;
|
||||
log_info("[VMEXIT] HLT - Linux guest parked");
|
||||
} else if (g_xj380_guest_active) {
|
||||
/* OpenXJ380 guest: 推进 RIP 并立即继续执行(不 park 不 terminate) */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
*out_resume = 1;
|
||||
} else {
|
||||
/* self-test guest: 终止 */
|
||||
log_info("[VMEXIT] HLT - guest halted");
|
||||
@@ -493,6 +497,7 @@ static void vmexit_before_resume(void) {
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
poll_host_serial_to_guest();
|
||||
virtio_net_poll();
|
||||
virtio_input_poll(); /* VSCode Phase 2: drain pending kbd/mouse events */
|
||||
pit_tick_update();
|
||||
maybe_inject_irq();
|
||||
}
|
||||
|
||||
+173
-18
@@ -85,6 +85,7 @@ static u8 g_msr_bitmap[4096] __attribute__((aligned(4096)));
|
||||
* VMX 要求 guest TR 非空且引用 GDT 中的 TSS 描述符。 */
|
||||
static u8 g_guest_gdt[4096] __attribute__((aligned(4096)));
|
||||
static u8 g_guest_tss[4096] __attribute__((aligned(4096)));
|
||||
static u8 g_guest_idt[4096] __attribute__((aligned(4096))); /* P8.4: IDT page for KVM nested VMX */
|
||||
|
||||
/* 64-bit GDT entry */
|
||||
typedef struct __attribute__((packed)) {
|
||||
@@ -234,10 +235,11 @@ static void vmcs_setup_host_state(void) {
|
||||
}
|
||||
|
||||
static void vmcs_setup_guest_state_phase1(void) {
|
||||
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表 */
|
||||
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表。
|
||||
* P8.4: CR4 must include VMXE (CR4_FIXED0 requires it under KVM). */
|
||||
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR3, read_cr3_local());
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
|
||||
|
||||
/* 段选择子:flat 模型。
|
||||
* CS=0x08 (GDT[1] code), DS/SS/ES=0x10 (GDT[2] data),
|
||||
@@ -261,7 +263,9 @@ static void vmcs_setup_guest_state_phase1(void) {
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_BASE, 0x4000);
|
||||
vmx_vmcs_write(VMCS_GUEST_GDTR_BASE, 0x3000);
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0);
|
||||
/* P8.4: IDTR base must be non-zero & page-aligned for KVM nested VMX.
|
||||
* Guest won't use interrupts (RFLAGS.IF=0), but VMX still checks. */
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0x5000);
|
||||
|
||||
/* 段 limit:代码/数据段 4GB,GDTR 覆盖 0x28 字节(3 个描述符 + TSS),
|
||||
* TR limit = 0x67(104 字节最小 TSS) */
|
||||
@@ -278,16 +282,17 @@ static void vmcs_setup_guest_state_phase1(void) {
|
||||
|
||||
/* 段 access rights:
|
||||
* CS: 0xA09B = present, DPL0, code, L=1 (64-bit), executed/read
|
||||
* DS/SS/ES/FS/GS: 0xC093 = present, DPL0, data, writable
|
||||
* LDTR: 0x82 = unusable (L=0 in high nibble)
|
||||
* DS/SS/ES: 0xC093 = present, DPL0, data, writable
|
||||
* P8.4: FS/GS/LDTR selector=0 -> must set bit 16 (unusable) per Intel SDM.
|
||||
* Without it, KVM nested VMX rejects VMLAUNCH (invalid guest state).
|
||||
* TR: 0x8B = present, DPL0, 64-bit busy TSS */
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0x1C093); /* unusable (selector=0) */
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0x1C093); /* unusable (selector=0) */
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x10082); /* unusable (selector=0) */
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B);
|
||||
|
||||
/* Guest RIP/RSP/RFLAGS */
|
||||
@@ -308,6 +313,10 @@ static void vmcs_setup_guest_state_phase1(void) {
|
||||
* NXE 允许 guest 使用 NX 位。 */
|
||||
vmx_vmcs_write(VMCS_GUEST_IA32_EFER, EFER_LME | EFER_LMA | EFER_NXE);
|
||||
|
||||
/* P8.4: VMCS link pointer must be 0xFFFFFFFFFFFFFFFF (Intel SDM Vol 3,
|
||||
* VM-entry checks). KVM nested VMX enforces this strictly. */
|
||||
vmx_vmcs_write(VMCS_GUEST_LINK_POINTER, 0xFFFFFFFFFFFFFFFFULL);
|
||||
|
||||
/* Guest activity state = 0 (active) */
|
||||
vmx_vmcs_write(VMCS_GUEST_ACTIVITY_STATE, 0);
|
||||
/* Guest interruptibility = 0(无阻塞) */
|
||||
@@ -325,14 +334,11 @@ static void vmcs_setup_controls(u64 eptp) {
|
||||
| CPU_BASED_INVLPG_EXITING;
|
||||
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
||||
|
||||
/* Secondary controls:启用 EPT + VPID */
|
||||
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
|
||||
{
|
||||
u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2);
|
||||
u32 allowed1 = (u32)(msr >> 32);
|
||||
cpu2 &= allowed1;
|
||||
cpu2 |= SEC_EXEC_ENABLE_EPT;
|
||||
}
|
||||
/* P8.4d: Try without EPT first to isolate nested VMX issue under KVM.
|
||||
* If KVM requires EPT, vmx_adjust_control will OR it back in. */
|
||||
u64 cpu2 = 0; /* no EPT, no VPID */
|
||||
cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2);
|
||||
log_hex64("[VMM] IA32_VMX_PROCBASED_CTLS2 raw=", vmx_read_msr(IA32_VMX_PROCBASED_CTLS2));
|
||||
|
||||
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
||||
@@ -357,8 +363,15 @@ static void vmcs_setup_controls(u64 eptp) {
|
||||
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
||||
vmx_vmcs_write(VMCS_VPID, 1);
|
||||
if (cpu2 & SEC_EXEC_ENABLE_EPT) {
|
||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
||||
log_hex64("[VMM] EPT enabled, eptp=", eptp);
|
||||
} else {
|
||||
log_info("[VMM] EPT not available (shadow paging)");
|
||||
}
|
||||
if (cpu2 & SEC_EXEC_ENABLE_VPID) {
|
||||
vmx_vmcs_write(VMCS_VPID, 1);
|
||||
}
|
||||
|
||||
/* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */
|
||||
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6));
|
||||
@@ -418,6 +431,90 @@ void vmm_shutdown(void) {
|
||||
int vmm_is_ready(void) { return g_vmm_ready; }
|
||||
u64 vmm_get_eptp(void) { return ept_get_eptp(); }
|
||||
|
||||
/* P8.4: Dump all guest-state VMCS fields for KVM nested VMX debugging.
|
||||
* Called before VMLAUNCH to identify field encoding/value errors. */
|
||||
static void vmm_dump_guest_state(void) {
|
||||
log_info("[VMM] === Guest State Validation ===");
|
||||
|
||||
/* ---- CR / EFER ---- */
|
||||
u64 g_cr0 = vmx_vmcs_read(VMCS_GUEST_CR0);
|
||||
u64 g_cr3 = vmx_vmcs_read(VMCS_GUEST_CR3);
|
||||
u64 g_cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
u64 g_efer = vmx_vmcs_read(VMCS_GUEST_IA32_EFER);
|
||||
log_hex64("[VMM] gCR0=", g_cr0);
|
||||
log_hex64("[VMM] gCR3=", g_cr3);
|
||||
log_hex64("[VMM] gCR4=", g_cr4);
|
||||
log_hex64("[VMM] gEFER=", g_efer);
|
||||
|
||||
/* ---- RIP/RSP/RFLAGS ---- */
|
||||
log_hex64("[VMM] gRIP=", vmx_vmcs_read(VMCS_GUEST_RIP));
|
||||
log_hex64("[VMM] gRSP=", vmx_vmcs_read(VMCS_GUEST_RSP));
|
||||
log_hex64("[VMM] gRFLAGS=", vmx_vmcs_read(VMCS_GUEST_RFLAGS));
|
||||
|
||||
/* ---- Segment selectors ---- */
|
||||
u64 es = vmx_vmcs_read(VMCS_GUEST_ES_SELECTOR);
|
||||
u64 cs = vmx_vmcs_read(VMCS_GUEST_CS_SELECTOR);
|
||||
u64 ss = vmx_vmcs_read(VMCS_GUEST_SS_SELECTOR);
|
||||
u64 ds = vmx_vmcs_read(VMCS_GUEST_DS_SELECTOR);
|
||||
u64 fs = vmx_vmcs_read(VMCS_GUEST_FS_SELECTOR);
|
||||
u64 gs = vmx_vmcs_read(VMCS_GUEST_GS_SELECTOR);
|
||||
u64 ldtr = vmx_vmcs_read(VMCS_GUEST_LDTR_SELECTOR);
|
||||
u64 tr = vmx_vmcs_read(VMCS_GUEST_TR_SELECTOR);
|
||||
log_hex64("[VMM] sSEL ES=", es); log_hex64("[VMM] sSEL CS=", cs);
|
||||
log_hex64("[VMM] sSEL SS=", ss); log_hex64("[VMM] sSEL DS=", ds);
|
||||
log_hex64("[VMM] sSEL FS=", fs); log_hex64("[VMM] sSEL GS=", gs);
|
||||
log_hex64("[VMM] sSEL LDTR=", ldtr); log_hex64("[VMM] sSEL TR=", tr);
|
||||
|
||||
/* ---- Segment limits ---- */
|
||||
log_hex64("[VMM] sLIM ES=", vmx_vmcs_read(VMCS_GUEST_ES_LIMIT));
|
||||
log_hex64("[VMM] sLIM CS=", vmx_vmcs_read(VMCS_GUEST_CS_LIMIT));
|
||||
log_hex64("[VMM] sLIM SS=", vmx_vmcs_read(VMCS_GUEST_SS_LIMIT));
|
||||
log_hex64("[VMM] sLIM DS=", vmx_vmcs_read(VMCS_GUEST_DS_LIMIT));
|
||||
log_hex64("[VMM] sLIM FS=", vmx_vmcs_read(VMCS_GUEST_FS_LIMIT));
|
||||
log_hex64("[VMM] sLIM GS=", vmx_vmcs_read(VMCS_GUEST_GS_LIMIT));
|
||||
log_hex64("[VMM] sLIM LDTR=", vmx_vmcs_read(VMCS_GUEST_LDTR_LIMIT));
|
||||
log_hex64("[VMM] sLIM TR=", vmx_vmcs_read(VMCS_GUEST_TR_LIMIT));
|
||||
|
||||
/* ---- Segment access rights ---- */
|
||||
log_hex64("[VMM] sACC ES=", vmx_vmcs_read(VMCS_GUEST_ES_ACCESS));
|
||||
log_hex64("[VMM] sACC CS=", vmx_vmcs_read(VMCS_GUEST_CS_ACCESS));
|
||||
log_hex64("[VMM] sACC SS=", vmx_vmcs_read(VMCS_GUEST_SS_ACCESS));
|
||||
log_hex64("[VMM] sACC DS=", vmx_vmcs_read(VMCS_GUEST_DS_ACCESS));
|
||||
log_hex64("[VMM] sACC FS=", vmx_vmcs_read(VMCS_GUEST_FS_ACCESS));
|
||||
log_hex64("[VMM] sACC GS=", vmx_vmcs_read(VMCS_GUEST_GS_ACCESS));
|
||||
log_hex64("[VMM] sACC LDTR=", vmx_vmcs_read(VMCS_GUEST_LDTR_ACCESS));
|
||||
log_hex64("[VMM] sACC TR=", vmx_vmcs_read(VMCS_GUEST_TR_ACCESS));
|
||||
|
||||
/* ---- Segment bases ---- */
|
||||
log_hex64("[VMM] sBAS CS=", vmx_vmcs_read(VMCS_GUEST_CS_BASE));
|
||||
log_hex64("[VMM] sBAS TR=", vmx_vmcs_read(VMCS_GUEST_TR_BASE));
|
||||
log_hex64("[VMM] sBAS GDTR=", vmx_vmcs_read(VMCS_GUEST_GDTR_BASE));
|
||||
log_hex64("[VMM] sBAS IDTR=", vmx_vmcs_read(VMCS_GUEST_IDTR_BASE));
|
||||
|
||||
/* ---- GDTR/IDTR limits ---- */
|
||||
log_hex64("[VMM] dLIM GDTR=", vmx_vmcs_read(VMCS_GUEST_GDTR_LIMIT));
|
||||
log_hex64("[VMM] dLIM IDTR=", vmx_vmcs_read(VMCS_GUEST_IDTR_LIMIT));
|
||||
|
||||
/* ---- Activity / interruptibility / link ---- */
|
||||
log_hex64("[VMM] gACT=", vmx_vmcs_read(VMCS_GUEST_ACTIVITY_STATE));
|
||||
log_hex64("[VMM] gINTSTATE=", vmx_vmcs_read(VMCS_GUEST_INTERRUPTIBILITY));
|
||||
log_hex64("[VMM] gLINK=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER));
|
||||
|
||||
/* ---- SYSENTER ---- */
|
||||
log_hex64("[VMM] gSYSENTER_CS=", vmx_vmcs_read(VMCS_GUEST_SYSENTER_CS));
|
||||
log_hex64("[VMM] gDR7=", vmx_vmcs_read(VMCS_GUEST_DR7));
|
||||
|
||||
/* ---- Host state summary ---- */
|
||||
log_hex64("[VMM] hCR0=", vmx_vmcs_read(VMCS_HOST_CR0));
|
||||
log_hex64("[VMM] hCR3=", vmx_vmcs_read(VMCS_HOST_CR3));
|
||||
log_hex64("[VMM] hCR4=", vmx_vmcs_read(VMCS_HOST_CR4));
|
||||
log_hex64("[VMM] hRIP=", vmx_vmcs_read(VMCS_HOST_RIP));
|
||||
log_hex64("[VMM] hRSP=", vmx_vmcs_read(VMCS_HOST_RSP));
|
||||
log_hex64("[VMM] hEFER=", vmx_vmcs_read(VMCS_HOST_IA32_EFER));
|
||||
|
||||
log_info("[VMM] === End Guest State ===");
|
||||
}
|
||||
|
||||
int vmm_self_test(void) {
|
||||
if (!g_vmm_ready) {
|
||||
log_error("[VMM] not ready");
|
||||
@@ -458,14 +555,46 @@ int vmm_self_test(void) {
|
||||
log_error("[VMM] map guest TSS failed");
|
||||
return -6;
|
||||
}
|
||||
/* P8.4: Map IDT page for KVM nested VMX (IDTR base = 0x5000) */
|
||||
{
|
||||
u64 guest_idt_phys = (u64)g_guest_idt - hhdm;
|
||||
for (int i = 0; i < 4096; i++) g_guest_idt[i] = 0;
|
||||
if (ept_map_range(0x5000, guest_idt_phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[VMM] map guest IDT failed");
|
||||
return -9;
|
||||
}
|
||||
}
|
||||
|
||||
/* 配置 VMCS */
|
||||
vmcs_setup_guest_state_phase1();
|
||||
vmcs_setup_controls(ept_get_eptp());
|
||||
vmcs_setup_host_state();
|
||||
|
||||
/* P8.4: VMCLEAR + VMPTRLD to put VMCS in "clear" state before VMLAUNCH.
|
||||
* Without this, VMLAUNCH fails (error 4 or silent failure under KVM). */
|
||||
if (vmx_vmcs_clear(g_vmcs_phys) != 0) {
|
||||
log_error("[VMM] vmclear failed");
|
||||
return -10;
|
||||
}
|
||||
if (vmx_vmcs_load(g_vmcs_phys) != 0) {
|
||||
log_error("[VMM] vmptrld after vmclear failed");
|
||||
return -11;
|
||||
}
|
||||
|
||||
log_info("[VMM] launching guest");
|
||||
|
||||
/* P8.4: dump key VMCS fields before VMLAUNCH for debugging */
|
||||
log_hex64("[VMM] pin=", vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL));
|
||||
log_hex64("[VMM] cpu=", vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL));
|
||||
log_hex64("[VMM] cpu2=", vmx_vmcs_read(VMCS_SECONDARY_VM_EXEC_CONTROL));
|
||||
log_hex64("[VMM] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS));
|
||||
log_hex64("[VMM] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS));
|
||||
log_hex64("[VMM] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER));
|
||||
log_hex64("[VMM] link=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER));
|
||||
|
||||
/* P8.4: Dump all guest-state fields for KVM debug */
|
||||
vmm_dump_guest_state();
|
||||
|
||||
/* vmlaunch 控制流:
|
||||
* - 失败:执行下一条指令,failed=1
|
||||
* - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。
|
||||
@@ -495,6 +624,32 @@ int vmm_self_test(void) {
|
||||
serial_write("[VMM] ");
|
||||
serial_write(vmx_error_str(error));
|
||||
serial_putc('\n');
|
||||
|
||||
/* P8.4: diagnostic - dump guest CR0/CR4 vs VMX fixed bits */
|
||||
{
|
||||
u64 cr0_fixed0 = vmx_read_msr(0x486); /* IA32_VMX_CR0_FIXED0 */
|
||||
u64 cr0_fixed1 = vmx_read_msr(0x487); /* IA32_VMX_CR0_FIXED1 */
|
||||
u64 cr4_fixed0 = vmx_read_msr(0x488); /* IA32_VMX_CR4_FIXED0 */
|
||||
u64 cr4_fixed1 = vmx_read_msr(0x489); /* IA32_VMX_CR4_FIXED1 */
|
||||
u64 g_cr0 = vmx_vmcs_read(VMCS_GUEST_CR0);
|
||||
u64 g_cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
log_hex64("[VMM] CR0_FIXED0=", cr0_fixed0);
|
||||
log_hex64("[VMM] CR0_FIXED1=", cr0_fixed1);
|
||||
log_hex64("[VMM] guest CR0 =", g_cr0);
|
||||
log_hex64("[VMM] CR4_FIXED0=", cr4_fixed0);
|
||||
log_hex64("[VMM] CR4_FIXED1=", cr4_fixed1);
|
||||
log_hex64("[VMM] guest CR4 =", g_cr4);
|
||||
/* Check: guest CR0 must have all bits in CR0_FIXED0 set,
|
||||
* and must not have any bits set that are 0 in CR0_FIXED1 */
|
||||
u64 cr0_missing = cr0_fixed0 & ~g_cr0; /* bits FIXED0 requires but guest lacks */
|
||||
u64 cr0_extra = g_cr0 & ~cr0_fixed1; /* bits guest has but FIXED1 forbids */
|
||||
log_hex64("[VMM] CR0 missing(need)=", cr0_missing);
|
||||
log_hex64("[VMM] CR0 extra(forbid)=", cr0_extra);
|
||||
u64 cr4_missing = cr4_fixed0 & ~g_cr4;
|
||||
u64 cr4_extra = g_cr4 & ~cr4_fixed1;
|
||||
log_hex64("[VMM] CR4 missing(need)=", cr4_missing);
|
||||
log_hex64("[VMM] CR4 extra(forbid)=", cr4_extra);
|
||||
}
|
||||
return -7;
|
||||
}
|
||||
|
||||
|
||||
+379
-331
@@ -1,331 +1,379 @@
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* ===== MSR / CR helpers ===== */
|
||||
|
||||
u64 vmx_read_msr(u32 msr) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
|
||||
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
||||
u32 hi = (u32)(value >> 32);
|
||||
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
|
||||
}
|
||||
|
||||
static inline u64 read_cr0(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr0, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_cr4(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr4, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void write_cr4(u64 v) {
|
||||
__asm__ volatile("mov %0, %%cr4" :: "r"(v));
|
||||
}
|
||||
|
||||
static inline u64 read_cr3(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr3, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_rflags(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("pushfq; popq %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
/* ===== CPUID ===== */
|
||||
|
||||
static void cpuid(u32 leaf, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) {
|
||||
__asm__ volatile("cpuid"
|
||||
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
|
||||
: "a"(leaf));
|
||||
}
|
||||
|
||||
int vmx_supported(void) {
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
return (ecx & (1U << 5)) != 0; /* CPUID.1:ECX[5] = VMX */
|
||||
}
|
||||
|
||||
/* ===== VMXON region ===== */
|
||||
|
||||
static u64 g_vmxon_phys;
|
||||
static u64 g_vmcs_revision;
|
||||
static u64 g_vmx_basic;
|
||||
static int g_vmx_enabled;
|
||||
|
||||
/* VMX 要求 CR0 固定位必须为 1(VMX operation 不允许清除)。
|
||||
* 这些位由 IA32_VMX_BASIC MSR 的高 32 位(CR0 fixed-0)和
|
||||
* IA32_VMX_CR0_FIXED1 MSR 决定。简化:直接 OR 上 PE/NE/PG/WP。 */
|
||||
static u64 vmx_required_cr0(void) {
|
||||
return read_cr0() | CR0_PE | CR0_NE | CR0_WP | CR0_PG;
|
||||
}
|
||||
|
||||
static u64 vmx_required_cr4(void) {
|
||||
return read_cr4() | CR4_VMXE | CR4_PAE | CR4_PGE;
|
||||
}
|
||||
|
||||
/* Check whether we are running under a hypervisor that does NOT support
|
||||
* nested VMX. This covers:
|
||||
* - WHPX: CPUID may report VMX but VMXON/CR4.VMXE causes fatal exit
|
||||
* - Any hypervisor without nested virtualization support
|
||||
*
|
||||
* Detection strategy:
|
||||
* 1. Check CPUID.01H:ECX[31] (hypervisor present bit)
|
||||
* 2. If hypervisor present, check CPUID.40000000H for vendor signature
|
||||
* 3. Try reading IA32_VMX_BASIC MSR — if it returns 0, VMX is not real
|
||||
* 4. Check Hyper-V specific: HV_CPU_MANAGEMENT_FEATURES for nested VMX
|
||||
*
|
||||
* If VMX CPUID flag is set but we're under a hypervisor that doesn't expose
|
||||
* real VMX MSRs, we must not attempt CR4.VMXE or VMXON, as this will cause
|
||||
* a fatal VM exit in WHPX (exit code 4).
|
||||
*/
|
||||
static int vmx_truly_available(void) {
|
||||
/* Step 1: If CPUID doesn't even report VMX, definitely not available */
|
||||
if (!vmx_supported()) return 0;
|
||||
|
||||
/* Step 2: Check if running under a hypervisor */
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
if (!(ecx & (1U << 31))) {
|
||||
/* No hypervisor — bare metal, VMX should work */
|
||||
return 1;
|
||||
}
|
||||
log_info("[VMX] hypervisor present bit set, checking nested VMX support");
|
||||
|
||||
/* Step 3: Try reading IA32_VMX_BASIC — if it's zero or faults,
|
||||
* the hypervisor doesn't expose real VMX. */
|
||||
/* We use a safe read: if the MSR doesn't exist, rdmsr would #GP.
|
||||
* However, under WHPX, IA32_VMX_BASIC may return a non-zero value
|
||||
* even though VMXON will fail. So we need another check. */
|
||||
|
||||
/* Step 4: Check hypervisor vendor for nested VMX support */
|
||||
cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
|
||||
|
||||
/* Check for Hyper-V ("Microsoft Hv") */
|
||||
if (ebx == 0x7263694DU && ecx == 0x666F736FU && edx == 0x76482074U) {
|
||||
log_info("[VMX] Hyper-V detected");
|
||||
/* Check if HV_CPU_MANAGEMENT_FEATURES (MSR 0x4000000F) exists.
|
||||
* Hyper-V CPUID leaf 0x40000003 tells us which MSRs are available.
|
||||
* Partition_privileges bit 1 (nested virtualization) is at EBX bit 1
|
||||
* of CPUID 0x40000000 + 3 = 0x40000003.
|
||||
* But safer: just check the max CPUID leaf for Hyper-V. */
|
||||
u32 hv_max_leaf = eax;
|
||||
if (hv_max_leaf >= 0x40000003) {
|
||||
/* CPUID 0x40000003 EBX bit 1 = AccessIntrCtrl (nested) */
|
||||
cpuid(0x40000003, &eax, &ebx, &ecx, &edx);
|
||||
if (ebx & (1U << 1)) {
|
||||
log_info("[VMX] Hyper-V nested virtualization supported");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
log_warn("[VMX] Hyper-V WITHOUT nested VMX support");
|
||||
log_warn("[VMX] CR4.VMXE / VMXON would cause fatal exit, skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check for KVM ("KVMKVMKVM") */
|
||||
if (ebx == 0x4B4D564BU && ecx == 0x564B4D56U && edx == 0x4D564B4DU) {
|
||||
log_info("[VMX] KVM detected — nested VMX may be available");
|
||||
/* KVM typically supports nested VMX if enabled */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Unknown hypervisor — be conservative: skip VMX to avoid
|
||||
* fatal exits. The user must explicitly enable nested VMX on
|
||||
* their hypervisor for Linux compat to work. */
|
||||
log_warn("[VMX] unknown hypervisor, VMX may not be truly available");
|
||||
log_warn("[VMX] skipping VMX init to avoid potential fatal exit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_enable(void) {
|
||||
if (g_vmx_enabled) return 0;
|
||||
|
||||
if (!vmx_truly_available()) {
|
||||
log_warn("[VMX] VMX not truly available, skipping VMX init");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 读 IA32_VMX_BASIC,获取 VMCS revision ID 与 region 大小 */
|
||||
g_vmx_basic = vmx_read_msr(IA32_VMX_BASIC);
|
||||
g_vmcs_revision = g_vmx_basic & 0x7FFFFFFFULL;
|
||||
u32 vmcs_size = (u32)((g_vmx_basic >> 32) & 0x1FFF);
|
||||
if (vmcs_size == 0) vmcs_size = 4096;
|
||||
|
||||
log_hex64("[VMX] VMX_BASIC=", g_vmx_basic);
|
||||
log_hex64("[VMX] VMCS revision=", g_vmcs_revision);
|
||||
|
||||
/* 分配 VMXON region(4KB 对齐,物理页) */
|
||||
dkm_dma_buffer vmxon_buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &vmxon_buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMXON region");
|
||||
return -2;
|
||||
}
|
||||
g_vmxon_phys = vmxon_buf.phys;
|
||||
|
||||
/* 在 VMXON region 偏移 0 写入 VMCS revision ID */
|
||||
u32 *vmxon_ptr = (u32 *)vmxon_buf.virt;
|
||||
vmxon_ptr[0] = (u32)g_vmcs_revision;
|
||||
log_hex64("[VMX] VMXON phys=", g_vmxon_phys);
|
||||
|
||||
/* 设置 CR4.VMXE */
|
||||
u64 cr4 = vmx_required_cr4();
|
||||
write_cr4(cr4);
|
||||
log_hex64("[VMX] CR4=", cr4);
|
||||
|
||||
/* 确保 CR0 满足 VMX 要求(这里只读不写,UEFI 已配置好) */
|
||||
(void)vmx_required_cr0();
|
||||
|
||||
/* 执行 vmxon */
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmxon %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(g_vmxon_phys)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
if (err) {
|
||||
/* 第二次尝试:读 vmx instruction error 通过 VMCS 不适用 vmxon;
|
||||
* 简化:返回失败。 */
|
||||
log_error("[VMX] vmxon failed");
|
||||
return -3;
|
||||
}
|
||||
|
||||
g_vmx_enabled = 1;
|
||||
log_info("[VMX] VMX root mode enabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_disable(void) {
|
||||
if (!g_vmx_enabled) return 0;
|
||||
__asm__ volatile("vmxoff" ::: "memory");
|
||||
g_vmx_enabled = 0;
|
||||
/* 清 CR4.VMXE */
|
||||
u64 cr4 = read_cr4();
|
||||
cr4 &= ~CR4_VMXE;
|
||||
write_cr4(cr4);
|
||||
log_info("[VMX] VMX root mode disabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_alloc(u64 *phys_out) {
|
||||
if (!phys_out) return -1;
|
||||
dkm_dma_buffer buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMCS region");
|
||||
return -2;
|
||||
}
|
||||
/* 偏移 0 写入 VMCS revision ID */
|
||||
u32 *p = (u32 *)buf.virt;
|
||||
p[0] = (u32)g_vmcs_revision;
|
||||
*phys_out = buf.phys;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_load(u64 phys) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmptrld %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(phys)
|
||||
: "memory"
|
||||
);
|
||||
if (err) {
|
||||
log_error("[VMX] vmptrld failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 vmx_vmcs_read(u64 field) {
|
||||
u64 value;
|
||||
__asm__ volatile(
|
||||
"vmread %1, %0\n"
|
||||
: "=r"(value)
|
||||
: "r"(field)
|
||||
: "memory"
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
void vmx_vmcs_write(u64 field, u64 value) {
|
||||
__asm__ volatile(
|
||||
"vmwrite %1, %0\n"
|
||||
:: "r"(field), "r"(value)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
int vmx_vmlaunch(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmlaunch\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
int vmx_vmresume(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmresume\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 暴露给其它模块用的辅助 */
|
||||
u64 vmx_get_host_cr3(void) { return read_cr3(); }
|
||||
u64 vmx_get_host_rflags(void) { return read_rflags(); }
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* ===== MSR / CR helpers ===== */
|
||||
|
||||
u64 vmx_read_msr(u32 msr) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
|
||||
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
||||
u32 hi = (u32)(value >> 32);
|
||||
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
|
||||
}
|
||||
|
||||
static inline u64 read_cr0(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr0, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_cr4(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr4, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void write_cr0(u64 v) {
|
||||
__asm__ volatile("mov %0, %%cr0" :: "r"(v));
|
||||
}
|
||||
static inline void write_cr4(u64 v) {
|
||||
__asm__ volatile("mov %0, %%cr4" :: "r"(v));
|
||||
}
|
||||
|
||||
static inline u64 read_cr3(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr3, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_rflags(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("pushfq; popq %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
/* ===== CPUID ===== */
|
||||
|
||||
static void cpuid(u32 leaf, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) {
|
||||
__asm__ volatile("cpuid"
|
||||
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
|
||||
: "a"(leaf));
|
||||
}
|
||||
|
||||
int vmx_supported(void) {
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
return (ecx & (1U << 5)) != 0; /* CPUID.1:ECX[5] = VMX */
|
||||
}
|
||||
|
||||
/* ===== VMXON region ===== */
|
||||
|
||||
static u64 g_vmxon_phys;
|
||||
static u64 g_vmcs_revision;
|
||||
static u64 g_vmx_basic;
|
||||
static int g_vmx_enabled;
|
||||
|
||||
/* VMX 要求 CR0 固定位必须为 1(VMX operation 不允许清除)。
|
||||
* 这些位由 IA32_VMX_BASIC MSR 的高 32 位(CR0 fixed-0)和
|
||||
* IA32_VMX_CR0_FIXED1 MSR 决定。简化:直接 OR 上 PE/NE/PG/WP。 */
|
||||
static u64 vmx_required_cr0(void) {
|
||||
return read_cr0() | CR0_PE | CR0_NE | CR0_WP | CR0_PG;
|
||||
}
|
||||
|
||||
static u64 vmx_required_cr4(void) {
|
||||
return read_cr4() | CR4_VMXE | CR4_PAE | CR4_PGE;
|
||||
}
|
||||
|
||||
/* Check whether we are running under a hypervisor that does NOT support
|
||||
* nested VMX. This covers:
|
||||
* - WHPX: CPUID may report VMX but VMXON/CR4.VMXE causes fatal exit
|
||||
* - Any hypervisor without nested virtualization support
|
||||
*
|
||||
* Detection strategy:
|
||||
* 1. Check CPUID.01H:ECX[31] (hypervisor present bit)
|
||||
* 2. If hypervisor present, check CPUID.40000000H for vendor signature
|
||||
* 3. Try reading IA32_VMX_BASIC MSR — if it returns 0, VMX is not real
|
||||
* 4. Check Hyper-V specific: HV_CPU_MANAGEMENT_FEATURES for nested VMX
|
||||
*
|
||||
* If VMX CPUID flag is set but we're under a hypervisor that doesn't expose
|
||||
* real VMX MSRs, we must not attempt CR4.VMXE or VMXON, as this will cause
|
||||
* a fatal VM exit in WHPX (exit code 4).
|
||||
*/
|
||||
static int vmx_truly_available(void) {
|
||||
/* Step 1: If CPUID doesn't even report VMX, definitely not available */
|
||||
if (!vmx_supported()) return 0;
|
||||
|
||||
/* Step 2: Check if running under a hypervisor */
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
if (!(ecx & (1U << 31))) {
|
||||
/* No hypervisor — bare metal, VMX should work */
|
||||
return 1;
|
||||
}
|
||||
log_info("[VMX] hypervisor present bit set, checking nested VMX support");
|
||||
|
||||
/* Step 3: Try reading IA32_VMX_BASIC — if it's zero or faults,
|
||||
* the hypervisor doesn't expose real VMX. */
|
||||
/* We use a safe read: if the MSR doesn't exist, rdmsr would #GP.
|
||||
* However, under WHPX, IA32_VMX_BASIC may return a non-zero value
|
||||
* even though VMXON will fail. So we need another check. */
|
||||
|
||||
/* Step 4: Check hypervisor vendor for nested VMX support */
|
||||
cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
|
||||
|
||||
/* P8.3 Fix: diagnostic logging of hypervisor vendor */
|
||||
{
|
||||
char hv[13];
|
||||
hv[0] = (char)(ebx & 0xFF); hv[1] = (char)((ebx >> 8) & 0xFF);
|
||||
hv[2] = (char)((ebx >> 16) & 0xFF); hv[3] = (char)((ebx >> 24) & 0xFF);
|
||||
hv[4] = (char)(ecx & 0xFF); hv[5] = (char)((ecx >> 8) & 0xFF);
|
||||
hv[6] = (char)((ecx >> 16) & 0xFF); hv[7] = (char)((ecx >> 24) & 0xFF);
|
||||
hv[8] = (char)(edx & 0xFF); hv[9] = (char)((edx >> 8) & 0xFF);
|
||||
hv[10] = (char)((edx >> 16) & 0xFF); hv[11] = (char)((edx >> 24) & 0xFF);
|
||||
hv[12] = 0;
|
||||
log_info("[VMX] hypervisor vendor: ");
|
||||
log_info(hv);
|
||||
}
|
||||
|
||||
/* Check for Hyper-V ("Microsoft Hv") */
|
||||
if (ebx == 0x7263694DU && ecx == 0x666F736FU && edx == 0x76482074U) {
|
||||
log_info("[VMX] Hyper-V detected");
|
||||
/* Check if HV_CPU_MANAGEMENT_FEATURES (MSR 0x4000000F) exists.
|
||||
* Hyper-V CPUID leaf 0x40000003 tells us which MSRs are available.
|
||||
* Partition_privileges bit 1 (nested virtualization) is at EBX bit 1
|
||||
* of CPUID 0x40000000 + 3 = 0x40000003.
|
||||
* But safer: just check the max CPUID leaf for Hyper-V. */
|
||||
u32 hv_max_leaf = eax;
|
||||
if (hv_max_leaf >= 0x40000003) {
|
||||
/* CPUID 0x40000003 EBX bit 1 = AccessIntrCtrl (nested) */
|
||||
cpuid(0x40000003, &eax, &ebx, &ecx, &edx);
|
||||
if (ebx & (1U << 1)) {
|
||||
log_info("[VMX] Hyper-V nested virtualization supported");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
log_warn("[VMX] Hyper-V WITHOUT nested VMX support");
|
||||
log_warn("[VMX] CR4.VMXE / VMXON would cause fatal exit, skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* P8.3 Fix: Check for KVM ("KVMKVMKVM"). Match ebx+ecx only
|
||||
* (edx was wrongly 0x4D564B4D, should be 0x0000004D for "M\0\0\0"). */
|
||||
if (ebx == 0x4B4D564BU && ecx == 0x564B4D56U) {
|
||||
log_info("[VMX] KVM detected - nested VMX may be available");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* P8.3 Fix: Unknown hypervisor - try IA32_VMX_BASIC MSR. */
|
||||
{
|
||||
u64 vmx_basic = vmx_read_msr(IA32_VMX_BASIC);
|
||||
if (vmx_basic != 0) {
|
||||
log_info("[VMX] unknown hypervisor but IA32_VMX_BASIC non-zero, attempting");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
log_warn("[VMX] unknown hypervisor, IA32_VMX_BASIC=0, VMX not available");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_enable(void) {
|
||||
if (g_vmx_enabled) return 0;
|
||||
|
||||
if (!vmx_truly_available()) {
|
||||
log_warn("[VMX] VMX not truly available, skipping VMX init");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 读 IA32_VMX_BASIC,获取 VMCS revision ID 与 region 大小 */
|
||||
g_vmx_basic = vmx_read_msr(IA32_VMX_BASIC);
|
||||
g_vmcs_revision = g_vmx_basic & 0x7FFFFFFFULL;
|
||||
u32 vmcs_size = (u32)((g_vmx_basic >> 32) & 0x1FFF);
|
||||
if (vmcs_size == 0) vmcs_size = 4096;
|
||||
|
||||
log_hex64("[VMX] VMX_BASIC=", g_vmx_basic);
|
||||
log_hex64("[VMX] VMCS revision=", g_vmcs_revision);
|
||||
|
||||
/* 分配 VMXON region(4KB 对齐,物理页) */
|
||||
dkm_dma_buffer vmxon_buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &vmxon_buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMXON region");
|
||||
return -2;
|
||||
}
|
||||
g_vmxon_phys = vmxon_buf.phys;
|
||||
|
||||
/* 在 VMXON region 偏移 0 写入 VMCS revision ID */
|
||||
u32 *vmxon_ptr = (u32 *)vmxon_buf.virt;
|
||||
vmxon_ptr[0] = (u32)g_vmcs_revision;
|
||||
log_hex64("[VMX] VMXON phys=", g_vmxon_phys);
|
||||
|
||||
/* 设置 CR4.VMXE */
|
||||
u64 cr4 = vmx_required_cr4();
|
||||
write_cr4(cr4);
|
||||
log_hex64("[VMX] CR4=", cr4);
|
||||
|
||||
/* 确保 CR0 满足 VMX 要求(这里只读不写,UEFI 已配置好) */
|
||||
/* P8.3 Fix: ensure CR0 meets VMX requirements before VMXON */
|
||||
u64 cr0 = vmx_required_cr0();
|
||||
write_cr0(cr0);
|
||||
log_hex64("[VMX] CR0=", cr0);
|
||||
|
||||
/* 执行 vmxon */
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmxon %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(g_vmxon_phys)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
if (err) {
|
||||
/* 第二次尝试:读 vmx instruction error 通过 VMCS 不适用 vmxon;
|
||||
* 简化:返回失败。 */
|
||||
log_error("[VMX] vmxon failed");
|
||||
return -3;
|
||||
}
|
||||
|
||||
g_vmx_enabled = 1;
|
||||
log_info("[VMX] VMX root mode enabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_disable(void) {
|
||||
if (!g_vmx_enabled) return 0;
|
||||
__asm__ volatile("vmxoff" ::: "memory");
|
||||
g_vmx_enabled = 0;
|
||||
/* 清 CR4.VMXE */
|
||||
u64 cr4 = read_cr4();
|
||||
cr4 &= ~CR4_VMXE;
|
||||
write_cr4(cr4);
|
||||
log_info("[VMX] VMX root mode disabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_alloc(u64 *phys_out) {
|
||||
if (!phys_out) return -1;
|
||||
dkm_dma_buffer buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMCS region");
|
||||
return -2;
|
||||
}
|
||||
/* 偏移 0 写入 VMCS revision ID */
|
||||
u32 *p = (u32 *)buf.virt;
|
||||
p[0] = (u32)g_vmcs_revision;
|
||||
*phys_out = buf.phys;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_load(u64 phys) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmptrld %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(phys)
|
||||
: "memory"
|
||||
);
|
||||
if (err) {
|
||||
log_error("[VMX] vmptrld failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* P8.4: VMCLEAR - put VMCS in "clear" state (required before VMLAUNCH). */
|
||||
int vmx_vmcs_clear(u64 phys) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmclear %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(phys)
|
||||
: "memory"
|
||||
);
|
||||
if (err) {
|
||||
log_error("[VMX] vmclear failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 vmx_vmcs_read(u64 field) {
|
||||
u64 value;
|
||||
__asm__ volatile(
|
||||
"vmread %1, %0\n"
|
||||
: "=r"(value)
|
||||
: "r"(field)
|
||||
: "memory"
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
void vmx_vmcs_write(u64 field, u64 value) {
|
||||
__asm__ volatile(
|
||||
"vmwrite %1, %0\n"
|
||||
:: "r"(field), "r"(value)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
int vmx_vmlaunch(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmlaunch\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
int vmx_vmresume(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmresume\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 暴露给其它模块用的辅助 */
|
||||
u64 vmx_get_host_cr3(void) { return read_cr3(); }
|
||||
u64 vmx_get_host_rflags(void) { return read_rflags(); }
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
/* xj380_boot.c — VMCS configuration and vmlaunch for OpenXJ380 guest.
|
||||
*
|
||||
* 参照 linux_boot.c 模式。XJ380 内核入口 KernelMain 是 C++ extern "C",
|
||||
* 以 SysV ABI 传 3 个引用参数(退化为指针):
|
||||
* RDI = &FrameBufferConfig (HHDM 虚拟地址)
|
||||
* RSI = &EFI_SYSTEM_TABLE (伪表,RuntimeServices=NULL,内核仅存指针)
|
||||
* RDX = &BOOT_CONFIG (HHDM 虚拟地址)
|
||||
* RIP = 0xFFFFFFFF80000000(guest 虚拟地址,由 guest 页表内核区映射)。
|
||||
*
|
||||
* guest 终止(异常/三重故障/0xCF9 重启)后本函数返回,host 恢复。
|
||||
*/
|
||||
|
||||
#include <utsm/xj380_loader.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* VM-Exit 汇编入口与 C 分发器(vmm.c / vmexit_asm.S) */
|
||||
extern void vmx_vm_exit_handler(void);
|
||||
extern int vmexit_dispatch(void);
|
||||
extern u64 vmexit_get_count(void);
|
||||
|
||||
extern u8 g_host_stack[8192];
|
||||
|
||||
/* 当前运行的 guest 是否为 XJ380(vmm.h 声明,vmexit.c handle_hlt 检查)。
|
||||
* 定义在本模块:XJ380 launch 置 1,guest 终止后清 0。 */
|
||||
volatile int g_xj380_guest_active;
|
||||
|
||||
/* CR 辅助 */
|
||||
static inline u64 read_cr0_local(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr0, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
static inline u64 read_cr3_local(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr3, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
static inline u64 read_cr4_local(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr4, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static u64 vmx_adjust_control(u64 value, u32 msr) {
|
||||
u32 allowed0 = (u32)vmx_read_msr(msr);
|
||||
u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32);
|
||||
u32 adjusted = (u32)value | allowed0;
|
||||
adjusted &= allowed1;
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
/* MSR bitmap(4KB,全零 = 不拦截任何 MSR,与 Linux guest 一致) */
|
||||
static u8 g_xj380_msr_bitmap[4096] __attribute__((aligned(4096)));
|
||||
|
||||
/* ===== VMCS host state ===== */
|
||||
|
||||
static void xj380_vmcs_setup_host_state(void) {
|
||||
vmx_vmcs_write(VMCS_HOST_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG);
|
||||
vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local());
|
||||
vmx_vmcs_write(VMCS_HOST_CR4, read_cr4_local() | CR4_VMXE);
|
||||
|
||||
vmx_vmcs_write(VMCS_HOST_CS_SELECTOR, 0x08);
|
||||
vmx_vmcs_write(VMCS_HOST_SS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_HOST_DS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_HOST_ES_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_HOST_FS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_GS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_FS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_GS_BASE, 0);
|
||||
|
||||
{
|
||||
struct __attribute__((packed)) { u16 limit; u64 base; } gdtr, idtr;
|
||||
__asm__ volatile("sgdt %0" : "=m"(gdtr));
|
||||
__asm__ volatile("sidt %0" : "=m"(idtr));
|
||||
vmx_vmcs_write(VMCS_HOST_GDTR_BASE, gdtr.base);
|
||||
vmx_vmcs_write(VMCS_HOST_IDTR_BASE, idtr.base);
|
||||
|
||||
u16 tr_sel;
|
||||
u64 tr_base;
|
||||
__asm__ volatile("str %0" : "=r"(tr_sel));
|
||||
u64 gdt_base = gdtr.base;
|
||||
u64 *tss_desc = (u64 *)(gdt_base + (tr_sel & 0xFFF8));
|
||||
u64 low = tss_desc[0];
|
||||
u64 high = tss_desc[1];
|
||||
tr_base = (low >> 16) & 0xFFFFFF;
|
||||
tr_base |= ((low >> 32) & 0xFF) << 24;
|
||||
tr_base |= (high & 0xFFFFFFFF) << 32;
|
||||
vmx_vmcs_write(VMCS_HOST_TR_SELECTOR, tr_sel);
|
||||
vmx_vmcs_write(VMCS_HOST_TR_BASE, tr_base);
|
||||
}
|
||||
|
||||
{
|
||||
u64 sysenter_cs;
|
||||
__asm__ volatile("rdmsr" : "=a"(sysenter_cs) : "c"(0x174));
|
||||
vmx_vmcs_write(VMCS_HOST_SYSENTER_CS, sysenter_cs & 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_HOST_SYSENTER_ESP, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_SYSENTER_EIP, 0);
|
||||
}
|
||||
|
||||
{
|
||||
u32 low, high;
|
||||
__asm__ volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(0xC0000080));
|
||||
u64 host_efer = ((u64)high << 32) | low;
|
||||
vmx_vmcs_write(VMCS_HOST_IA32_EFER, host_efer);
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_HOST_RSP, (u64)&g_host_stack[sizeof(g_host_stack) - 16]);
|
||||
vmx_vmcs_write(VMCS_HOST_RIP, (u64)vmx_vm_exit_handler);
|
||||
}
|
||||
|
||||
/* ===== VMCS guest state ===== */
|
||||
|
||||
static void xj380_vmcs_setup_guest_state(const struct xj380_guest_info *gi) {
|
||||
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_gpa);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_SELECTOR, 0x18);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_BASE, gi->gdt_gpa + 0x100);
|
||||
vmx_vmcs_write(VMCS_GUEST_GDTR_BASE, gi->gdt_gpa);
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_LIMIT, 0xFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_LIMIT, 0x67);
|
||||
vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28);
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); /* 64 位代码 L=1 */
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82); /* unusable */
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */
|
||||
|
||||
/* guest RIP = XJ380 内核入口(guest 虚拟地址,经内核区页表映射) */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, gi->kernel_entry);
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, XJ380_BOOT_STACK_TOP);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_RFLAGS, 0x2); /* IF=0 */
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_IA32_EFER, EFER_LME | EFER_LMA | EFER_NXE);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_SYSENTER_CS, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_SYSENTER_ESP, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_SYSENTER_EIP, 0);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_DR7, 0x400);
|
||||
vmx_vmcs_write(VMCS_GUEST_ACTIVITY_STATE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_INTERRUPTIBILITY, 0);
|
||||
}
|
||||
|
||||
/* ===== VMCS controls ===== */
|
||||
|
||||
static void xj380_vmcs_setup_controls(u64 eptp) {
|
||||
u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING;
|
||||
if (vmx_preemption_timer_supported()) {
|
||||
pin_want |= PIN_VMX_PREEMPTION_TIMER;
|
||||
}
|
||||
u64 pin = vmx_adjust_control(pin_want, IA32_VMX_TRUE_PINBASED_CTLS);
|
||||
|
||||
u64 cpu = CPU_BASED_HLT_EXITING
|
||||
| CPU_BASED_ACTIVATE_SECONDARY
|
||||
| CPU_BASED_USE_MSR_BITMAPS
|
||||
| CPU_BASED_UNCOND_IO_EXITING
|
||||
| CPU_BASED_INVLPG_EXITING;
|
||||
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
||||
|
||||
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
|
||||
{
|
||||
u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2);
|
||||
u32 allowed1 = (u32)(msr >> 32);
|
||||
cpu2 &= allowed1;
|
||||
cpu2 |= SEC_EXEC_ENABLE_EPT;
|
||||
}
|
||||
|
||||
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
||||
| VM_EXIT_LOAD_HOST_EFER;
|
||||
exit_ctrl = vmx_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS);
|
||||
|
||||
u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS
|
||||
| VM_ENTRY_IA32E_MODE_GUEST
|
||||
| VM_ENTRY_LOAD_GUEST_EFER;
|
||||
entry_ctrl = vmx_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS);
|
||||
|
||||
vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin);
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu);
|
||||
vmx_vmcs_write(VMCS_SECONDARY_VM_EXEC_CONTROL, cpu2);
|
||||
vmx_vmcs_write(VMCS_VM_EXIT_CONTROLS, exit_ctrl);
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_CONTROLS, entry_ctrl);
|
||||
|
||||
{
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
u64 msr_bitmap_phys = (u64)g_xj380_msr_bitmap - hhdm;
|
||||
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
||||
vmx_vmcs_write(VMCS_VPID, 3); /* VPID 1=self-test, 2=Linux, 3=XJ380 */
|
||||
|
||||
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6) | (1ULL << 8));
|
||||
}
|
||||
|
||||
/* ===== 传给 vmlaunch asm 的参数 ===== */
|
||||
static volatile u64 g_xj380_rdi;
|
||||
static volatile u64 g_xj380_rsi;
|
||||
static volatile u64 g_xj380_rdx;
|
||||
|
||||
/* ===== XJ380 launch =====
|
||||
*
|
||||
* 前置条件:vmm_init() + xj380_loader_init()。
|
||||
* guest 终止后返回 0;vmlaunch 失败返回负值。 */
|
||||
int xj380_launch(void) {
|
||||
const struct xj380_guest_info *gi = xj380_get_guest_info();
|
||||
if (!gi || !gi->loaded) {
|
||||
log_error("[XJ380] guest not loaded");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!vmm_is_ready()) {
|
||||
log_error("[XJ380] VMM not ready");
|
||||
return -2;
|
||||
}
|
||||
|
||||
log_info("[XJ380] launch begin");
|
||||
|
||||
/* 真机 EPT TLB 刷新:vmlaunch 前确保 EPT 映射可见 */
|
||||
ept_flush_ept();
|
||||
|
||||
/* 重置 VM-Exit 状态 */
|
||||
g_guest_terminated = 0;
|
||||
g_last_exit_reason = 0xFFFFFFFFULL;
|
||||
g_linux_guest_active = 0; /* XJ380 是当前 active guest */
|
||||
g_guest_parked = 0;
|
||||
g_xj380_guest_active = 1;
|
||||
|
||||
/* 配置 VMCS */
|
||||
xj380_vmcs_setup_guest_state(gi);
|
||||
xj380_vmcs_setup_controls(vmm_get_eptp());
|
||||
xj380_vmcs_setup_host_state();
|
||||
|
||||
/* 初始 arm preemption timer(1ms 后首次周期 exit) */
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
|
||||
/* KernelMain 3 个 SysV 参数(HHDM 虚拟地址) */
|
||||
g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET;
|
||||
g_xj380_rsi = XJ380_FAKEEFI_GPA + XJ380_HHDM_OFFSET;
|
||||
g_xj380_rdx = XJ380_BOOTCFG_GPA + XJ380_HHDM_OFFSET;
|
||||
|
||||
log_hex64("[XJ380] RIP=", gi->kernel_entry);
|
||||
log_hex64("[XJ380] RSP=", XJ380_BOOT_STACK_TOP);
|
||||
log_hex64("[XJ380] RDI(fbc)=", g_xj380_rdi);
|
||||
log_hex64("[XJ380] RSI(efi)=", g_xj380_rsi);
|
||||
log_hex64("[XJ380] RDX(bootcfg)=", g_xj380_rdx);
|
||||
log_hex64("[XJ380] CR3=", gi->pgt_gpa);
|
||||
log_hex64("[XJ380] GDTR=", gi->gdt_gpa);
|
||||
log_info("[XJ380] vmlaunch (host suspended until guest terminates)");
|
||||
|
||||
int failed;
|
||||
__asm__ volatile(
|
||||
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t"
|
||||
"leaq 1f(%%rip), %%rax\n\t"
|
||||
"movq %%rax, g_saved_return_rip(%%rip)\n\t"
|
||||
"movq g_xj380_rdi(%%rip), %%rdi\n\t"
|
||||
"movq g_xj380_rsi(%%rip), %%rsi\n\t"
|
||||
"movq g_xj380_rdx(%%rip), %%rdx\n\t"
|
||||
"vmlaunch\n\t"
|
||||
/* ---- failure path ---- */
|
||||
"movl $1, %0\n\t"
|
||||
"jmp 2f\n\t"
|
||||
/* ---- post_guest: terminate path jmps here ---- */
|
||||
"1:\n\t"
|
||||
"movl $0, %0\n\t"
|
||||
"2:\n\t"
|
||||
: "=r"(failed)
|
||||
:: "rax", "rcx", "rdx", "rsi", "rdi",
|
||||
"r8", "r9", "r10", "r11", "memory"
|
||||
);
|
||||
|
||||
if (failed) {
|
||||
u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR);
|
||||
log_hex64("[XJ380] vmlaunch failed, error=", error);
|
||||
g_xj380_guest_active = 0;
|
||||
return -3;
|
||||
}
|
||||
|
||||
/* guest 终止,host 恢复 */
|
||||
g_xj380_guest_active = 0;
|
||||
log_info("[XJ380] guest terminated, host resumed");
|
||||
log_hex64("[XJ380] last exit reason=", g_last_exit_reason);
|
||||
log_hex64("[XJ380] vmexit count=", vmexit_get_count());
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
vmm/xj380_boot.o: vmm\xj380_boot.c include\utsm\xj380_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\log.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\xj380_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -0,0 +1,712 @@
|
||||
/* xj380_loader.c — OpenXJ380 kernel.krl loader for UTSM VMX non-root guest.
|
||||
*
|
||||
* 参照 linux_loader.c 的既有模式实现 XJ380 第二 guest:
|
||||
* 1. 在 Limine boot module 中查找 kernel.krl(路径含 "xj380")
|
||||
* 2. 解析 ELF64 PT_LOAD 段(固定链接 0xFFFFFFFF80000000)
|
||||
* 3. 分配 host 物理内存:boot area / framebuffer / kernel / RAM
|
||||
* 4. 构造 guest 初始页表(恒等 0-4GB + HHDM + 内核区三区布局)
|
||||
* 5. 构造 guest GDT(64 位 code/data/TSS)
|
||||
* 6. 构造 ACPI 桩表 MADT/FADT/MCFG/HPET(幽灵设备地址,EPT 按需分配)
|
||||
* 7. 构造 EFI 格式内存图(单一 Conventional 区 = guest RAM)
|
||||
* 8. 构造 BOOT_CONFIG / FrameBufferConfig / 伪 EFI_SYSTEM_TABLE
|
||||
* 9. EPT 映射所有 guest 区域
|
||||
*
|
||||
* VMCS 配置与 vmlaunch 见 xj380_boot.c。
|
||||
*/
|
||||
|
||||
#include <utsm/xj380_loader.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_module_request g_module_request;
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* ===== 运行期辅助 ===== */
|
||||
|
||||
static void mem_copy(void *dst, const void *src, u64 n) {
|
||||
u8 *d = (u8 *)dst;
|
||||
const u8 *s = (const u8 *)src;
|
||||
for (u64 i = 0; i < n; i++) d[i] = s[i];
|
||||
}
|
||||
|
||||
static void mem_zero(void *dst, u64 n) {
|
||||
u8 *d = (u8 *)dst;
|
||||
for (u64 i = 0; i < n; i++) d[i] = 0;
|
||||
}
|
||||
|
||||
static u64 hhdm(void) {
|
||||
return g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
}
|
||||
|
||||
static void *to_virt(u64 hpa) {
|
||||
return (void *)(hpa + hhdm());
|
||||
}
|
||||
|
||||
/* ===== ELF64 解析(kernel.krl 为 ET_EXEC) ===== */
|
||||
|
||||
#define ELF_MAGIC 0x464C457FULL /* "\x7fELF" */
|
||||
#define ELF_ET_EXEC 2
|
||||
#define ELF_PT_LOAD 1
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry;
|
||||
u64 e_phoff;
|
||||
u64 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
} xj_elf64_ehdr;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u32 p_type;
|
||||
u32 p_flags;
|
||||
u64 p_offset;
|
||||
u64 p_vaddr;
|
||||
u64 p_paddr;
|
||||
u64 p_filesz;
|
||||
u64 p_memsz;
|
||||
u64 p_align;
|
||||
} xj_elf64_phdr;
|
||||
|
||||
/* 解析 ELF:校验头、遍历 PT_LOAD 计算内核跨度,记录入口。
|
||||
* 返回 0 成功;负值失败。 */
|
||||
static int xj380_parse_elf(const void *img, u64 size,
|
||||
u64 *entry_out, u64 *span_out) {
|
||||
if (size < sizeof(xj_elf64_ehdr)) {
|
||||
log_error("[XJ380] kernel.krl too small");
|
||||
return -1;
|
||||
}
|
||||
const xj_elf64_ehdr *eh = (const xj_elf64_ehdr *)img;
|
||||
if (*(u32 *)eh->e_ident != (u32)ELF_MAGIC) {
|
||||
log_error("[XJ380] bad ELF magic");
|
||||
return -2;
|
||||
}
|
||||
if (eh->e_ident[4] != 2) { /* ELFCLASS64 */
|
||||
log_error("[XJ380] not a 64-bit ELF");
|
||||
return -3;
|
||||
}
|
||||
if (eh->e_type != ELF_ET_EXEC) {
|
||||
log_error("[XJ380] kernel.krl not ET_EXEC");
|
||||
return -4;
|
||||
}
|
||||
if (eh->e_phnum == 0 || eh->e_phentsize != sizeof(xj_elf64_phdr) ||
|
||||
eh->e_phoff + (u64)eh->e_phnum * sizeof(xj_elf64_phdr) > size) {
|
||||
log_error("[XJ380] bad program header table");
|
||||
return -5;
|
||||
}
|
||||
|
||||
u64 span = 0;
|
||||
const xj_elf64_phdr *ph = (const xj_elf64_phdr *)((const u8 *)img + eh->e_phoff);
|
||||
for (u16 i = 0; i < eh->e_phnum; i++) {
|
||||
if (ph[i].p_type != ELF_PT_LOAD) continue;
|
||||
if (ph[i].p_vaddr < XJ380_KERNEL_VADDR) {
|
||||
log_error("[XJ380] PT_LOAD below kernel base");
|
||||
log_hex64("[XJ380] vaddr=", ph[i].p_vaddr);
|
||||
return -6;
|
||||
}
|
||||
u64 off = ph[i].p_vaddr - XJ380_KERNEL_VADDR;
|
||||
if (off + ph[i].p_memsz > XJ380_GUEST_KERNEL_MAX) {
|
||||
log_error("[XJ380] kernel exceeds reserved window (32MB)");
|
||||
return -7;
|
||||
}
|
||||
if (ph[i].p_offset + ph[i].p_filesz > size) {
|
||||
log_error("[XJ380] PT_LOAD filesz beyond image");
|
||||
return -8;
|
||||
}
|
||||
if (off + ph[i].p_memsz > span) span = off + ph[i].p_memsz;
|
||||
}
|
||||
if (span == 0) {
|
||||
log_error("[XJ380] no PT_LOAD segments");
|
||||
return -9;
|
||||
}
|
||||
|
||||
if (entry_out) *entry_out = eh->e_entry;
|
||||
if (span_out) *span_out = span;
|
||||
log_hex64("[XJ380] ELF entry=", eh->e_entry);
|
||||
log_hex64("[XJ380] kernel span=", span);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Guest 状态 ===== */
|
||||
|
||||
static struct xj380_guest_info g_guest;
|
||||
|
||||
/* ===== Guest 页表(三区布局,同 XJ380 引导器契约) =====
|
||||
*
|
||||
* PML4 @ pgt_gpa + 0x0000
|
||||
* [0] → PDPT0 恒等映射 0-4GB(早期直读 MADT 等,内核自清低半)
|
||||
* [256] → PDPT0 HHDM 映射 0xFFFF800000000000 + [0,4GB)
|
||||
* [511] → PDPT_K 内核区 0xFFFFFF8000000000+
|
||||
* PDPT0 @ pgt_gpa + 0x1000,[0..3] → PD0..PD3(每页覆盖 1GB,2MB 大页)
|
||||
* PD0-3 @ pgt_gpa + 0x2000..0x5000
|
||||
* PDPT_K@ pgt_gpa + 0x6000,[480] → PD_K(0xFFFFFFFF80000000 区间)
|
||||
* PD_K @ pgt_gpa + 0x7000,[0..n] 2MB 大页映射内核 GPA
|
||||
*/
|
||||
#define XJ_PGT_PDPT0_OFF 0x1000
|
||||
#define XJ_PGT_PD0_OFF 0x2000
|
||||
#define XJ_PGT_PDPTK_OFF 0x6000
|
||||
#define XJ_PGT_PDK_OFF 0x7000
|
||||
|
||||
/* x86-64 页表项位 */
|
||||
#define XJ_PTE_P (1ULL << 0)
|
||||
#define XJ_PTE_RW (1ULL << 1)
|
||||
#define XJ_PTE_PS (1ULL << 7)
|
||||
|
||||
static int xj380_build_page_tables(u64 pgt_hpa, u64 kernel_gpa, u64 kernel_span) {
|
||||
u64 *pml4 = (u64 *)to_virt(pgt_hpa);
|
||||
mem_zero(pml4, XJ380_PGT_SIZE);
|
||||
|
||||
u64 pdpt0_gpa = g_guest.pgt_gpa + XJ_PGT_PDPT0_OFF;
|
||||
u64 pdptk_gpa = g_guest.pgt_gpa + XJ_PGT_PDPTK_OFF;
|
||||
|
||||
/* PML4[0] 恒等 + PML4[256] HHDM 指向同一个 PDPT0 */
|
||||
pml4[0] = pdpt0_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
pml4[256] = pdpt0_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
/* PML4[511] → PDPT_K */
|
||||
pml4[511] = pdptk_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
|
||||
/* PDPT0[0..3] → PD0..PD3(覆盖 0-4GB) */
|
||||
u64 *pdpt0 = (u64 *)((u8 *)pml4 + XJ_PGT_PDPT0_OFF);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
u64 pd_gpa = g_guest.pgt_gpa + XJ_PGT_PD0_OFF + (u64)i * 4096;
|
||||
pdpt0[i] = pd_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
}
|
||||
/* 每个 PD 512 项,每项 2MB 大页:GPA = i*1GB + j*2MB */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
u64 *pd = (u64 *)((u8 *)pml4 + XJ_PGT_PD0_OFF + (u64)i * 4096);
|
||||
for (int j = 0; j < 512; j++) {
|
||||
u64 page_gpa = (u64)i * (1ULL << 30) + (u64)j * (2ULL << 20);
|
||||
pd[j] = page_gpa | XJ_PTE_P | XJ_PTE_RW | XJ_PTE_PS;
|
||||
}
|
||||
}
|
||||
|
||||
/* PDPT_K[480] → PD_K(虚拟 0xFFFFFFFF80000000 落在 PML4[511] 第 480 个 1GB) */
|
||||
u64 *pdptk = (u64 *)((u8 *)pml4 + XJ_PGT_PDPTK_OFF);
|
||||
u64 pdk_gpa = g_guest.pgt_gpa + XJ_PGT_PDK_OFF;
|
||||
pdptk[480] = pdk_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
|
||||
/* PD_K[i]:虚拟 0xFFFFFFFF80000000 + i*2MB → GPA kernel_gpa + i*2MB */
|
||||
u64 *pdk = (u64 *)((u8 *)pml4 + XJ_PGT_PDK_OFF);
|
||||
u64 pages = (kernel_span + (2ULL << 20) - 1) >> 21;
|
||||
for (u64 i = 0; i < pages; i++) {
|
||||
u64 gpa = kernel_gpa + (i << 21);
|
||||
pdk[i] = gpa | XJ_PTE_P | XJ_PTE_RW | XJ_PTE_PS;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Guest GDT(与 linux_loader 相同的 64 位布局) ===== */
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u16 limit_low;
|
||||
u16 base_low;
|
||||
u8 base_mid;
|
||||
u8 access;
|
||||
u8 flags_limit_high;
|
||||
u8 base_high;
|
||||
} xj_gdt_entry64;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u32 limit_low_base_low;
|
||||
u8 base_mid;
|
||||
u8 access;
|
||||
u8 flags_limit_high;
|
||||
u8 base_high;
|
||||
u32 base_upper;
|
||||
u32 reserved;
|
||||
} xj_tss_desc64;
|
||||
|
||||
static int xj380_build_gdt(u64 gdt_hpa) {
|
||||
u8 *gdt_page = (u8 *)to_virt(gdt_hpa);
|
||||
mem_zero(gdt_page, 4096);
|
||||
|
||||
xj_gdt_entry64 *gdt = (xj_gdt_entry64 *)gdt_page;
|
||||
gdt[1].limit_low = 0xFFFF;
|
||||
gdt[1].access = 0x9B;
|
||||
gdt[1].flags_limit_high = 0xAF; /* G=1, L=1(64 位代码) */
|
||||
|
||||
gdt[2].limit_low = 0xFFFF;
|
||||
gdt[2].access = 0x93;
|
||||
gdt[2].flags_limit_high = 0xCF;
|
||||
|
||||
/* [0x18] 64 位 TSS 描述符,TSS 本体在同页 +0x100 */
|
||||
xj_tss_desc64 *tss_d = (xj_tss_desc64 *)&gdt_page[0x18];
|
||||
u64 tss_base = g_guest.gdt_gpa + 0x100;
|
||||
u32 tss_limit = 0x67;
|
||||
tss_d->limit_low_base_low = (tss_limit & 0xFFFF) | ((u32)(tss_base & 0xFFFFFF) << 16);
|
||||
tss_d->base_mid = (u8)((tss_base >> 24) & 0xFF);
|
||||
tss_d->access = 0x89;
|
||||
tss_d->flags_limit_high = (u8)((tss_limit >> 16) & 0x0F);
|
||||
tss_d->base_high = 0;
|
||||
tss_d->base_upper = 0;
|
||||
tss_d->reserved = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== ACPI 桩表 =====
|
||||
*
|
||||
* XJ380 初始化需要 MADT(APIC/LAPIC/IOAPIC)、FADT(未使用)、MCFG(PCIe)、
|
||||
* HPET(时钟)。所有设备地址均为"幽灵地址":EPT 未映射该区域时由
|
||||
* handle_ept_violation 按需分配真实页,guest 读写不崩溃。
|
||||
* LAPIC/IOAPIC/HPET/MMCONF 均落在低 4GB,恒等 + HHDM 双路可见。
|
||||
*/
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char signature[4];
|
||||
u32 length;
|
||||
u8 revision;
|
||||
u8 checksum;
|
||||
char oemid[6];
|
||||
char oemtableid[8];
|
||||
u32 oemrev;
|
||||
u32 creatorid;
|
||||
u32 creatorrev;
|
||||
} xj_acpi_header; /* 36 字节 */
|
||||
|
||||
static void acpi_set_header(xj_acpi_header *h, const char sig[4], u32 len) {
|
||||
mem_zero(h, len);
|
||||
h->signature[0] = sig[0];
|
||||
h->signature[1] = sig[1];
|
||||
h->signature[2] = sig[2];
|
||||
h->signature[3] = sig[3];
|
||||
h->length = len;
|
||||
h->revision = 1;
|
||||
mem_copy(h->oemid, "DESHAB", 6);
|
||||
mem_copy(h->oemtableid, "UTSMVMM", 8);
|
||||
h->oemrev = 1;
|
||||
mem_copy((char *)&h->creatorid, "UTSM", 4);
|
||||
h->creatorrev = 1;
|
||||
/* 校验和 = -sum(校验和字节置 0 后求和) */
|
||||
u32 sum = 0;
|
||||
u8 *p = (u8 *)h;
|
||||
for (u32 i = 0; i < len; i++) sum += p[i];
|
||||
h->checksum = (u8)(0 - (u8)sum);
|
||||
}
|
||||
|
||||
/* MADT:36 头 + LAPIC 地址(4) + 标志(4) + LAPIC 项(8) + IOAPIC 项(12) = 64 字节。
|
||||
* LAPIC 项:type=0, len=8, uid=0, id=0, flags=1(仅 enabled,不触发 AP 启动)
|
||||
* IOAPIC 项:type=1, len=12, id=0, addr=0xFEC00000(幽灵), gsib=0 */
|
||||
static int xj380_build_madt(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "APIC", 64);
|
||||
u8 *p = t + 36;
|
||||
p[0] = 0x00; p[1] = 0x00; p[2] = 0x00; p[3] = 0xF0; /* lapic addr = 0xFEE00000 */
|
||||
p[4] = 0x01; p[5] = 0x00; p[6] = 0x00; p[7] = 0x00; /* flags = PCAT_COMPAT */
|
||||
p += 8;
|
||||
p[0] = 0x00; /* type = LAPIC */
|
||||
p[1] = 0x08; /* len */
|
||||
p[2] = 0x00; /* ACPI processor uid */
|
||||
p[3] = 0x00; /* lapic id */
|
||||
p[4] = 0x01; p[5] = 0x00; p[6] = 0x00; p[7] = 0x00; /* flags = enabled only */
|
||||
p += 8;
|
||||
p[0] = 0x01; /* type = IOAPIC */
|
||||
p[1] = 0x0C; /* len */
|
||||
p[2] = 0x00; /* ioapic id */
|
||||
p[3] = 0x00; /* reserved */
|
||||
p[4] = 0x00; p[5] = 0x00; p[6] = 0xC0; p[7] = 0xFE; /* ioapic addr = 0xFEC00000 */
|
||||
p[8] = 0x00; p[9] = 0x00; p[10] = 0x00; p[11] = 0x00; /* gsib */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FADT:最小桩表(XJ380 仅在未接线的 power_shutdown 路径使用,启动不读) */
|
||||
static int xj380_build_fadt(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "FACP", 96);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* MCFG:36 头 + reserved(8) + 1 项(16) = 60 字节。
|
||||
* base=0xF0000000(幽灵 MMCONF),1 个 segment、1 条总线(扫描结果为无设备) */
|
||||
static int xj380_build_mcfg(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "MCFG", 60);
|
||||
u8 *p = t + 36 + 8;
|
||||
p[0] = 0x00; p[1] = 0x00; p[2] = 0x00; p[3] = 0x00;
|
||||
p[4] = 0x00; p[5] = 0x00; p[6] = 0xF0; p[7] = 0x0F; /* base = 0xF0000000 */
|
||||
p[8] = 0x00; p[9] = 0x00; /* segment group 0 */
|
||||
p[10] = 0x00; /* start bus 0 */
|
||||
p[11] = 0x00; /* end bus 0 */
|
||||
p[12] = 0x00; p[13] = 0x00; p[14] = 0x00; p[15] = 0x00; /* reserved */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* HPET:36 头 + event_block_id(4) + GAS(12) + tick_unit(2) + 属性(1) = 55 → 56。
|
||||
* base=0xFED00000(幽灵),generalCapabilities 读 0 → hpetPeriod=0,不挂起 */
|
||||
static int xj380_build_hpet(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "HPET", 56);
|
||||
u8 *p = t + 36;
|
||||
p[0] = 0x00; p[1] = 0x00; p[2] = 0x00; p[3] = 0x00; /* event_block_id */
|
||||
p[4] = 0x00; /* address_space = 系统内存 */
|
||||
p[5] = 0x20; /* bit_width = 64 */
|
||||
p[6] = 0x00; /* bit_offset */
|
||||
p[7] = 0x00; /* access_size */
|
||||
p[8] = 0x00; p[9] = 0x00; p[10] = 0x00; p[11] = 0x00;
|
||||
p[12] = 0x00; p[13] = 0xD0; p[14] = 0xFE; p[15] = 0x0F; /* base = 0xFED00000 */
|
||||
p[16] = 0x00; p[17] = 0x00; /* clock_tick_unit */
|
||||
p[18] = 0x00; /* page_oem_flags */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== EFI 格式内存图 =====
|
||||
*
|
||||
* 仅一个 Conventional 区(guest RAM),其余区域隐式保留。
|
||||
* EfiMemoryDesc:Type(4)+pad(4)+PhysicalStart(8)+VirtualStart(8)+
|
||||
* NumberOfPages(8)+Attribute(8),16 字节对齐 → 48 字节。
|
||||
*/
|
||||
typedef struct __attribute__((aligned(16))) {
|
||||
u32 type;
|
||||
u32 pad;
|
||||
u64 physical_start;
|
||||
u64 virtual_start;
|
||||
u64 number_of_pages;
|
||||
u64 attribute;
|
||||
} xj_efi_memdesc;
|
||||
|
||||
#define XJ_EFI_CONVENTIONAL 7
|
||||
#define XJ_EFI_DESC_SIZE (u64)sizeof(xj_efi_memdesc)
|
||||
#define XJ_EFI_MEM_ATTR 0xFULL
|
||||
|
||||
static int xj380_build_memmap(u64 hpa) {
|
||||
xj_efi_memdesc *d = (xj_efi_memdesc *)to_virt(hpa);
|
||||
mem_zero(d, sizeof(*d));
|
||||
d->type = XJ_EFI_CONVENTIONAL;
|
||||
d->physical_start = g_guest.ram_gpa;
|
||||
d->virtual_start = 0;
|
||||
d->number_of_pages = g_guest.ram_size / 4096;
|
||||
d->attribute = XJ_EFI_MEM_ATTR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== BOOT_CONFIG(布局必须与 OpenXJ380 boot/include/boot.h 一致) =====
|
||||
*
|
||||
* MEMORY_MAP 在 memory.h 中 #pragma pack(1):36 字节;BOOT_CONFIG 默认对齐。
|
||||
* 关键:MemoryMap.Buffer / saved_mtrrs 是【物理地址】(内核自行 +HHDM);
|
||||
* temp_stack[] 是【HHDM 虚拟地址】数组(仅拷贝,1 CPU 不 deref)。
|
||||
*/
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u64 map_size;
|
||||
u64 buffer; /* 物理 GPA */
|
||||
u64 map_key;
|
||||
u64 descriptor_size;
|
||||
u32 descriptor_version;
|
||||
} xj_memory_map; /* 36 字节 */
|
||||
|
||||
typedef struct {
|
||||
xj_memory_map memory_map;
|
||||
u64 fadt;
|
||||
u64 madt;
|
||||
u64 hpet;
|
||||
u64 mcfg;
|
||||
u64 *saved_mtrrs; /* 物理 GPA,内核 +HHDM */
|
||||
u64 *temp_stack[256]; /* HHDM 虚拟地址 */
|
||||
int is_qemu;
|
||||
u64 boot_flags;
|
||||
u64 installer_root_pak;
|
||||
u64 installer_root_pak_size;
|
||||
u64 system_payload_pak;
|
||||
u64 system_payload_pak_size;
|
||||
} xj_boot_config;
|
||||
|
||||
_Static_assert(sizeof(xj_memory_map) == 36, "xj_memory_map layout mismatch");
|
||||
_Static_assert(sizeof(xj_boot_config) == 2176, "xj_boot_config layout mismatch");
|
||||
|
||||
/* FrameBufferConfig(boot/include/fbc.h,默认对齐,24 字节) */
|
||||
typedef struct {
|
||||
u8 *frame_buffer; /* HHDM 虚拟地址 */
|
||||
u32 pixels_per_scan_line;
|
||||
u32 horizontal_resolution;
|
||||
u32 vertical_resolution;
|
||||
u32 pixel_format; /* 0=kRGBR, 1=kBGRR */
|
||||
} xj_framebuffer_config;
|
||||
_Static_assert(sizeof(xj_framebuffer_config) == 24, "fbc layout mismatch");
|
||||
|
||||
static int xj380_build_boot_config(u64 hpa, u64 mtrr_hpa, u64 stack_gpa, u64 fbc_gpa) {
|
||||
xj_boot_config *bc = (xj_boot_config *)to_virt(hpa);
|
||||
mem_zero(bc, sizeof(*bc));
|
||||
|
||||
bc->memory_map.map_size = XJ_EFI_DESC_SIZE; /* 单描述符 */
|
||||
bc->memory_map.buffer = XJ380_MEMMAP_GPA; /* 物理 GPA */
|
||||
bc->memory_map.map_key = 0;
|
||||
bc->memory_map.descriptor_size = XJ_EFI_DESC_SIZE;
|
||||
bc->memory_map.descriptor_version = 1;
|
||||
|
||||
bc->fadt = XJ380_FADT_GPA;
|
||||
bc->madt = XJ380_MADT_GPA;
|
||||
bc->hpet = XJ380_HPET_GPA;
|
||||
bc->mcfg = XJ380_MCFG_GPA;
|
||||
|
||||
bc->saved_mtrrs = (u64 *)mtrr_hpa; /* 物理 GPA */
|
||||
|
||||
u64 stack_hhdm = stack_gpa + XJ380_HHDM_OFFSET;
|
||||
for (int i = 0; i < 256; i++) bc->temp_stack[i] = (u64 *)stack_hhdm;
|
||||
|
||||
bc->is_qemu = 1;
|
||||
bc->boot_flags = XJ380_BOOT_FLAG_DISABLE_KMOD | XJ380_BOOT_FLAG_SAFE_STORAGE_IO;
|
||||
/* installer paks 均为 0(安装器模式关闭) */
|
||||
|
||||
/* FrameBufferConfig:frame_buffer 传 HHDM 虚拟地址,pixel_format=1(kBGRR) */
|
||||
xj_framebuffer_config *fbc = (xj_framebuffer_config *)to_virt(fbc_gpa);
|
||||
mem_zero(fbc, sizeof(*fbc));
|
||||
fbc->frame_buffer = (u8 *)(XJ380_GUEST_FB_GPA + XJ380_HHDM_OFFSET);
|
||||
fbc->pixels_per_scan_line = XJ380_FB_WIDTH;
|
||||
fbc->horizontal_resolution = XJ380_FB_WIDTH;
|
||||
fbc->vertical_resolution = XJ380_FB_HEIGHT;
|
||||
fbc->pixel_format = 1; /* kBGRR */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Module finder ===== */
|
||||
|
||||
void *xj380_find_kernel_module(u64 *size_out) {
|
||||
if (!g_module_request.response) {
|
||||
log_error("[XJ380] no Limine module response");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
/* 路径含 "xj380"(大小写不敏感) */
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
if ((s[0] == 'x' || s[0] == 'X') &&
|
||||
(s[1] == 'j' || s[1] == 'J') &&
|
||||
(s[2] == '3' || s[2] == '3') &&
|
||||
(s[3] == '8' || s[3] == '8') &&
|
||||
(s[4] == '0' || s[4] == '0')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[XJ380] found kernel module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[XJ380] module size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_warn("[XJ380] no kernel.krl module found");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== 主加载入口 ===== */
|
||||
|
||||
int xj380_loader_init(void) {
|
||||
log_info("[XJ380] loader init begin");
|
||||
|
||||
/* 1. 查找 kernel.krl 模块 */
|
||||
u64 krl_size = 0;
|
||||
void *krl = xj380_find_kernel_module(&krl_size);
|
||||
if (!krl) {
|
||||
log_warn("[XJ380] no kernel.krl found, skipping XJ380 guest");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 2. 解析 ELF */
|
||||
u64 entry, span;
|
||||
if (xj380_parse_elf(krl, krl_size, &entry, &span) != 0) {
|
||||
log_error("[XJ380] kernel.krl parse failed");
|
||||
return -2;
|
||||
}
|
||||
if (entry < XJ380_KERNEL_VADDR || entry >= XJ380_KERNEL_VADDR + span) {
|
||||
log_error("[XJ380] entry outside kernel window");
|
||||
return -3;
|
||||
}
|
||||
|
||||
/* 3. 分配 boot area 缓冲(按页) */
|
||||
dkm_dma_buffer pgt_buf, gdt_buf, madt_buf, fadt_buf, mcfg_buf, hpet_buf;
|
||||
dkm_dma_buffer memmap_buf, bootcfg_buf, mtrr_buf, stack_buf, fakeefi_buf, fbc_buf;
|
||||
if (dma_alloc_pages(8, 4096, 0, &pgt_buf) != 0) { log_error("[XJ380] alloc pgt failed"); return -4; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &gdt_buf) != 0) { log_error("[XJ380] alloc gdt failed"); return -5; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &madt_buf) != 0) { log_error("[XJ380] alloc madt failed"); return -6; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &fadt_buf) != 0) { log_error("[XJ380] alloc fadt failed"); return -7; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &mcfg_buf) != 0) { log_error("[XJ380] alloc mcfg failed"); return -8; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &hpet_buf) != 0) { log_error("[XJ380] alloc hpet failed"); return -9; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &memmap_buf) != 0) { log_error("[XJ380] alloc memmap failed"); return -10; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &bootcfg_buf) != 0) { log_error("[XJ380] alloc bootcfg failed"); return -11; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &mtrr_buf) != 0) { log_error("[XJ380] alloc mtrr failed"); return -12; }
|
||||
if (dma_alloc_pages(2, 4096, 0, &stack_buf) != 0) { log_error("[XJ380] alloc stack failed"); return -13; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &fakeefi_buf) != 0) { log_error("[XJ380] alloc fake efi failed"); return -14; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &fbc_buf) != 0) { log_error("[XJ380] alloc fbc failed"); return -15; }
|
||||
|
||||
/* 4. 分配内核缓冲并装入 PT_LOAD 段 */
|
||||
u64 kernel_alloc = (span + 4095) & ~4095ULL;
|
||||
dkm_dma_buffer kernel_buf;
|
||||
if (dma_alloc_pages(kernel_alloc / 4096, 2 * 1024 * 1024, 0, &kernel_buf) != 0) {
|
||||
log_error("[XJ380] failed to alloc kernel memory");
|
||||
return -16;
|
||||
}
|
||||
{
|
||||
const xj_elf64_ehdr *eh = (const xj_elf64_ehdr *)krl;
|
||||
const xj_elf64_phdr *ph = (const xj_elf64_phdr *)((const u8 *)krl + eh->e_phoff);
|
||||
u8 *dst = (u8 *)kernel_buf.virt;
|
||||
for (u16 i = 0; i < eh->e_phnum; i++) {
|
||||
if (ph[i].p_type != ELF_PT_LOAD) continue;
|
||||
u64 off = ph[i].p_vaddr - XJ380_KERNEL_VADDR;
|
||||
mem_copy(dst + off, (const u8 *)krl + ph[i].p_offset, ph[i].p_filesz);
|
||||
if (ph[i].p_memsz > ph[i].p_filesz)
|
||||
mem_zero(dst + off + ph[i].p_filesz, ph[i].p_memsz - ph[i].p_filesz);
|
||||
}
|
||||
log_hex64("[XJ380] kernel loaded HPA=", kernel_buf.phys);
|
||||
}
|
||||
|
||||
/* 5. 分配 framebuffer 并清零 */
|
||||
dkm_dma_buffer fb_buf;
|
||||
if (dma_alloc_pages((XJ380_FB_SIZE + 4095) / 4096, 4096, 0, &fb_buf) != 0) {
|
||||
log_error("[XJ380] failed to alloc framebuffer");
|
||||
return -17;
|
||||
}
|
||||
{
|
||||
u8 *fb = (u8 *)fb_buf.virt;
|
||||
for (u64 i = 0; i < XJ380_FB_SIZE; i++) fb[i] = 0;
|
||||
}
|
||||
|
||||
/* 6. 分配 guest RAM(1GB,降级 512MB/256MB) */
|
||||
u64 ram_size = XJ380_GUEST_RAM_SIZE;
|
||||
dkm_dma_buffer ram_buf;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_warn("[XJ380] failed to alloc guest RAM (1GB), trying 512MB");
|
||||
ram_size = 512 * 1024 * 1024;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_warn("[XJ380] failed to alloc guest RAM (512MB), trying 256MB");
|
||||
ram_size = XJ380_GUEST_RAM_MIN;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[XJ380] failed to alloc guest RAM (256MB minimum)");
|
||||
return -18;
|
||||
}
|
||||
}
|
||||
}
|
||||
log_hex64("[XJ380] guest RAM HPA=", ram_buf.phys);
|
||||
log_hex64("[XJ380] guest RAM size=", ram_size);
|
||||
|
||||
/* 7. 记录 guest 信息 */
|
||||
g_guest.pgt_gpa = XJ380_PGT_GPA;
|
||||
g_guest.gdt_gpa = XJ380_GDT_GPA;
|
||||
g_guest.kernel_gpa = XJ380_GUEST_KERNEL_GPA;
|
||||
g_guest.kernel_entry = entry;
|
||||
g_guest.kernel_size = span;
|
||||
g_guest.ram_gpa = XJ380_GUEST_RAM_GPA;
|
||||
g_guest.ram_size = ram_size;
|
||||
g_guest.fb_gpa = XJ380_GUEST_FB_GPA;
|
||||
|
||||
/* 8. 构造 guest 页表 / GDT */
|
||||
xj380_build_page_tables(pgt_buf.phys, g_guest.kernel_gpa, span);
|
||||
xj380_build_gdt(gdt_buf.phys);
|
||||
log_hex64("[XJ380] page tables HPA=", pgt_buf.phys);
|
||||
log_hex64("[XJ380] GDT HPA=", gdt_buf.phys);
|
||||
|
||||
/* 9. 构造 ACPI 桩表 */
|
||||
xj380_build_madt(madt_buf.phys);
|
||||
xj380_build_fadt(fadt_buf.phys);
|
||||
xj380_build_mcfg(mcfg_buf.phys);
|
||||
xj380_build_hpet(hpet_buf.phys);
|
||||
|
||||
/* 10. 构造内存图 + BOOT_CONFIG(含 MTRR 缓冲清零、伪 EFI 表清零) */
|
||||
xj380_build_memmap(memmap_buf.phys);
|
||||
mem_zero(mtrr_buf.virt, 4096);
|
||||
mem_zero(fakeefi_buf.virt, 4096);
|
||||
xj380_build_boot_config(bootcfg_buf.phys, mtrr_buf.phys, XJ380_STACK_GPA, fbc_buf.phys);
|
||||
|
||||
/* 11. EPT 映射所有 guest 区域。
|
||||
* boot area 各缓冲独立分配,逐个按自身 GPA 映射。 */
|
||||
if (ept_map_range(XJ380_PGT_GPA, pgt_buf.phys, XJ380_PGT_SIZE, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map page tables failed");
|
||||
return -19;
|
||||
}
|
||||
if (ept_map_range(XJ380_GDT_GPA, gdt_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map GDT failed");
|
||||
return -20;
|
||||
}
|
||||
if (ept_map_range(XJ380_MADT_GPA, madt_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map MADT failed");
|
||||
return -21;
|
||||
}
|
||||
if (ept_map_range(XJ380_FADT_GPA, fadt_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map FADT failed");
|
||||
return -22;
|
||||
}
|
||||
if (ept_map_range(XJ380_MCFG_GPA, mcfg_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map MCFG failed");
|
||||
return -23;
|
||||
}
|
||||
if (ept_map_range(XJ380_HPET_GPA, hpet_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map HPET failed");
|
||||
return -24;
|
||||
}
|
||||
if (ept_map_range(XJ380_MEMMAP_GPA, memmap_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map memory map failed");
|
||||
return -25;
|
||||
}
|
||||
if (ept_map_range(XJ380_BOOTCFG_GPA, bootcfg_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map BOOT_CONFIG failed");
|
||||
return -26;
|
||||
}
|
||||
if (ept_map_range(XJ380_MTRR_GPA, mtrr_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map MTRR buffer failed");
|
||||
return -27;
|
||||
}
|
||||
if (ept_map_range(XJ380_STACK_GPA, stack_buf.phys, 8192, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map stack failed");
|
||||
return -28;
|
||||
}
|
||||
if (ept_map_range(XJ380_FAKEEFI_GPA, fakeefi_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map fake EFI table failed");
|
||||
return -29;
|
||||
}
|
||||
if (ept_map_range(XJ380_FBC_GPA, fbc_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map FrameBufferConfig failed");
|
||||
return -30;
|
||||
}
|
||||
if (ept_map_range(g_guest.fb_gpa, fb_buf.phys, XJ380_FB_SIZE,
|
||||
EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map framebuffer failed");
|
||||
return -31;
|
||||
}
|
||||
if (ept_map_range(g_guest.kernel_gpa, kernel_buf.phys, kernel_alloc,
|
||||
EPT_RWX) != 0) {
|
||||
log_error("[XJ380] EPT map kernel failed");
|
||||
return -32;
|
||||
}
|
||||
if (ept_map_range(g_guest.ram_gpa, ram_buf.phys, ram_size,
|
||||
EPT_RWX) != 0) {
|
||||
log_error("[XJ380] EPT map guest RAM failed");
|
||||
return -33;
|
||||
}
|
||||
/* 低 1MB 恒等映射(BIOS 区,与 linux_loader 一致;<1MB 无需按需分配) */
|
||||
if (ept_identity_map(0, 0x100000, EPT_RWX) != 0) {
|
||||
log_warn("[XJ380] EPT identity map first 1MB failed (non-critical)");
|
||||
}
|
||||
|
||||
/* 12. 完成 */
|
||||
g_guest.loaded = 1;
|
||||
log_hex64("[XJ380] kernel GPA=", g_guest.kernel_gpa);
|
||||
log_hex64("[XJ380] kernel entry=", g_guest.kernel_entry);
|
||||
log_hex64("[XJ380] kernel size=", g_guest.kernel_size);
|
||||
log_hex64("[XJ380] RAM GPA=", g_guest.ram_gpa);
|
||||
log_hex64("[XJ380] RAM size=", g_guest.ram_size);
|
||||
log_info("[XJ380] loader init ok");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct xj380_guest_info *xj380_get_guest_info(void) {
|
||||
return g_guest.loaded ? &g_guest : (struct xj380_guest_info *)0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
vmm/xj380_loader.o: vmm\xj380_loader.c include\utsm\xj380_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\log.h include\utsm\dma.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\xj380_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
Reference in New Issue
Block a user