This commit is contained in:
xyou
2026-07-28 21:11:10 +08:00
parent 537890df9e
commit 2774c10d0c
49 changed files with 4019 additions and 94 deletions
+15
View File
@@ -79,3 +79,18 @@ void serial_write(const char *s) {
serial_putc(*s++);
}
}
/* 非阻塞读取 host 串口一个字节(QEMU stdio → guest COM1 输入桥)。
* 返回 0-255,无数据返回 -1。供 vmexit 在 guest resume 前轮询。 */
int serial_try_read(void) {
if (inb(COM1 + 5) & 0x01) {
return inb(COM1);
}
return -1;
}
/* 导出 PIT 校准的 TSC 频率(kHz 量级,单位 ticks/ms),
* 供 VMX preemption timer 与 guest PIT tick 注入使用。 */
u64 serial_tsc_per_ms(void) {
return g_tsc_per_ms;
}
Binary file not shown.
+8 -24
View File
@@ -173,8 +173,8 @@ typedef long long du_i64;
#define DU_RADIUS_NONE 0u
#define DU_RADIUS_SM 4u /* 小按钮、标签 */
#define DU_RADIUS_MD 8u /* 卡片、输入框 */
#define DU_RADIUS_LG 12u /* 大卡片、面板 */
#define DU_RADIUS_MD 0u /* 卡片、输入框(性能优化:改为直角) */
#define DU_RADIUS_LG 0u /* 大卡片、面板(性能优化:改为直角) */
#define DU_RADIUS_XL 16u /* 模态框 */
#define DU_RADIUS_FULL 9999u /* 完全圆角(药丸形) */
@@ -722,9 +722,9 @@ static inline du_u32 du_theme_accent(du_theme t) {
/* --- 强调色:霓虹青 (Neon Cyan) --- */
#define DP_KATE_ACCENT 0xFF00E5FFu /* --accent: #00e5ff */
#define DP_KATE_ACCENT_DIM 0x2E00E5FFu /* --accent-dim: rgba(0,229,255,0.18) */
#define DP_KATE_ACCENT_DIM_SOLID 0xFF1A3A4Au /* 不透明近似 */
#define DP_KATE_ACCENT_DIM_SOLID 0xFF2A4A5Au /* 不透明近似(提升亮度改善对比度) */
#define DP_KATE_PANEL_BORDER 0x5900E5FFu /* --panel-border: rgba(0,229,255,0.35) */
#define DP_KATE_PANEL_BORDER_SOLID 0xFF005A6Au /* 不透明近似 */
#define DP_KATE_PANEL_BORDER_SOLID 0xFF2A6070u /* 不透明近似(提升亮度) */
/* --- 辅助色:霓虹绿 (Neon Green) --- */
#define DP_KATE_ACCENT2 0xFF39FF14u /* --accent2: #39ff14 */
@@ -781,28 +781,12 @@ static inline du_u32 du_theme_accent(du_theme t) {
* Winux-Kate 特效函数
* =================================================================== */
/* 绘制扫描线纹理( repeating-linear-gradient 0deg, 3px 周期)
* 在指定矩形区域内叠加半透明青色水平细线 */
/* 绘制扫描线纹理(已禁用以提升性能)
* 原效果:在指定矩形区域内叠加半透明青色水平细线 */
static inline void du_kate_scanlines(du_context *ctx, du_i64 x, du_i64 y,
du_i64 w, du_i64 h) {
for (du_i64 ry = 0; ry < h; ry++) {
du_i64 yy = y + ry;
if (yy < 0 || (du_u64)yy >= ctx->height) continue; /* 每 3 像素一条线 */
du_u32 *line = (du_u32 *)((du_u8 *)ctx->fb + (du_u64)yy * ctx->pitch);
for (du_i64 rx = 0; rx < w; rx++) {
du_i64 xx = x + rx;
if (xx < 0 || (du_u64)xx >= ctx->width) continue;
/* 叠加极淡青色(alpha ~0.03 ≈ 8/255)*/
du_u32 cur = line[(du_u64)xx];
du_u32 b = (cur >> 16) & 0xFF;
du_u32 g = (cur >> 8) & 0xFF;
du_u32 r = cur & 0xFF;
b = (b + 2 < 255) ? b + 2 : 255;
g = (g + 4 < 255) ? g + 4 : 255;
r = (r + 2 < 255) ? r + 2 : 255;
line[(du_u64)xx] = 0xFF000000u | (b << 16) | (g << 8) | r;
}
}
/* 性能优化:禁用扫描线纹理,减少约50%的像素处理开销 */
(void)ctx; (void)x; (void)y; (void)w; (void)h;
}
/* 绘制带光晕的矩形边框(box-shadow 效果)
+28 -2
View File
@@ -121,6 +121,25 @@ struct linux_setup_header {
/* General RAM starts after IPC region */
#define LINUX_GUEST_RAM_GPA 0x05000000ULL /* 80 MB */
/* ===== virtio-mmio device GPA layout =====
* virtio-mmio 设备放在 GPA 高位 MMIO 区(guest RAM 之上,不占用 e820 RAM)。
* guest 通过 cmdline 参数 virtio_mmio.device= 发现这些设备。
* 每个设备占 0x200 字节寄存器空间(virtio-mmio 规范要求 4KB 对齐)。
*
* 这些 GPA 在 EPT 中【故意不映射】,访问时触发 EPT violation,
* 由 vmexit handler 路由到 virtio_mmio 后端模拟寄存器读写。
*
* GPA 选择 0xF4000000 起始(接近 4GB,远离 guest RAM 0x05000000-0x09000000)。 */
#define VIRTIO_MMIO_GPA_BASE 0xF4000000ULL
#define VIRTIO_MMIO_GPA_STRIDE 0x1000ULL /* 每个设备 4KB */
#define VIRTIO_MMIO_BLK_GPA (VIRTIO_MMIO_GPA_BASE + 0 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4000000 */
#define VIRTIO_MMIO_NET_GPA (VIRTIO_MMIO_GPA_BASE + 1 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4001000 */
/* 判断 GPA 是否落在 virtio-mmio 设备区(含两端) */
#define VIRTIO_MMIO_GPA_END (VIRTIO_MMIO_GPA_BASE + 2 * VIRTIO_MMIO_GPA_STRIDE)
#define IS_VIRTIO_MMIO_GPA(gpa) ((gpa) >= VIRTIO_MMIO_GPA_BASE && (gpa) < VIRTIO_MMIO_GPA_END)
/* ===== Linux loader API ===== */
/* Loaded Linux guest state */
@@ -165,9 +184,16 @@ int linux_launch(void);
/* Default Linux command line (can be overridden).
* nohlt: prevent kernel idle loop from using HLT — only the exec daemon's
* explicit ioctl(PARK) HLT should trigger VM-Exit park, so that
* linux_launch() returns only after the daemon is ready. */
* linux_launch() returns only after the daemon is ready.
* noapic/nolapic/nosmp: UTSM VMM 只模拟 legacy PIC(8259) + PIT(8254),
* 不提供 LAPIC/IOAPIC;guest 走 XT-PIC 模式,IRQ vector = 0x30+irq。
* virtio_mmio.device=: 向 guest 声明 virtio-mmio 设备(blk@0xF4000000 IRQ5,
* net@0xF4001000 IRQ6),需 CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y。 */
#define LINUX_DEFAULT_CMDLINE \
"console=ttyS0,115200 earlyprintk=serial nokaslr " \
"no_timer_check loglevel=7 nohlt idle=poll"
"no_timer_check loglevel=7 nohlt idle=poll " \
"noapic nolapic nosmp " \
"virtio_mmio.device=4K@0xF4000000:5 " \
"virtio_mmio.device=4K@0xF4001000:6"
#endif
+156
View File
@@ -0,0 +1,156 @@
#ifndef UTSM_VIRTIO_MMIO_H
#define UTSM_VIRTIO_MMIO_H
/* virtio_mmio.h — UTSM virtio-mmio 设备模拟后端框架。
*
* 为 Linux guest 提供 virtio-mmio 设备(block + net)。
* 设备 GPA 位于 VIRTIO_MMIO_GPA_BASE 起的高位 MMIO 区,EPT 故意不映射,
* guest 访问时触发 EPT violation,vmexit handler 调用本模块的
* virtio_mmio_read/write 模拟寄存器行为。
*
* 参考 Virtio Spec 1.1 §4.2 (virtio-mmio)。
* guest 通过 cmdline virtio_mmio.device= 参数发现设备(无需 PCI/ACPI)。
*/
#include <utsm/types.h>
/* virtio-mmio 寄存器偏移(Virtio Spec 1.1 §4.2.2.1,均为 32 位寄存器) */
#define VIRTIO_MMIO_MAGIC_VALUE 0x000 /* R "virt" = 0x74726976 */
#define VIRTIO_MMIO_VERSION 0x004 /* R 1=legacy, 2=modern */
#define VIRTIO_MMIO_DEVICE_ID 0x008 /* R 1=net, 2=blk, 3=console... */
#define VIRTIO_MMIO_VENDOR_ID 0x00C /* R */
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 /* R features[31:0] (sel) */
#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 /* W */
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 /* W features[31:0] (sel) */
#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 /* W */
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 /* W legacy only */
#define VIRTIO_MMIO_QUEUE_SEL 0x030 /* W */
#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 /* R */
#define VIRTIO_MMIO_QUEUE_NUM 0x038 /* W */
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03C /* W legacy only */
#define VIRTIO_MMIO_QUEUE_PFN 0x040 /* RW legacy only */
#define VIRTIO_MMIO_QUEUE_READY 0x044 /* RW modern */
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 /* W */
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 /* R */
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 /* W */
#define VIRTIO_MMIO_STATUS 0x070 /* RW */
#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 /* W modern */
#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 /* W modern */
#define VIRTIO_MMIO_QUEUE_DRIVER_LOW 0x090 /* W modern */
#define VIRTIO_MMIO_QUEUE_DRIVER_HIGH 0x094 /* W modern */
#define VIRTIO_MMIO_QUEUE_DEVICE_LOW 0x0A0 /* W modern */
#define VIRTIO_MMIO_QUEUE_DEVICE_HIGH 0x0A4 /* W modern */
#define VIRTIO_MMIO_CONFIG_GENERATION 0x0FC /* R modern */
#define VIRTIO_MMIO_CONFIG 0x100 /* RW device-specific config */
/* device IDs */
#define VIRTIO_ID_NET 1
#define VIRTIO_ID_BLOCK 2
#define VIRTIO_ID_CONSOLE 3
/* device status bits */
#define VIRTIO_STATUS_ACKNOWLEDGE 1
#define VIRTIO_STATUS_DRIVER 2
#define VIRTIO_STATUS_DRIVER_OK 4
#define VIRTIO_STATUS_FEATURES_OK 8
#define VIRTIO_STATUS_FAILED 128
/* 通用 feature bits */
#define VIRTIO_F_VERSION_1 32 /* modern 设备必须提供(bit32=高32位word bit0) */
/* ===== 后端框架 API ===== */
/* 初始化 virtio-mmio 后端框架(注册 block + net 设备)。
* 在 linux_loader_init 之后、linux_launch 之前调用。 */
void virtio_mmio_init(void);
/* 处理一次 virtio-mmio 寄存器读/写(由 vmexit EPT violation 调用)。
* gpa : 触发访问的 GPA(落在某设备 4KB 寄存器区)
* width : 访问宽度(字节),通常为 4
* value : 写操作的数据
* 返回读操作的数据(写操作返回 0)。 */
u64 virtio_mmio_read(u64 gpa, int width);
void virtio_mmio_write(u64 gpa, u64 value, int width);
/* 后端驱动注册接口(virtio_blk.c / virtio_net.c 调用) */
struct virtio_backend {
u32 device_id; /* VIRTIO_ID_* */
u64 gpa_base; /* 该设备的 MMIO GPA 起始 */
u32 irq; /* guest ISA IRQ 号(cmdline virtio_mmio.device=:IRQ,
* legacy PIC 下 vector = 0x30 + irq;0 = 不注入中断) */
u32 num_queues; /* 队列数(blk=1, net=2) */
u32 queue_size; /* 每队列描述符数 */
u64 device_features; /* device 提供的 feature 位 */
u32 config_len; /* config 空间长度(从 0x100 起) */
/* 读取 device config 空间(offset 相对 0x100)。返回 32 位值。 */
u32 (*read_config)(u32 offset, int width);
/* 通知:guest 写入 QUEUE_NOTIFY,后端处理该队列的请求。 */
void (*queue_notify)(u32 queue_idx);
/* 复位设备。 */
void (*reset)(void);
};
/* 注册一个后端设备(内部使用,按 gpa_base 排序查找)。 */
int virtio_mmio_register(struct virtio_backend *dev);
/* 提供 guest GPA→host 虚拟地址转换(后端访问 virtqueue/描述符用)。
* 通过 EPT 翻译 GPA 到 HPA,再加 HHDM offset 得到 host 虚拟地址。
* 返回 NULL 表示 GPA 未映射。 */
void *virtio_gpa_to_host(u64 gpa);
/* ===== virtqueue 结构(Virtio Spec 1.1 §2.6 split ring) ===== */
/* 描述符:16 字节 */
struct virtq_desc {
u64 addr; /* GPA of data buffer */
u32 len; /* data length */
u16 flags; /* VIRTQ_DESC_F_* */
u16 next; /* next descriptor if F_NEXT */
} __attribute__((packed));
#define VIRTQ_DESC_F_NEXT 1
#define VIRTQ_DESC_F_WRITE 2
#define VIRTQ_DESC_F_INDIRECT 4
/* avail ring(driver → device) */
struct virtq_avail {
u16 flags;
u16 idx;
u16 ring[1]; /* queue_size entries */
} __attribute__((packed));
/* used ring 元素 */
struct virtq_used_elem {
u32 id; /* descriptor chain head index */
u32 len; /* bytes written (device→driver) */
} __attribute__((packed));
/* used ring(device → driver) */
struct virtq_used {
u16 flags;
u16 idx;
struct virtq_used_elem ring[1];
} __attribute__((packed));
/* ===== 队列状态访问 API(后端 queue_notify 用) =====
*
* 后端在其 queue_notify(queue_idx) 回调中需要:
* 1. 读 avail ring 取请求描述符链
* 2. 处理请求(读/写 guest 数据 buffer)
* 3. 写 used ring 回执
* 以下函数按 device_id + queue_idx 定位队列,操作 guest 内存。 */
/* 取队列的 desc/avail/used 表 host 地址 + 队列大小。
* 返回 0 成功(指针填充),负值失败(队列未 ready/未映射)。 */
int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx,
struct virtq_desc **desc_out,
struct virtq_avail **avail_out,
struct virtq_used **used_out,
u32 *queue_num_out);
/* 更新 used ring idx(后端处理完一批请求后调用,让 guest 看到新回执)。
* 由后端在写 used ring 元素后调用,内部做内存屏障。 */
void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx);
#endif /* UTSM_VIRTIO_MMIO_H */
+20
View File
@@ -169,8 +169,10 @@
/* ===== Pin-based controls ===== */
#define PIN_EXT_INTERRUPT_EXITING (1ULL << 0)
#define PIN_NMI_EXITING (1ULL << 3)
#define PIN_VMX_PREEMPTION_TIMER (1ULL << 6)
/* ===== Primary processor-based controls ===== */
#define CPU_BASED_INTR_WINDOW_EXITING (1ULL << 2)
#define CPU_BASED_HLT_EXITING (1ULL << 7)
#define CPU_BASED_INVLPG_EXITING (1ULL << 9)
#define CPU_BASED_MWAIT_EXITING (1ULL << 10)
@@ -208,6 +210,7 @@
#define EXIT_EXTERNAL_INTERRUPT 1
#define EXIT_TRIPLE_FAULT 2
#define EXIT_INIT_SIGNAL 3
#define EXIT_INTERRUPT_WINDOW 7
#define EXIT_CPUID 10
#define EXIT_INVD 13
#define EXIT_RDMSR 31
@@ -232,8 +235,13 @@
#define EXIT_INVLPG 14
#define EXIT_EPT_VIOLATION 48
#define EXIT_EPT_MISCONFIG 49
#define EXIT_VMX_PREEMPTION_TIMER 52
#define EXIT_APIC_ACCESS 44
/* ===== VM-entry interruption-information field 编码 ===== */
#define VM_ENTRY_INTR_INFO_VALID 0x80000000ULL
#define VM_ENTRY_INTR_TYPE_HW_IRQ 0 /* external interrupt (bits 10:8) */
/* ===== EPT memory types ===== */
#define EPT_MEMORY_TYPE_UC 0ULL
#define EPT_MEMORY_TYPE_WC 1ULL
@@ -265,4 +273,16 @@ int vmx_vmresume(void);
u64 vmx_read_msr(u32 msr);
u64 vmx_get_host_cr3(void);
/* ===== 虚拟中断注入(vmexit.c 实现) ===== */
/* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。
* 仅排队;实际在下一次 vmresume 前(guest RFLAGS.IF=1 且无阻塞时)注入,
* 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */
void vmx_guest_queue_irq(u32 vector);
/* VMX preemption timer:返回 1ms 对应的 timer 计数值(按 IA32_VMX_BASIC
* bits[55:48] 的 scale 换算 TSC 频率)。 */
u64 vmx_preemption_quantum_1ms(void);
/* 检查 CPU 是否支持 VMX preemption timer。 */
int vmx_preemption_timer_supported(void);
#endif
+16 -2
View File
@@ -16,6 +16,7 @@
#include <utsm/vmx.h>
#include <utsm/ept.h>
#include <utsm/vmm.h>
#include <utsm/virtio_mmio.h>
#include <utsm/log.h>
#include <utsm/types.h>
#include "../arch/x86_64/limine.h"
@@ -203,8 +204,14 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
/* ===== VMCS controls for Linux ===== */
static void linux_vmcs_setup_controls(u64 eptp) {
u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING,
IA32_VMX_TRUE_PINBASED_CTLS);
/* Pin-based:外部中断 exit + NMI exit + VMX preemption timer(若支持)。
* preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、
* 推进 guest PIT tick、注入 pending virtio/COM1 IRQ。 */
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);
/* CPU-based: exit on HLT, INVLPG, MWAIT, IO, use MSR bitmap,
* activate secondary controls. Don't intercept RDTSC (Linux needs it). */
@@ -281,6 +288,10 @@ int linux_launch(void) {
log_info("[LINUX] launch begin");
/* 注册 virtio-mmio 后端(block + net),guest 经 cmdline
* virtio_mmio.device= 发现。幂等:重复调用直接返回。 */
virtio_mmio_init();
/* Reset VM-Exit state */
g_guest_terminated = 0;
g_last_exit_reason = 0xFFFFFFFFULL;
@@ -292,6 +303,9 @@ int linux_launch(void) {
linux_vmcs_setup_controls(vmm_get_eptp());
linux_vmcs_setup_host_state();
/* 初始 arm preemption timer(1ms 后首次周期 exit) */
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
/* Set RSI for Linux boot_params (passed via global, loaded in asm) */
g_linux_rsi = gi->bootparams_gpa;
+70 -1
View File
@@ -270,6 +270,45 @@ void *linux_find_bzimage_module(u64 *size_out) {
return (void *)0;
}
/* ===== initrd module finder =====
*
* 在 Limine boot module 中查找 initramfs(路径含 "initrd" 或 "initramfs")。
* 返回模块数据指针,*size_out 为大小;未找到返回 NULL。 */
static void *linux_find_initrd_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++) {
/* 匹配 "initrd"(如 linux-initrd.img) */
if ((s[0] == 'i' || s[0] == 'I') &&
(s[1] == 'n' || s[1] == 'N') &&
(s[2] == 'i' || s[2] == 'I') &&
(s[3] == 't' || s[3] == 'T') &&
(s[4] == 'r' || s[4] == 'R') &&
(s[5] == 'd' || s[5] == 'D')) {
match = 1;
break;
}
}
if (!match) continue;
if (size_out) *size_out = file->size;
log_info("[LINUX] found initrd module:");
log_info(file->path);
log_hex64("[LINUX] initrd size=", file->size);
return file->address;
}
log_warn("[LINUX] no initrd module found");
return (void *)0;
}
/* ===== bzImage parser ===== */
int linux_parse_bzimage(const void *bzimage, u64 size,
@@ -516,9 +555,39 @@ int linux_loader_init(void) {
g_guest.cmdline_gpa = LINUX_GUEST_CMDLINE_GPA;
g_guest.cmdline_size = cmdline_len;
/* For now, no initrd (Phase 1.3+ will add it) */
/* 8.5. Load initrd (busybox initramfs) if present.
* initrd 放在 GPA 0x03000000(initrd 区,上限 16MB 到 IPC 区 0x04000000)。 */
g_guest.initrd_gpa = 0;
g_guest.initrd_size = 0;
{
u64 initrd_size = 0;
void *initrd = linux_find_initrd_module(&initrd_size);
if (initrd && initrd_size > 0) {
u64 initrd_max = LINUX_GUEST_IPC_SHM_GPA - LINUX_GUEST_INITRD_GPA; /* 16MB */
if (initrd_size > initrd_max) {
log_error("[LINUX] initrd too large (>16MB), skipped");
} else {
u64 initrd_pages = (initrd_size + 4095) / 4096;
dkm_dma_buffer initrd_buf;
if (dma_alloc_pages(initrd_pages, 4096, 0, &initrd_buf) != 0) {
log_error("[LINUX] failed to alloc initrd memory");
return -17;
}
mem_copy(initrd_buf.virt, initrd, initrd_size);
if (ept_map_range(LINUX_GUEST_INITRD_GPA, initrd_buf.phys,
initrd_pages * 4096, EPT_READ | EPT_WRITE) != 0) {
log_error("[LINUX] EPT map initrd failed");
return -18;
}
g_guest.initrd_gpa = LINUX_GUEST_INITRD_GPA;
g_guest.initrd_size = initrd_size;
log_hex64("[LINUX] initrd mapped GPA=", g_guest.initrd_gpa);
log_hex64("[LINUX] initrd size=", g_guest.initrd_size);
}
} else {
log_warn("[LINUX] booting without initrd (no userland!)");
}
}
setup_boot_params(bp_buf.virt, g_guest.bootparams_gpa,
g_guest.kernel_entry,
+254
View File
@@ -0,0 +1,254 @@
/* virtio_blk.c — UTSM virtio-blk 设备模拟后端。
*
* 为 Linux guest 提供 virtio-blk 块设备。guest 的块读写请求经
* virtqueue 传入,本后端桥接到 UTSM 真实块设备(block API,AHCI/SATA)。
*
* Virtio Spec 1.1 §5.2 (Block Device)。
* 请求描述符链: [header(16B, R)] [data(N*512B, R/W)] [status(1B, W)]
* 支持的操作:VIRTIO_BLK_T_IN(读)、VIRTIO_BLK_T_OUT(写)、VIRTIO_BLK_T_GET_ID。
*
* 设计说明:
* - 桥接到 UTSM block API 的第 0 个设备(ahci0 SATA 盘)。
* - guest 把 Arch rootfs 镜像当作 /dev/vda 挂载;镜像需预先写入 SATA 盘
* 的某个分区/LBA 区间(由 mkfat32/build 流程或独立 raw 区提供)。
* - 当前为最小可用:扇区 512B,单队列,无 flush/discard。
*/
#include <utsm/virtio_mmio.h>
#include <utsm/linux_loader.h>
#include <utsm/block.h>
#include <utsm/log.h>
#include <utsm/types.h>
/* virtio-blk 请求头(Virtio Spec 1.1 §5.2.6.2) */
struct virtio_blk_outhdr {
u32 type; /* VIRTIO_BLK_T_* */
u32 reserved; /* (ioprio) */
u64 sector; /* 起始扇区(512B 单位) */
} __attribute__((packed));
#define VIRTIO_BLK_T_IN 0 /* 读 */
#define VIRTIO_BLK_T_OUT 1 /* 写 */
#define VIRTIO_BLK_T_FLUSH 4
#define VIRTIO_BLK_T_GET_ID 8
#define VIRTIO_BLK_S_OK 0
#define VIRTIO_BLK_S_IOERR 1
#define VIRTIO_BLK_S_UNSUPP 2
/* feature bits */
#define VIRTIO_BLK_F_SIZE_MAX 1
#define VIRTIO_BLK_F_SEG_MAX 2
#define VIRTIO_BLK_F_BLK_SIZE 6
#define VIRTIO_BLK_F_FLUSH 9
/* device config 空间(VIRTIO_MMIO_CONFIG 起) */
struct virtio_blk_config {
u64 capacity; /* 总扇区数(512B) */
u32 size_max;
u32 seg_max;
u16 geometry[4];
u32 blk_size;
/* ... 其余字段省略 */
} __attribute__((packed));
/* 后端状态 */
static u64 g_blk_capacity; /* 扇区数 */
static u32 g_blk_sector_size = 512;
static int g_blk_bound = -1; /* 绑定的 UTSM block 设备索引 */
/* 读取 device config 空间 */
static u32 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_blk_capacity;
cfg.size_max = 128; /* 单请求最大 128 扇区 */
cfg.seg_max = 32;
cfg.blk_size = g_blk_sector_size;
/* 按 offset 返回 32 位窗口 */
if (offset + 4 <= sizeof(cfg)) {
u32 v;
u8 *src = (u8 *)&cfg + offset;
v = (u32)src[0] | ((u32)src[1] << 8) | ((u32)src[2] << 16) | ((u32)src[3] << 24);
return v;
}
return 0;
}
/* 处理一个块请求描述符链。返回写入 guest 的字节数(用于 used.len)。 */
static u32 blk_handle_chain(struct virtq_desc *desc, u16 head, u32 qnum) {
(void)qnum;
if (!desc) return 0;
/* 第一个描述符:请求头(driver→device,16 字节) */
struct virtio_blk_outhdr *hdr =
(struct virtio_blk_outhdr *)virtio_gpa_to_host(desc[head].addr);
if (!hdr) return 0;
u32 type = hdr->type;
u64 sector = hdr->sector;
/* 沿描述符链找到 data 与 status 描述符 */
u16 cur = head;
u16 data_idx = 0xFFFF, status_idx = 0xFFFF;
int hops = 0;
if (desc[cur].flags & VIRTQ_DESC_F_NEXT) {
data_idx = desc[cur].next;
cur = data_idx;
/* data 之后可能还有 status */
while ((desc[cur].flags & VIRTQ_DESC_F_NEXT) && hops < 16) {
cur = desc[cur].next;
hops++;
}
/* 最后一个是 status(device write,1 字节) */
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;
const dkm_block_api *blk = block_get_api();
switch (type) {
case VIRTIO_BLK_T_IN: { /* 读:块设备 → guest data buffer */
if (data_idx == 0xFFFF || g_blk_bound < 0 || !blk) {
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; }
u32 nsec = dlen / 512;
if (nsec == 0) nsec = 1;
if (blk->read((u32)g_blk_bound, sector, nsec, dbuf) != 0) {
result = VIRTIO_BLK_S_IOERR;
} else {
bytes_done = dlen;
}
break;
}
case VIRTIO_BLK_T_OUT: { /* 写:guest data buffer → 块设备 */
if (data_idx == 0xFFFF || g_blk_bound < 0 || !blk) {
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; }
u32 nsec = dlen / 512;
if (nsec == 0) nsec = 1;
if (blk->write((u32)g_blk_bound, sector, nsec, dbuf) != 0) {
result = VIRTIO_BLK_S_IOERR;
} else {
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-VIRTIO-BLK";
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;
}
/* queue_notify:guest 提交了块请求,处理队列。 */
static u16 g_blk_last_avail;
static void blk_queue_notify(u32 queue_idx) {
struct virtq_desc *desc;
struct virtq_avail *avail;
struct virtq_used *used;
u32 qnum;
if (virtio_queue_get_ptrs(VIRTIO_ID_BLOCK, queue_idx,
&desc, &avail, &used, &qnum) != 0) {
return;
}
/* 处理自上次以来的所有新 avail 请求 */
u16 cur = g_blk_last_avail;
u16 used_idx = used->idx;
while (cur != avail->idx) {
u16 head = avail->ring[cur % qnum];
u32 len = blk_handle_chain(desc, head, qnum);
/* 写 used 回执 */
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_blk_last_avail = cur;
}
static void blk_reset(void) {
g_blk_last_avail = 0;
}
/* 后端结构体 */
static struct virtio_backend g_blk_backend = {
.device_id = VIRTIO_ID_BLOCK,
.gpa_base = VIRTIO_MMIO_BLK_GPA,
.irq = 5, /* guest ISA IRQ5(cmdline :5,vector 0x35) */
.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 = blk_read_config,
.queue_notify = blk_queue_notify,
.reset = blk_reset,
};
void virtio_blk_backend_init(void) {
/* 绑定 UTSM 第 0 个块设备(ahci0 SATA 盘)作为后端存储 */
const dkm_block_api *blk = block_get_api();
if (blk && blk->device_count() > 0) {
g_blk_bound = 0;
u64 ssize = blk->sector_size(0);
if (ssize > 0) g_blk_sector_size = (u32)ssize;
/* 从设备名推算容量不可得,用默认值(guest 端可再探测)。
* 实际 capacity 需读 ATA IDENTIFY;这里给 4GB 占位,
* guest mkfs/mount 会按此上限工作。 */
g_blk_capacity = 8ULL * 1024 * 1024 * 1024 / 512; /* 8GB in sectors */
log_info("[VBLK] bound to UTSM block device 0");
log_hex64("[VBLK] capacity(sectors)=", g_blk_capacity);
} else {
g_blk_bound = -1;
g_blk_capacity = 0;
log_warn("[VBLK] no UTSM block device, blk backend degraded");
}
virtio_mmio_register(&g_blk_backend);
}
+294
View File
@@ -0,0 +1,294 @@
/* virtio_mmio.c — UTSM virtio-mmio 设备模拟后端框架实现。
*
* 管理一组 virtio_backend 设备(block + net),处理 guest 对 MMIO 寄存器
* 的读/写。寄存器语义遵循 Virtio Spec 1.1 §4.2(modern,version=2)。
*
* guest 设备发现:cmdline virtio_mmio.device=SIZE@GPA:IRQ 参数(见
* linux_loader 的 cmdline 注入)。QUEUE_NOTIFY 处理完后经
* vmx_guest_queue_irq 注入虚拟中断(legacy PIC,vector=0x30+irq)。
*/
#include <utsm/virtio_mmio.h>
#include <utsm/linux_loader.h>
#include <utsm/ept.h>
#include <utsm/vmx.h>
#include <utsm/log.h>
#include <utsm/types.h>
#include "../arch/x86_64/limine.h"
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
/* 每个 virtio 设备的运行状态(框架层维护,后端无关) */
struct virtio_dev_state {
struct virtio_backend *be; /* 后端驱动(NULL = 未注册) */
u32 status; /* device status */
u32 drv_features_sel; /* DRIVER_FEATURES_SEL */
u32 dev_features_sel; /* DEVICE_FEATURES_SEL */
u64 driver_features; /* guest 协商的 features */
u32 queue_sel; /* 当前选中队列 */
u32 interrupt_status; /* INTERRUPT_STATUS */
u32 config_generation;
/* 每队列状态 */
struct {
u32 num; /* QUEUE_NUM(实际启用大小) */
u32 ready; /* QUEUE_READY */
u64 desc_gpa; /* 描述符表 GPA */
u64 driver_gpa; /* avail ring GPA */
u64 device_gpa; /* used ring GPA */
} q[4];
};
static struct virtio_dev_state g_vdevs[MAX_VIRTIO_DEVICES];
static int g_vdev_count;
static int g_vio_ready;
/* ===== guest 内存访问辅助 ===== */
void *virtio_gpa_to_host(u64 gpa) {
u64 hpa = ept_gpa_to_hpa(gpa);
if (hpa == 0) return (void *)0;
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
return (void *)(hhdm + hpa);
}
/* ===== 设备注册 ===== */
int virtio_mmio_register(struct virtio_backend *dev) {
if (!dev || g_vdev_count >= MAX_VIRTIO_DEVICES) return -1;
struct virtio_dev_state *s = &g_vdevs[g_vdev_count];
s->be = dev;
s->status = 0;
s->drv_features_sel = 0;
s->dev_features_sel = 0;
s->driver_features = 0;
s->queue_sel = 0;
s->interrupt_status = 0;
s->config_generation = 0;
for (int i = 0; i < 4; i++) {
s->q[i].num = 0;
s->q[i].ready = 0;
s->q[i].desc_gpa = 0;
s->q[i].driver_gpa = 0;
s->q[i].device_gpa = 0;
}
g_vdev_count++;
log_hex64("[VIO] registered device id=", dev->device_id);
log_hex64("[VIO] gpa=", dev->gpa_base);
return 0;
}
/* 按 GPA 查找设备(含该设备的 4KB 寄存器区)。 */
static struct virtio_dev_state *find_dev(u64 gpa, u32 *offset_out) {
for (int i = 0; i < g_vdev_count; i++) {
u64 base = g_vdevs[i].be->gpa_base;
if (gpa >= base && gpa < base + 0x1000) {
if (offset_out) *offset_out = (u32)(gpa - base);
return &g_vdevs[i];
}
}
return (struct virtio_dev_state *)0;
}
/* ===== feature 读取(按 sel 返回 32 位窗口) ===== */
static u32 read_features(struct virtio_dev_state *s) {
if (s->dev_features_sel == 0) {
return (u32)(s->be->device_features & 0xFFFFFFFFULL);
} else if (s->dev_features_sel == 1) {
return (u32)(s->be->device_features >> 32);
}
return 0;
}
/* ===== 寄存器读 ===== */
u64 virtio_mmio_read(u64 gpa, int width) {
(void)width; /* virtio-mmio 寄存器固定 32 位访问 */
u32 off;
struct virtio_dev_state *s = find_dev(gpa, &off);
if (!s || !s->be) {
log_hex64("[VIO] read unmapped gpa=", gpa);
return 0;
}
switch (off) {
case VIRTIO_MMIO_MAGIC_VALUE: return VIRTIO_MMIO_MAGIC;
case VIRTIO_MMIO_VERSION: return VIRTIO_MMIO_VERSION_MODERN;
case VIRTIO_MMIO_DEVICE_ID: return s->be->device_id;
case VIRTIO_MMIO_VENDOR_ID: return 0x55444D55; /* "UMDU" 自定义 */
case VIRTIO_MMIO_DEVICE_FEATURES:return read_features(s);
case VIRTIO_MMIO_QUEUE_NUM_MAX: return s->be->queue_size;
case VIRTIO_MMIO_QUEUE_READY: return s->q[s->queue_sel].ready;
case VIRTIO_MMIO_INTERRUPT_STATUS:return s->interrupt_status;
case VIRTIO_MMIO_STATUS: return s->status;
case VIRTIO_MMIO_CONFIG_GENERATION:return s->config_generation;
default:
/* device config 空间(0x100 起) */
if (off >= VIRTIO_MMIO_CONFIG && off < VIRTIO_MMIO_CONFIG + s->be->config_len) {
if (s->be->read_config) {
return s->be->read_config(off - VIRTIO_MMIO_CONFIG, width);
}
}
return 0;
}
}
/* ===== 寄存器写 ===== */
void virtio_mmio_write(u64 gpa, u64 value, int width) {
(void)width;
u32 off;
struct virtio_dev_state *s = find_dev(gpa, &off);
if (!s || !s->be) {
log_hex64("[VIO] write unmapped gpa=", gpa);
return;
}
u32 v32 = (u32)value;
switch (off) {
case VIRTIO_MMIO_DEVICE_FEATURES_SEL:
s->dev_features_sel = v32;
break;
case VIRTIO_MMIO_DRIVER_FEATURES:
if (s->drv_features_sel == 0) {
s->driver_features = (s->driver_features & 0xFFFFFFFF00000000ULL) | v32;
} else if (s->drv_features_sel == 1) {
s->driver_features = (s->driver_features & 0xFFFFFFFFULL) | ((u64)v32 << 32);
}
break;
case VIRTIO_MMIO_DRIVER_FEATURES_SEL:
s->drv_features_sel = v32;
break;
case VIRTIO_MMIO_QUEUE_SEL:
if (v32 < 4) s->queue_sel = v32;
break;
case VIRTIO_MMIO_QUEUE_NUM: {
/* 限制在后端 queue_size 内 */
if (v32 == 0 || v32 > s->be->queue_size) {
s->q[s->queue_sel].num = s->be->queue_size;
} else {
s->q[s->queue_sel].num = v32;
}
break;
}
case VIRTIO_MMIO_QUEUE_READY:
s->q[s->queue_sel].ready = v32 ? 1 : 0;
break;
case VIRTIO_MMIO_QUEUE_DESC_LOW:
s->q[s->queue_sel].desc_gpa = (s->q[s->queue_sel].desc_gpa & 0xFFFFFFFF00000000ULL) | v32;
break;
case VIRTIO_MMIO_QUEUE_DESC_HIGH:
s->q[s->queue_sel].desc_gpa = (s->q[s->queue_sel].desc_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32);
break;
case VIRTIO_MMIO_QUEUE_DRIVER_LOW:
s->q[s->queue_sel].driver_gpa = (s->q[s->queue_sel].driver_gpa & 0xFFFFFFFF00000000ULL) | v32;
break;
case VIRTIO_MMIO_QUEUE_DRIVER_HIGH:
s->q[s->queue_sel].driver_gpa = (s->q[s->queue_sel].driver_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32);
break;
case VIRTIO_MMIO_QUEUE_DEVICE_LOW:
s->q[s->queue_sel].device_gpa = (s->q[s->queue_sel].device_gpa & 0xFFFFFFFF00000000ULL) | v32;
break;
case VIRTIO_MMIO_QUEUE_DEVICE_HIGH:
s->q[s->queue_sel].device_gpa = (s->q[s->queue_sel].device_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32);
break;
case VIRTIO_MMIO_QUEUE_NOTIFY:
/* guest 通知设备处理队列 v32。调用后端处理请求。 */
if (s->be->queue_notify) {
s->be->queue_notify(v32);
}
/* 设置中断状态位(used buffer notification)并注入虚拟 IRQ。
* legacy PIC 模式:vector = 0x30 + irq(master PIC 基址 0x30)。
* 是否真正投递由 vmexit 的 maybe_inject_irq 按 IMR/IF 决定。 */
s->interrupt_status |= 1;
if (s->be->irq != 0) {
vmx_guest_queue_irq(0x30 + s->be->irq);
}
break;
case VIRTIO_MMIO_INTERRUPT_ACK:
s->interrupt_status &= ~v32;
break;
case VIRTIO_MMIO_STATUS:
s->status = v32;
if (v32 == 0) {
/* 复位 */
if (s->be->reset) s->be->reset();
s->driver_features = 0;
s->interrupt_status = 0;
for (int i = 0; i < 4; i++) {
s->q[i].ready = 0;
s->q[i].num = 0;
}
}
break;
default:
/* device config 空间写(多数设备 config 只读,忽略) */
break;
}
}
/* ===== 队列访问 API 实现 =====
*
* 后端 queue_notify 调用 virtio_queue_get_ptrs 取队列三个表的 host 地址,
* 自行遍历 avail ring 处理请求,写 used ring 回执。 */
int virtio_queue_get_ptrs(u32 device_id, 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->device_id == device_id) {
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");
used->idx = new_idx;
__asm__ volatile("mfence" ::: "memory");
}
/* ===== 框架初始化 =====
*
* 注册 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)。
* 这里声明以便链接;具体注册在各自文件完成。 */
void virtio_blk_backend_init(void);
void virtio_net_backend_init(void);
void virtio_mmio_init(void) {
if (g_vio_ready) return;
g_vdev_count = 0;
virtio_blk_backend_init();
virtio_net_backend_init();
g_vio_ready = 1;
log_info("[VIO] virtio-mmio backend init ok");
}
+218
View File
@@ -0,0 +1,218 @@
/* virtio_net.c — UTSM virtio-net 设备模拟后端。
*
* 为 Linux guest 提供 virtio-net 网卡。guest 的收发包请求经 virtqueue
* 传入,本后端桥接到 UTSM 真实网卡(net API,e1000/virtio_net DKM 驱动)。
*
* Virtio Spec 1.1 §5.1 (Network Device)。
* 队列:0=RX(device→driver 收包),1=TX(driver→device 发包)。
* 每帧前置 virtio_net_hdr(10/12 字节,无 GSO 时全零)。
*
* 最小可用实现:
* - 无 GSO/TSO/checksum offload(feature 不声明,guest 走软件路径)
* - RX 轮询:guest RX 队列提交空 buffer,queue_notify 时尝试 rx_poll
* - TX:queue_notify 时取帧直接 net->tx 发出
*/
#include <utsm/virtio_mmio.h>
#include <utsm/linux_loader.h>
#include <utsm/net.h>
#include <utsm/log.h>
#include <utsm/types.h>
/* virtio_net_hdr(VIRTIO_NET_F_CSUM/GSO 未协商时为 10 字节 legacy) */
struct virtio_net_hdr {
u8 flags;
u8 gso_type;
u16 hdr_len;
u16 gso_size;
u16 csum_start;
u16 csum_offset;
/* u16 num_buffers; // 仅 VIRTIO_NET_F_MRG_RXBUF 协商后存在 */
} __attribute__((packed));
#define VIRTIO_NET_HDR_LEN 10
/* feature bits(我们都不声明,保持最简) */
#define VIRTIO_NET_F_CSUM 0
#define VIRTIO_NET_F_GUEST_CSUM 1
#define VIRTIO_NET_F_MAC 5
#define VIRTIO_NET_F_STATUS 16
/* device config(VIRTIO_MMIO_CONFIG 起) */
struct virtio_net_config {
u8 mac[6];
u16 status; /* bit0 = link up */
u16 max_virtqueue_pairs;
} __attribute__((packed));
static u8 g_net_mac[6] = { 0x52, 0x54, 0x00, 0xDE, 0x5A, 0x01 };
static int g_net_bound = -1;
static u32 net_read_config(u32 offset, int width) {
(void)width;
struct virtio_net_config cfg;
u8 *c = (u8 *)&cfg;
for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0;
for (int i = 0; i < 6; i++) cfg.mac[i] = g_net_mac[i];
cfg.status = 1; /* link up */
cfg.max_virtqueue_pairs = 1;
if (offset + 4 <= sizeof(cfg)) {
u8 *src = (u8 *)&cfg + offset;
return (u32)src[0] | ((u32)src[1] << 8) | ((u32)src[2] << 16) | ((u32)src[3] << 24);
}
return 0;
}
/* 从描述符链拼接一个连续帧(virtio-net 帧通常 2 个描述符:hdr + data)。
* 返回帧长度,数据拷入 out_buf(out_cap 容量)。 */
static u32 net_gather_frame(struct virtq_desc *desc, u16 head,
u8 *out_buf, u32 out_cap, int skip_hdr) {
u32 total = 0;
u16 cur = head;
int hops = 0;
while (hops < 32) {
u8 *src = (u8 *)virtio_gpa_to_host(desc[cur].addr);
u32 len = desc[cur].len;
if (!src) break;
/* 跳过第一个描述符中的 virtio_net_hdr */
if (hops == 0 && skip_hdr && len > VIRTIO_NET_HDR_LEN) {
u32 dl = len - VIRTIO_NET_HDR_LEN;
if (total + dl <= out_cap) {
u8 *d = src + VIRTIO_NET_HDR_LEN;
for (u32 i = 0; i < dl; i++) out_buf[total + i] = d[i];
total += dl;
}
} else {
if (total + len <= out_cap) {
for (u32 i = 0; i < len; i++) out_buf[total + i] = src[i];
total += len;
}
}
if (!(desc[cur].flags & VIRTQ_DESC_F_NEXT)) break;
cur = desc[cur].next;
hops++;
}
return total;
}
/* TX queue(idx=1):guest 发包。 */
static u16 g_tx_last_avail;
static u8 g_tx_scratch[2048];
static void net_handle_tx(void) {
struct virtq_desc *desc;
struct virtq_avail *avail;
struct virtq_used *used;
u32 qnum;
if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 1, &desc, &avail, &used, &qnum) != 0)
return;
const dkm_net_api *net = net_get_api();
u16 cur = g_tx_last_avail;
u16 used_idx = used->idx;
while (cur != avail->idx) {
u16 head = avail->ring[cur % qnum];
u32 flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch), 1);
if (flen > 0 && net && g_net_bound >= 0) {
net->tx((u32)g_net_bound, g_tx_scratch, flen);
}
used->ring[used_idx % qnum].id = head;
used->ring[used_idx % qnum].len = 0;
used_idx++;
cur++;
}
virtio_queue_bump_used(used, used_idx);
g_tx_last_avail = cur;
}
/* RX queue(idx=0):guest 提交空 buffer,我们尝试 rx_poll 填充。
* guest 每提交一批 buffer 就 notify 一次;有包则填充 + used,无包则
* 保留 buffer 等下次 notify(简化:直接返回 0 长度 used,让 guest 重投)。 */
static u16 g_rx_last_avail;
static void net_handle_rx(void) {
struct virtq_desc *desc;
struct virtq_avail *avail;
struct virtq_used *used;
u32 qnum;
if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 0, &desc, &avail, &used, &qnum) != 0)
return;
const dkm_net_api *net = net_get_api();
if (!net || g_net_bound < 0) return;
u16 cur = g_rx_last_avail;
u16 used_idx = used->idx;
while (cur != avail->idx) {
u16 head = avail->ring[cur % qnum];
/* RX 描述符链:hdr(W) + data(W)。尝试收一包。 */
u16 curd = head;
u8 *hdrp = (u8 *)virtio_gpa_to_host(desc[curd].addr);
u8 *datap = (u8 *)0;
u32 datalen = 0;
if (desc[curd].flags & VIRTQ_DESC_F_NEXT) {
u16 di = desc[curd].next;
datap = (u8 *)virtio_gpa_to_host(desc[di].addr);
datalen = desc[di].len;
}
if (!hdrp || !datap || datalen == 0) { cur++; continue; }
u32 got = 0;
if (net->rx_poll((u32)g_net_bound, datap, datalen, &got) == 0 && got > 0) {
/* 填 virtio_net_hdr 全零(无 GSO) */
for (int i = 0; i < VIRTIO_NET_HDR_LEN; i++) hdrp[i] = 0;
used->ring[used_idx % qnum].id = head;
used->ring[used_idx % qnum].len = VIRTIO_NET_HDR_LEN + got;
used_idx++;
} else {
/* 无包:不消耗该 buffer,留待下次(但为避免死循环,跳过) */
}
cur++;
}
virtio_queue_bump_used(used, used_idx);
g_rx_last_avail = cur;
}
static void net_queue_notify(u32 queue_idx) {
if (queue_idx == 1) net_handle_tx();
else if (queue_idx == 0) net_handle_rx();
}
static void net_reset(void) {
g_tx_last_avail = 0;
g_rx_last_avail = 0;
}
static struct virtio_backend g_net_backend = {
.device_id = VIRTIO_ID_NET,
.gpa_base = VIRTIO_MMIO_NET_GPA,
.irq = 6, /* guest ISA IRQ6(cmdline :6,vector 0x36) */
.num_queues = 2,
.queue_size = 128,
.device_features = (1ULL << VIRTIO_F_VERSION_1) |
(1ULL << VIRTIO_NET_F_MAC) | (1ULL << VIRTIO_NET_F_STATUS),
.config_len = sizeof(struct virtio_net_config),
.read_config = net_read_config,
.queue_notify = net_queue_notify,
.reset = net_reset,
};
void virtio_net_backend_init(void) {
/* 绑定 UTSM 第 0 个网卡(e1000 或 virtio_net DKM 驱动) */
const dkm_net_api *net = net_get_api();
if (net && net->device_count() > 0) {
g_net_bound = 0;
dkm_net_device_info info;
if (net->device_info(0, &info) == 0) {
for (int i = 0; i < 6; i++) g_net_mac[i] = info.mac[i];
}
log_info("[VNET] bound to UTSM net device 0");
} else {
g_net_bound = -1;
log_warn("[VNET] no UTSM net device, net backend degraded");
}
virtio_mmio_register(&g_net_backend);
}
+638 -15
View File
@@ -5,15 +5,35 @@
#include <utsm/ipc_shm.h>
#include <utsm/log.h>
#include <utsm/types.h>
#include <utsm/dma.h>
#include <utsm/linux_loader.h>
#include <utsm/virtio_mmio.h>
#include "../arch/x86_64/limine.h"
/* VM-Exit 处理器:从 VMCS 读取 exit reason 与 guest 状态,分发处理。
*
* Phase 1.1:处理 HLT(终止 guest)、EPT violation(动态映射)、异常。
* Phase 1.3:增加 VMCALL hypercall 处理(读 guest GPR、分发、写返回值)。
* Phase 1.4:EPT violation 区分 RAM(按需分配 HPA)与 virtio-mmio
* (路由到设备模拟寄存器读写,不映射真实内存)。
* Phase 1.5:guest 平台设备模拟(legacy 模式,配合 noapic/nolapic):
* - COM1 8250 UART(console=ttyS0 输出 + host 串口输入桥)
* - 8259 PIC(IMR 跟踪,EOI/ICW 吞掉)
* - 8254 PIT ch0(divisor 跟踪,按 guest 编程频率注入 IRQ0 tick)
* - CPUID passthrough(屏蔽 VMX/hypervisor 位)
* - 虚拟中断注入(pending queue + interrupt-window exiting)
* - VMX preemption timer 周期 exit(轮询 host 串口 RX + tick)
*/
extern volatile struct limine_hhdm_request g_hhdm_request;
extern void vmx_vmresume_wrapper(void);
/* serial.c 导出的 host 串口与 TSC 频率接口 */
extern int serial_try_read(void);
extern u64 serial_tsc_per_ms(void);
extern void serial_putc(char c);
static u64 g_vmexit_count;
static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len) {
@@ -41,16 +61,595 @@ static int handle_hlt(u64 rip, u64 instr_len, int *out_resume) {
return 0;
}
/* 处理 EPT violation:动态映射缺失的 GPA。
* Phase 1.1:1:1 映射缺失的页面,权限 RWX。 */
static int handle_ept_violation(u64 qualification, int *out_resume) {
/* ===== EPT violation: guest RAM 按需分配 =====
*
* guest 首次访问某 GPA 页时触发 EPT violation。我们按需分配一个
* 4KB HPA 页并建立 GPA→HPA 映射(不再是错误的 1:1 恒等映射——
* 那会把 guest GPA 直接当 host 物理地址,可能与 UTSM 自身内存冲突)。
*
* 已映射区域的访问权限不足(如先 R 映射后写)也走这里:升级为 RWX。
*/
/* 按需为普通 guest RAM GPA 分配一个 HPA 页并映射。返回 0 成功。 */
static int ept_map_guest_ram_page(u64 gpa_page) {
dkm_dma_buffer buf;
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0, &buf) != 0) {
log_error("[VMEXIT] OOM for guest RAM page");
return -1;
}
if (ept_map_range(gpa_page, buf.phys, EPT_PAGE_SIZE, EPT_RWX) != 0) {
log_error("[VMEXIT] EPT map guest RAM page failed");
return -1;
}
return 0;
}
/* ===== 虚拟中断注入(legacy PIC 模式,vector = 0x30 + irq) =====
*
* Linux 以 noapic/nolapic 启动时走 XT-PIC:master PIC 向量基址 0x30。
* 我们向 guest 注入的外部中断只能是这些 ISA IRQ 向量:
* IRQ0 (0x30) PIT tick、IRQ4 (0x34) COM1、IRQ5 (0x35) virtio-blk、
* IRQ6 (0x36) virtio-net。
*
* 注入条件:guest RFLAGS.IF=1 且 interruptibility=0 且 IMR 未 mask。
* 不满足时 arm CPU_BASED_INTR_WINDOW_EXITING,guest 一旦开中断
* 立即产生 interrupt-window exit,届时再注入。
*/
#define IRQ_PENDING_MAX 16
static u8 g_pending_vectors[IRQ_PENDING_MAX];
static int g_pending_head, g_pending_tail;
/* guest 8259 PIC 状态(master) */
static u8 g_pic_imr = 0xFF; /* 初始全 mask */
static int g_pic_icw_state = 0; /* 0=就绪(OCW), 1=等ICW2, 2=等ICW3, 3=等ICW4 */
static int g_pic_icw_sngl = 0;
static int g_pic_icw_need4 = 0;
void vmx_guest_queue_irq(u32 vector) {
/* 合并连续相同向量(PIT tick 合并 / COM1 RX 去重) */
if (g_pending_tail != g_pending_head) {
int last = (g_pending_tail + IRQ_PENDING_MAX - 1) % IRQ_PENDING_MAX;
if (g_pending_vectors[last] == (u8)vector) return;
}
int next = (g_pending_tail + 1) % IRQ_PENDING_MAX;
if (next == g_pending_head) return; /* 队列满:丢弃 */
g_pending_vectors[g_pending_tail] = (u8)vector;
g_pending_tail = next;
}
static void maybe_inject_irq(void) {
if (g_pending_head == g_pending_tail) return;
u8 vec = g_pending_vectors[g_pending_head];
/* IMR 检查:master PIC vector 0x30+n → IMR bit n */
if (vec >= 0x30 && vec < 0x38) {
if (g_pic_imr & (1u << (vec - 0x30))) {
/* 被 guest mask:丢弃(级别触发设备下次 notify 会重发) */
g_pending_head = (g_pending_head + 1) % IRQ_PENDING_MAX;
return;
}
}
u64 rflags = vmx_vmcs_read(VMCS_GUEST_RFLAGS);
u64 intr_st = vmx_vmcs_read(VMCS_GUEST_INTERRUPTIBILITY);
u64 cpu = vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL);
if ((rflags & 0x200) && intr_st == 0) {
/* 可注入:写 VM-entry interruption-information field */
vmx_vmcs_write(VMCS_VM_ENTRY_INTERRUPT_INFO,
VM_ENTRY_INTR_INFO_VALID |
(VM_ENTRY_INTR_TYPE_HW_IRQ << 8) | vec);
g_pending_head = (g_pending_head + 1) % IRQ_PENDING_MAX;
if (cpu & CPU_BASED_INTR_WINDOW_EXITING) {
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
cpu & ~CPU_BASED_INTR_WINDOW_EXITING);
}
} else {
/* 暂不可注入:arm interrupt-window exiting */
if (!(cpu & CPU_BASED_INTR_WINDOW_EXITING)) {
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
cpu | CPU_BASED_INTR_WINDOW_EXITING);
}
}
}
/* ===== guest COM1 8250 UART 模拟 =====
*
* console=ttyS0 的 I/O 端口 0x3F8-0x3FF。
* TX:THR 写 → host 串口输出(与 UTSM log 共用 QEMU stdio)。
* RX:host 串口输入 → g_guest_rx 环形缓冲 → guest 读 RBR;
* 有数据时注入 IRQ4(vector 0x34)。
* 寄存器:DLL/DLM/IER/FCR/LCR/MCR/SCR 存储,LSR/IIR/MSR 按状态合成。
*/
#define GUEST_RX_SIZE 256
static u8 g_guest_rx[GUEST_RX_SIZE];
static int g_rx_head, g_rx_tail;
static u8 g_com1_dll, g_com1_dlm, g_com1_ier, g_com1_fcr;
static u8 g_com1_lcr, g_com1_mcr, g_com1_scr;
static u64 com1_io(u32 port, int is_in, u64 value) {
u32 reg = port - 0x3F8;
int dlab = (g_com1_lcr & 0x80) != 0;
if (is_in) {
switch (reg) {
case 0: /* RBR / DLL */
if (dlab) return g_com1_dll;
if (g_rx_head != g_rx_tail) {
u8 b = g_guest_rx[g_rx_head];
g_rx_head = (g_rx_head + 1) % GUEST_RX_SIZE;
return b;
}
return 0;
case 1: return dlab ? g_com1_dlm : g_com1_ier;
case 2: /* IIR:bit0=0 表示有中断;bits7:6=1 表示 FIFO 启用 */
if (g_rx_head != g_rx_tail) return 0xC4; /* RX data available */
return 0xC1; /* no interrupt pending */
case 3: return g_com1_lcr;
case 4: return g_com1_mcr;
case 5: /* LSR:bit0 DR,bit5 THRE,bit6 TEMT */
return ((g_rx_head != g_rx_tail) ? 0x01 : 0x00) | 0x20 | 0x40;
case 6: return 0xB0; /* MSR:CTS|DSR|DCD */
case 7: return g_com1_scr;
}
return 0xFF;
}
switch (reg) {
case 0:
if (dlab) g_com1_dll = (u8)value;
else serial_putc((char)(value & 0xFF)); /* guest → host 串口 */
break;
case 1: if (dlab) g_com1_dlm = (u8)value; else g_com1_ier = (u8)value; break;
case 2: g_com1_fcr = (u8)value; break;
case 3: g_com1_lcr = (u8)value; break;
case 4: g_com1_mcr = (u8)value; break;
case 7: g_com1_scr = (u8)value; break;
}
return 0;
}
/* ===== guest 8254 PIT ch0 模拟(仅跟踪 divisor,tick 由 preemption exit 驱动) ===== */
static u16 g_pit_divisor = 0; /* 0 = 65536(默认 18.2Hz) */
static int g_pit_load_state = 0; /* 0=等 lo 字节, 1=等 hi 字节 */
static u8 g_pit_lo = 0;
static u64 g_pit_period_tsc = 0; /* 每 tick 的 TSC 周期 */
static u64 g_pit_accum = 0;
static u64 g_last_tick_tsc = 0;
static u64 rdtsc_local(void) {
u32 lo, hi;
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
return ((u64)hi << 32) | lo;
}
static void pit_recompute_period(void) {
u64 tpm = serial_tsc_per_ms();
if (tpm == 0) tpm = 2000000; /* 未校准时按 ~2GHz 兜底 */
u64 div = g_pit_divisor ? g_pit_divisor : 65536;
/* period_tsc = div / 1193182 Hz,用 ms 刻度换算避免溢出 */
g_pit_period_tsc = (div * tpm) / 1193; /* ≈ div * tpm * 1000 / 1193182 */
if (g_pit_period_tsc == 0) g_pit_period_tsc = 1;
}
static u64 pit_io(u32 port, int is_in, u64 value) {
if (is_in) return 0; /* 计数器读回:恒 0(Linux 用 TSC clocksource,不依赖) */
if (port == 0x43) {
/* mode/command:仅关心 ch0 + lo/hi 访问方式 */
if ((value & 0xC0) == 0x00 && (value & 0x30) == 0x30) {
g_pit_load_state = 0;
}
return 0;
}
if (port == 0x40) {
if (g_pit_load_state == 0) {
g_pit_lo = (u8)value;
g_pit_load_state = 1;
} else {
g_pit_divisor = (u16)(g_pit_lo | ((u16)(u8)value << 8));
g_pit_load_state = 0;
pit_recompute_period();
}
return 0;
}
return 0; /* 0x41/0x42 吞掉 */
}
/* ===== 其余 legacy 端口(吞掉/返回安全值) ===== */
static u64 misc_io(u32 port, int is_in, u64 value, int *out_terminate) {
/* 8259 PIC master */
if (port == 0x20) {
if (!is_in) {
if (value & 0x10) { /* ICW1 */
g_pic_icw_sngl = (value & 0x02) != 0;
g_pic_icw_need4 = (value & 0x01) != 0;
g_pic_icw_state = 1;
g_pic_imr = 0xFF;
}
/* 其余 OCW2/OCW3(含 EOI)吞掉 */
}
return 0;
}
if (port == 0x21) {
if (is_in) return g_pic_imr;
switch (g_pic_icw_state) {
case 1: /* ICW2(向量基址,忽略,固定 0x30) */
g_pic_icw_state = g_pic_icw_sngl ? (g_pic_icw_need4 ? 3 : 0) : 2;
break;
case 2: /* ICW3 */
g_pic_icw_state = g_pic_icw_need4 ? 3 : 0;
break;
case 3: /* ICW4 */
g_pic_icw_state = 0;
break;
default: /* OCW1 = IMR */
g_pic_imr = (u8)value;
break;
}
return 0;
}
/* 8259 PIC slave:吞掉(不用) */
if (port == 0xA0 || port == 0xA1) return is_in ? 0xFF : 0;
/* 8254 PIT */
if (port >= 0x40 && port <= 0x43) return pit_io(port, is_in, value);
/* PS/2 键盘/鼠标(i8042):status 读 0(空),data 读 0,写吞掉。
* i8042 自检将失败 → guest 无 PS/2 键鼠(console 输入走串口)。 */
if (port == 0x60 || port == 0x64) return is_in ? 0 : 0;
/* CMOS/RTC */
if (port == 0x70 || port == 0x71) return is_in ? 0 : 0;
/* PCI config mechanism #1:对 guest 隐藏整个 PCI 总线。
* guest reconfig BAR 会破坏 UTSM 的 AHCI/e1000,故全部返回"无设备"。 */
if (port >= 0xCF8 && port <= 0xCFF) return is_in ? 0xFFFFFFFFULL : 0;
/* 0xCF9 reset control:guest 请求重启 → 终止 guest 回 host */
if (port == 0xCF9) {
if (!is_in) {
log_warn("[VMEXIT] guest requested reboot via 0xCF9");
*out_terminate = 1;
}
return 0;
}
/* COM2-4:无设备 */
if ((port >= 0x2F8 && port <= 0x2FF) ||
(port >= 0x3E8 && port <= 0x3EF) ||
(port >= 0x2E8 && port <= 0x2EF)) {
return is_in ? 0xFF : 0;
}
/* 其余端口(0x80 delay、0x61 NMI、0x92 A20、0xB2 APM、0xF0-0xFF FPU
* error、0xED delay、ACPI 等):读 0,写吞掉 */
return is_in ? 0 : 0;
}
/* ===== EXIT_IO_INSTRUCTION 处理 =====
*
* exit qualification(Intel SDM 27.2.4.2? 实为 Vol 3D 表 27-6):
* bits 2:0 = 访问大小-1(0=1B, 1=2B, 3=4B)
* bit 3 = 方向(0=OUT, 1=IN)
* bit 4 = string 指令
* bit 5 = REP 前缀
* bits31:16 = 端口号
* OUT 数据在 guest RAX;IN 结果写回 guest RAX。
*/
static int handle_io(u64 qualification, u64 rip, u64 instr_len, int *out_resume) {
u32 port = (u32)(qualification >> 16);
u32 size = (u32)(qualification & 7) + 1;
int is_in = (qualification >> 3) & 1;
u64 value = 0;
int terminate = 0;
if (!is_in) {
value = g_guest_regs.rax;
if (size == 1) value &= 0xFF;
else if (size == 2) value &= 0xFFFF;
else value &= 0xFFFFFFFFULL;
}
u64 result;
if (port >= 0x3F8 && port <= 0x3FF) {
result = com1_io(port, is_in, value);
} else {
result = misc_io(port, is_in, value, &terminate);
}
if (is_in) {
if (size == 1) {
g_guest_regs.rax = (g_guest_regs.rax & ~0xFFULL) | (result & 0xFF);
} else if (size == 2) {
g_guest_regs.rax = (g_guest_regs.rax & ~0xFFFFULL) | (result & 0xFFFF);
} else {
g_guest_regs.rax = result & 0xFFFFFFFFULL; /* 32 位写清零高 32 位 */
}
}
if (terminate) {
*out_resume = 0;
return 0;
}
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
*out_resume = 1;
return 0;
}
/* ===== EXIT_CPUID 处理:host passthrough + 屏蔽 VMX/hypervisor =====
*
* CPUID 在 VMX non-root 无条件 exit。必须真实执行并把结果写回 guest
* rax/rbx/rcx/rdx,否则 guest 特性探测全错(原"skip"实现会崩 Linux)。
* leaf 1 ecx:清 bit5(VMX) 与 bit31(hypervisor),让 guest 认为自己在裸机。
*/
static void cpuid_host(u32 a, u32 c, u32 *oa, u32 *ob, u32 *oc, u32 *od) {
__asm__ volatile("cpuid"
: "=a"(*oa), "=b"(*ob), "=c"(*oc), "=d"(*od)
: "a"(a), "c"(c));
}
static int handle_cpuid(u64 rip, u64 instr_len, int *out_resume) {
u32 a = (u32)g_guest_regs.rax;
u32 c = (u32)g_guest_regs.rcx;
u32 oa, ob, oc, od;
cpuid_host(a, c, &oa, &ob, &oc, &od);
if (a == 1) {
oc &= ~(1u << 5); /* VMX */
oc &= ~(1u << 31); /* hypervisor present */
}
/* leaf 0x15/0x16 passthrough:QEMU 提供 TSC/总线频率,
* guest 据此校准 TSC(绕过 PIT ch2 校准路径)。 */
g_guest_regs.rax = oa;
g_guest_regs.rbx = ob;
g_guest_regs.rcx = oc;
g_guest_regs.rdx = od;
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
*out_resume = 1;
return 0;
}
/* ===== resume 前例行工作:RX 轮询 + PIT tick + 注入 pending IRQ ===== */
static void poll_host_serial_to_guest(void) {
for (int i = 0; i < 32; i++) {
int b = serial_try_read();
if (b < 0) break;
int next = (g_rx_tail + 1) % GUEST_RX_SIZE;
if (next == g_rx_head) break; /* ring 满 */
g_guest_rx[g_rx_tail] = (u8)b;
g_rx_tail = next;
}
if (g_rx_head != g_rx_tail) {
vmx_guest_queue_irq(0x34); /* COM1 = IRQ4 → vector 0x34 */
}
}
static void pit_tick_update(void) {
u64 now = rdtsc_local();
if (g_last_tick_tsc == 0) {
g_last_tick_tsc = now;
if (g_pit_period_tsc == 0) pit_recompute_period();
return;
}
u64 delta = now - g_last_tick_tsc;
g_last_tick_tsc = now;
if (g_pit_period_tsc == 0) return;
g_pit_accum += delta;
int n = 0;
while (g_pit_accum >= g_pit_period_tsc && n < 8) {
g_pit_accum -= g_pit_period_tsc;
vmx_guest_queue_irq(0x30); /* PIT = IRQ0 → vector 0x30(自动合并) */
n++;
}
if (g_pit_accum >= g_pit_period_tsc) g_pit_accum = 0; /* 防积压 */
}
static u64 g_preemption_quantum = 0;
u64 vmx_preemption_quantum_1ms(void) {
if (g_preemption_quantum) return g_preemption_quantum;
u64 basic = vmx_read_msr(IA32_VMX_BASIC);
u64 scale = (basic >> 48) & 0xFF; /* preemption timer scale */
if (scale == 0) scale = 1;
u64 tpm = serial_tsc_per_ms();
if (tpm == 0) tpm = 2000000;
g_preemption_quantum = tpm / scale;
if (g_preemption_quantum == 0) g_preemption_quantum = 1;
return g_preemption_quantum;
}
int vmx_preemption_timer_supported(void) {
u64 msr = vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS);
return (msr & (1ULL << (6 + 32))) != 0; /* bit6 允许置 1 */
}
/* 每次 vmresume 前调用(vmexit_dispatch resume=1 路径末尾)。
* 重新 arm preemption timer(1ms 周期 exit),轮询 host 串口 RX,
* 推进 PIT tick,注入 pending IRQ。 */
static void vmexit_before_resume(void) {
if (!g_linux_guest_active) return;
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
poll_host_serial_to_guest();
pit_tick_update();
maybe_inject_irq();
}
/* ===== EPT violation: 指令解码(用于 virtio-mmio MMIO 数据) =====
*
* virtio-mmio 寄存器访问是普通 MOV 指令,如:
* mov eax, [gpa] 读 (8A /r 或 A1)
* mov [gpa], eax 写 (89 /r 或 A3)
* movzx/movsx 等
*
* 我们不需要完整解码——只需确定:
* 1. 访问方向(读 or 写)
* 2. 访问宽度(1/2/4/8 字节)
* 3. 写访问时的写入数据;读访问时数据写到哪个 guest GPR
*
* 这通过解码指令字节 + 读 guest RIP 处的机器码完成。
* 用 GVA=EPT GPA 简化:guest 采用恒等页表,GUEST_LINEAR_ADDR 即 GPA。
*/
/* 从 guest 内存读指令字节(GPA=GVA 恒等映射,经 EPT 翻译到 HPA)。 */
static int guest_fetch(u64 gva, u8 *buf, u64 len) {
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
for (u64 i = 0; i < len; i++) {
u64 hpa = ept_gpa_to_hpa(gva + i);
if (hpa == 0) return -1;
buf[i] = *(volatile u8 *)(hhdm + hpa);
}
return 0;
}
/* 解码 MMIO 访问指令。
* rip_gpa : guest RIP(GPA,恒等映射)
* is_write_out : 输出 1=写 MMIO,0=读 MMIO
* width_out : 输出访问宽度(字节)
* reg_field_out: 输出目标/源 GPR 在 g_guest_regs 中的索引(0-15)
* 返回指令总长度(用于回退时推进 RIP),失败返回 0。 */
static u64 decode_mmio_insn(u64 rip_gpa, int *is_write_out, int *width_out,
int *reg_field_out) {
u8 insn[16];
if (guest_fetch(rip_gpa, insn, sizeof(insn)) != 0) return 0;
u64 off = 0;
int rex_w = 0, rex_r = 0;
/* 跳过前缀 + REX */
for (;;) {
u8 b = insn[off];
if (b == 0x66 || b == 0x67 || b == 0xF0 || b == 0xF2 || b == 0xF3 ||
(b >= 0x2E && b <= 0x3E && (b==0x2E||b==0x36||b==0x3E||b==0x26||b==0x64||b==0x65))) {
off++;
continue;
}
if (b >= 0x40 && b <= 0x4F) { /* REX */
rex_w = (b >> 3) & 1;
rex_r = (b >> 2) & 1;
off++;
}
break;
}
u8 op = insn[off];
u8 modrm = insn[off + 1];
int reg = ((modrm >> 3) & 7) | (rex_r << 3);
/* mov r, m(读 MMIO): 8A(8位) / 8B(全宽) */
if (op == 0x8A || op == 0x8B) {
*is_write_out = 0;
*width_out = (op == 0x8A) ? 1 : (rex_w ? 8 : 4);
*reg_field_out = reg;
return off + 2; /* 简化:假设 ModRM mod=00,rm=101(无 SIB/disp) */
}
/* mov m, r(写 MMIO): 88(8位) / 89(全宽) */
if (op == 0x88 || op == 0x89) {
*is_write_out = 1;
*width_out = (op == 0x88) ? 1 : (rex_w ? 8 : 4);
*reg_field_out = reg;
return off + 2;
}
/* movzx: 0F B6(8→宽) / 0F B7(16→宽) 读 */
if (op == 0x0F && (insn[off+1] == 0xB6 || insn[off+1] == 0xB7)) {
*is_write_out = 0;
*width_out = (insn[off+1] == 0xB6) ? 1 : 2;
u8 m2 = insn[off + 2];
*reg_field_out = ((m2 >> 3) & 7) | (rex_r << 3);
return off + 3;
}
/* 无法解码(如带 SIB/displacement 的复杂寻址)。
* virtio-mmio 驱动用简单寄存器访问,暂不支持则返回 0。 */
return 0;
}
/* 按索引读写 guest GPR(用于 MMIO 数据的源/目的)。 */
static u64 *gpr_ptr(int idx) {
switch (idx & 15) {
case 0: return (u64 *)&g_guest_regs.rax;
case 1: return (u64 *)&g_guest_regs.rcx;
case 2: return (u64 *)&g_guest_regs.rdx;
case 3: return (u64 *)&g_guest_regs.rbx;
case 6: return (u64 *)&g_guest_regs.rsi;
case 7: return (u64 *)&g_guest_regs.rdi;
case 5: return (u64 *)&g_guest_regs.rbp;
case 4: return (u64 *)&g_guest_regs.rax; /* rsp 不用作 MMIO 数据,占位 */
case 8: return (u64 *)&g_guest_regs.r8;
case 9: return (u64 *)&g_guest_regs.r9;
case 10: return (u64 *)&g_guest_regs.r10;
case 11: return (u64 *)&g_guest_regs.r11;
case 12: return (u64 *)&g_guest_regs.r12;
case 13: return (u64 *)&g_guest_regs.r13;
case 14: return (u64 *)&g_guest_regs.r14;
case 15: return (u64 *)&g_guest_regs.r15;
}
return (u64 *)&g_guest_regs.rax;
}
/* 处理 virtio-mmio 设备 GPA 的访问:路由到设备模拟。返回 1=已处理(resume)。 */
static int handle_virtio_mmio_access(u64 gpa, u64 rip, int *out_resume) {
int is_write, width, reg;
u64 insn_len = decode_mmio_insn(rip, &is_write, &width, &reg);
if (insn_len == 0) {
log_error("[VMEXIT] virtio-mmio: cannot decode access insn");
log_hex64("[VMEXIT] rip=", rip);
*out_resume = 0;
return -1;
}
u64 value = 0;
if (is_write) {
/* 从 guest GPR 取写入数据(按宽度截断) */
u64 raw = *gpr_ptr(reg);
if (width == 1) value = raw & 0xFF;
else if (width == 2) value = raw & 0xFFFF;
else if (width == 4) value = raw & 0xFFFFFFFFULL;
else value = raw;
virtio_mmio_write(gpa, value, width);
} else {
/* 从设备读,写入 guest GPR(按宽度) */
value = virtio_mmio_read(gpa, width);
u64 *dst = gpr_ptr(reg);
if (width == 1) *dst = (*dst & ~0xFFULL) | (value & 0xFF);
else if (width == 2) *dst = (*dst & ~0xFFFFULL) | (value & 0xFFFF);
else if (width == 4) *dst = value & 0xFFFFFFFFULL; /* 32位写清零高32 */
else *dst = value;
}
/* 推进 RIP 越过 MMIO 指令,resume guest */
vmx_vmcs_write(VMCS_GUEST_RIP, rip + insn_len);
*out_resume = 1;
return 0;
}
/* 处理 EPT violation:区分 guest RAM(按需分配)与 virtio-mmio(设备模拟)。
*
* exit qualification bits:
* bit0 = read access, bit1 = write access, bit2 = instruction fetch
* bit3 = EPT entry readable, bit4 = writable, bit5 = executable
* virtio-mmio GPA 故意不映射 → bit3/4/5 全 0 → "访问不存在页"。 */
static int handle_ept_violation(u64 qualification, u64 rip, int *out_resume) {
(void)qualification; /* virtio-mmio 判定只看 GPA,不看访问类型 */
u64 gpa = vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR);
/* virtio-mmio 设备区:路由到设备模拟,绝不映射真实内存 */
if (IS_VIRTIO_MMIO_GPA(gpa)) {
return handle_virtio_mmio_access(gpa, rip, out_resume);
}
/* 普通 guest RAM:按需分配 HPA 页并映射 */
u64 page = gpa & ~(EPT_PAGE_SIZE - 1);
log_hex64("[VMEXIT] EPT violation gpa=", gpa);
(void)qualification;
/* 1:1 映射该页(假设 HPA=GPA) */
if (ept_identity_map(page, EPT_PAGE_SIZE, EPT_RWX) != 0) {
log_error("[VMEXIT] failed to map EPT page");
if (ept_map_guest_ram_page(page) != 0) {
log_error("[VMEXIT] EPT violation: guest RAM map failed");
log_hex64("[VMEXIT] gpa=", gpa);
*out_resume = 0;
return -1;
}
@@ -84,7 +683,13 @@ int vmexit_dispatch(void) {
handle_hlt(rip, instr_len, &resume);
break;
case EXIT_EPT_VIOLATION:
handle_ept_violation(qualification, &resume);
handle_ept_violation(qualification, rip, &resume);
break;
case EXIT_EPT_MISCONFIG:
/* MMIO 区域权限配置错误或硬件 EPT 表损坏。诊断后终止。 */
log_error("[VMEXIT] EPT misconfig");
log_hex64("[VMEXIT] gpa=", vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR));
resume = 0;
break;
case EXIT_EXCEPTION_NMI:
handle_exception(qualification, rip, &resume);
@@ -94,14 +699,26 @@ int vmexit_dispatch(void) {
resume = 0;
break;
case EXIT_CPUID:
/* 简化:让 guest 通过 CPUID,前进 RIP。Phase 1.2+ 完整模拟。 */
log_info("[VMEXIT] CPUID - skip");
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
resume = 1;
/* host passthrough + 屏蔽 VMX/hypervisor 位(handle_cpuid 内推进 RIP) */
handle_cpuid(rip, instr_len, &resume);
break;
case EXIT_IO_INSTRUCTION:
/* 简化:跳过 IO 指令。Phase 1.4 virtio-console 完整处理。 */
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
/* legacy 设备模拟:COM1 UART / PIC / PIT / 其余端口吞掉 */
handle_io(qualification, rip, instr_len, &resume);
break;
case EXIT_INTERRUPT_WINDOW:
/* guest 刚开中断(RFLAGS.IF=1):尝试注入 pending IRQ */
{
u64 cpu = vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL);
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
cpu & ~CPU_BASED_INTR_WINDOW_EXITING);
maybe_inject_irq();
resume = 1;
}
break;
case EXIT_VMX_PREEMPTION_TIMER:
/* 1ms 周期 exit:resume 前由 vmexit_before_resume 统一
* 轮询 host 串口 RX / 推进 PIT tick / 注入 pending IRQ */
resume = 1;
break;
case EXIT_RDMSR:
@@ -129,6 +746,12 @@ int vmexit_dispatch(void) {
}
(void)qualification;
/* resume 前例行工作:arm preemption timer + host 串口 RX 轮询 +
* PIT tick 推进 + pending IRQ 注入(仅 Linux guest active 时) */
if (resume) {
vmexit_before_resume();
}
return resume;
}