fix: let nested-KVM vmlaunch succeed with Virtual NMIs

Self-test and Linux guest were failing VM-instruction error 7. Share a KVM-style control adjust, request PIN_VIRTUAL_NMIS, and stop intercepting guest #PF. Land isolated XNU/Python/KDE scaffolding off the boot path.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Linehu
2026-09-06 10:48:07 +08:00
co-authored by Cursor
parent cc0fcf26e3
commit 737a2cb0db
17 changed files with 1615 additions and 163 deletions
+135
View File
@@ -0,0 +1,135 @@
#ifndef UTSM_IPC_NATIVE_H
#define UTSM_IPC_NATIVE_H
#include <utsm/types.h>
/*
* Experimental native Deshab capability IPC ABI.
*
* This is intentionally distinct from CODE/utsm-ipc/ipc_proto.h, which is a
* UTSM <-> Linux guest transport. No native queue implementation or syscall
* binding exists yet. All ABI fields use bounded fixed-width Deshab types;
* pointers and implementation-owned kernel addresses never appear on wire.
*/
#define UTSM_IPC_NATIVE_MAGIC 0x44534950u /* "DSIP" */
#define UTSM_IPC_NATIVE_ABI_MAJOR 1u
#define UTSM_IPC_NATIVE_ABI_MINOR 0u
#define UTSM_IPC_NATIVE_MESSAGE_BYTES 256u
#define UTSM_IPC_NATIVE_PAYLOAD_BYTES 144u
#define UTSM_IPC_NATIVE_MAX_CAPS 4u
#define UTSM_IPC_NATIVE_MIN_DEPTH 2u
#define UTSM_IPC_NATIVE_MAX_DEPTH 4096u
#define UTSM_IPC_NATIVE_DEADLINE_NONE (~0ULL)
/*
* The rights field requests or describes attenuation. Authority comes from
* the calling task's kernel capability table; copying or editing this value
* must never create authority.
*/
typedef struct utsm_ipc_native_cap {
u64 object_id;
u32 generation;
u32 rights;
} utsm_ipc_native_cap;
typedef enum utsm_ipc_native_right {
UTSM_IPC_RIGHT_SEND = (1u << 0),
UTSM_IPC_RIGHT_RECEIVE = (1u << 1),
UTSM_IPC_RIGHT_CALL = (1u << 2),
UTSM_IPC_RIGHT_REPLY = (1u << 3),
UTSM_IPC_RIGHT_TRANSFER = (1u << 4),
UTSM_IPC_RIGHT_DUPLICATE = (1u << 5),
UTSM_IPC_RIGHT_MANAGE = (1u << 6),
UTSM_IPC_RIGHT_WAIT = (1u << 7),
UTSM_IPC_RIGHT_SIGNAL = (1u << 8)
} utsm_ipc_native_right;
#define UTSM_IPC_RIGHT_ALL ((u32)((1u << 9) - 1u))
/*
* Queue operations are carried in a u16 ABI field. CALL creates a transaction
* ID; REPLY must name that transaction. WAIT/SIGNAL are queue-state events,
* not substitutes for SEND/RECEIVE.
*/
typedef enum utsm_ipc_native_queue_op {
UTSM_IPC_QUEUE_CREATE = 1,
UTSM_IPC_QUEUE_DESTROY = 2,
UTSM_IPC_QUEUE_SEND = 3,
UTSM_IPC_QUEUE_RECEIVE = 4,
UTSM_IPC_QUEUE_CALL = 5,
UTSM_IPC_QUEUE_REPLY = 6,
UTSM_IPC_QUEUE_WAIT = 7,
UTSM_IPC_QUEUE_SIGNAL = 8,
UTSM_IPC_QUEUE_QUERY = 9
} utsm_ipc_native_queue_op;
#define UTSM_IPC_MSG_F_NONBLOCK (1u << 0)
#define UTSM_IPC_MSG_F_MOVE_CAPS (1u << 1)
#define UTSM_IPC_MSG_F_EXPECT_REPLY (1u << 2)
#define UTSM_IPC_MSG_F_CANCEL (1u << 3)
typedef enum utsm_ipc_native_result {
UTSM_IPC_NATIVE_OK = 0,
UTSM_IPC_NATIVE_ERR_INVALID = -1,
UTSM_IPC_NATIVE_ERR_ACCESS = -2,
UTSM_IPC_NATIVE_ERR_STALE_CAP = -3,
UTSM_IPC_NATIVE_ERR_FULL = -4,
UTSM_IPC_NATIVE_ERR_EMPTY = -5,
UTSM_IPC_NATIVE_ERR_CLOSED = -6,
UTSM_IPC_NATIVE_ERR_TIMEOUT = -7,
UTSM_IPC_NATIVE_ERR_CANCELLED = -8,
UTSM_IPC_NATIVE_ERR_UNSUPPORTED = -38
} utsm_ipc_native_result;
/*
* Fixed 256-byte message. message_size must equal sizeof(this structure),
* payload_length is bounded by PAYLOAD_BYTES, and capability_count is bounded
* by MAX_CAPS. Receivers must reject nonzero reserved fields.
*/
typedef struct utsm_ipc_native_message {
u32 magic;
u16 abi_major;
u16 abi_minor;
u16 operation; /* utsm_ipc_native_queue_op */
u16 flags;
u32 message_size;
u64 transaction_id;
utsm_ipc_native_cap endpoint;
u32 payload_length;
u16 capability_count;
u16 reserved0;
utsm_ipc_native_cap capabilities[UTSM_IPC_NATIVE_MAX_CAPS];
u8 payload[UTSM_IPC_NATIVE_PAYLOAD_BYTES];
} utsm_ipc_native_message;
/*
* Fixed queue-control request for a future syscall dispatcher. depth is valid
* only for CREATE and must stay within MIN_DEPTH..MAX_DEPTH. deadline_ns is
* an absolute monotonic deadline or UTSM_IPC_NATIVE_DEADLINE_NONE.
*/
typedef struct utsm_ipc_native_queue_request {
u16 abi_major;
u16 abi_minor;
u16 operation; /* utsm_ipc_native_queue_op */
u16 flags;
u32 depth;
u32 message_size;
utsm_ipc_native_cap queue;
u64 deadline_ns;
} utsm_ipc_native_queue_request;
_Static_assert(sizeof(utsm_ipc_native_cap) == 16u,
"native IPC capability ABI changed");
_Static_assert(sizeof(utsm_ipc_native_message) ==
UTSM_IPC_NATIVE_MESSAGE_BYTES,
"native IPC message ABI changed");
_Static_assert(sizeof(utsm_ipc_native_queue_request) == 40u,
"native IPC queue request ABI changed");
#endif /* UTSM_IPC_NATIVE_H */
+130
View File
@@ -0,0 +1,130 @@
#ifndef UTSM_SCHED_H
#define UTSM_SCHED_H
#include <utsm/types.h>
/*
* Experimental SAS-R0-PCQ scheduler contract.
*
* This header reserves the first ABI shape for a future per-CPU O(1)
* priority-bitmap scheduler. It is not connected to boot or context-switch
* code. Structures begin with version/size where they may cross component
* boundaries; existing fields must not be reordered and extensions append.
*/
#define UTSM_SCHED_ABI_VERSION 1u
#define UTSM_SCHED_PRIORITY_COUNT 64u
#define UTSM_SCHED_INVALID_SLOT 0xffffffffu
#define UTSM_SCHED_PRIORITY_HIGHEST 0u
#define UTSM_SCHED_PRIORITY_LOWEST (UTSM_SCHED_PRIORITY_COUNT - 1u)
/* Explicit API results. The isolated stub returns UNSUPPORTED for every call. */
typedef enum utsm_sched_result {
UTSM_SCHED_OK = 0,
UTSM_SCHED_ERR_INVALID = -1,
UTSM_SCHED_ERR_STATE = -2,
UTSM_SCHED_ERR_NO_TASK = -3,
UTSM_SCHED_ERR_UNSUPPORTED = -38
} utsm_sched_result;
/* Values are stored in u32 fields so the TCB layout does not depend on enum size. */
typedef enum utsm_sched_task_state {
UTSM_SCHED_TASK_UNUSED = 0,
UTSM_SCHED_TASK_NEW,
UTSM_SCHED_TASK_READY,
UTSM_SCHED_TASK_RUNNING,
UTSM_SCHED_TASK_BLOCKED,
UTSM_SCHED_TASK_SLEEPING,
UTSM_SCHED_TASK_RECOVERING,
UTSM_SCHED_TASK_FAULTED,
UTSM_SCHED_TASK_ZOMBIE
} utsm_sched_task_state;
#define UTSM_SCHED_TASK_F_KERNEL (1u << 0)
#define UTSM_SCHED_TASK_F_IDLE (1u << 1)
#define UTSM_SCHED_TASK_F_NO_MIGRATE (1u << 2)
#define UTSM_SCHED_TASK_F_RECOVERY (1u << 3)
/*
* Minimum x86_64 resume context. Volatile registers belong to the eventual
* interrupt/syscall frame and are deliberately not duplicated here.
*/
typedef struct utsm_sched_arch_context {
u64 rsp;
u64 rip;
u64 rflags;
u64 rbx;
u64 rbp;
u64 r12;
u64 r13;
u64 r14;
u64 r15;
} utsm_sched_arch_context;
/*
* Task control block prefix.
*
* runq_prev/runq_next are task-table slots, not pointers, so queues remain
* relocatable. priority 0 is highest. crypto_context is loaded as a pointer
* during a future switch; switch code must not scan capabilities or perform
* cryptography/checkpoint work.
*/
typedef struct utsm_sched_tcb {
u32 abi_version;
u32 struct_size;
u64 task_id;
u32 process_slot;
u32 flags;
u32 state; /* utsm_sched_task_state */
u32 priority;
u32 assigned_cpu;
u32 last_cpu;
u64 affinity_mask;
u64 timeslice_ns;
u64 wake_deadline_ns;
void *crypto_context;
u32 runq_prev;
u32 runq_next;
utsm_sched_arch_context arch;
u64 reserved[4];
} utsm_sched_tcb;
/*
* One ready bitmap and one intrusive FIFO per priority form the planned O(1)
* run queue. A set bit means that head[priority] is valid. Synchronization is
* an implementation concern and is intentionally not implied by this layout.
*/
typedef struct utsm_sched_run_queue {
u32 abi_version;
u32 struct_size;
u32 cpu_id;
u32 flags;
u64 ready_bitmap;
u32 head[UTSM_SCHED_PRIORITY_COUNT];
u32 tail[UTSM_SCHED_PRIORITY_COUNT];
u32 current_task_slot;
u32 idle_task_slot;
u32 ready_count;
u32 reserved0;
} utsm_sched_run_queue;
int utsm_sched_init(void);
int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue);
int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id,
u32 process_slot, u32 priority, u64 affinity_mask);
int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task);
int utsm_sched_dequeue(utsm_sched_tcb *task);
int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state);
int utsm_sched_wake(utsm_sched_tcb *task);
int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task);
int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns,
utsm_sched_tcb **out_preempted_task);
#endif /* UTSM_SCHED_H */
+2
View File
@@ -88,5 +88,7 @@ int vmm_self_test(void);
/* VMM 状态查询 */
int vmm_is_ready(void);
u64 vmm_get_eptp(void);
/* P8.5: VMCS 物理地址(linux_launch 前 VMCLEAR+VMPTRLD 用) */
u64 vmm_get_vmcs_phys(void);
#endif
+35 -19
View File
@@ -9,6 +9,7 @@
#define IA32_VMX_PROCBASED_CTLS 0x482
#define IA32_VMX_EXIT_CTLS 0x483
#define IA32_VMX_ENTRY_CTLS 0x484
#define IA32_VMX_MISC 0x485
#define IA32_VMX_PROCBASED_CTLS2 0x48B
#define IA32_VMX_EPT_VPID_CAP 0x48C
#define IA32_VMX_TRUE_PINBASED_CTLS 0x48D
@@ -37,15 +38,9 @@
/* Control fields */
#define VMCS_VPID 0x0000
#define VMCS_GUEST_ES_ADDR 0x0006
#define VMCS_GUEST_CS_ADDR 0x0008
#define VMCS_GUEST_SS_ADDR 0x000A
#define VMCS_GUEST_DS_ADDR 0x000C
#define VMCS_GUEST_FS_ADDR 0x000E
#define VMCS_GUEST_GS_ADDR 0x0010
#define VMCS_GUEST_LDTR_ADDR 0x0012
#define VMCS_GUEST_TR_ADDR 0x0014
#define VMCS_GUEST_INTR_STATUS 0x0009
/* P8.5: guest interrupt status 真实编码为 0x0810(16-bit guest-state),
* 旧值 0x0009 是 posted-interrupt 描述符区域,属于错误编码。 */
#define VMCS_GUEST_INTR_STATUS 0x0810
#define VMCS_IO_BITMAP_A 0x2000
#define VMCS_IO_BITMAP_B 0x2002
#define VMCS_MSR_BITMAP 0x2004
@@ -54,15 +49,22 @@
/* Read-only data fields */
#define VMCS_GUEST_PHYSICAL_ADDR 0x2400
#define VMCS_EXIT_INTR_INFO 0x4404
#define VMCS_EXIT_INTR_ERROR_CODE 0x4406
#define VMCS_EXIT_REASON 0x4402
#define VMCS_EXIT_QUALIFICATION 0x6400
#define VMCS_IO_RCX 0x6404
#define VMCS_IO_RSI 0x6406
#define VMCS_IO_RDI 0x6408
#define VMCS_IO_RIP 0x640A
/* P8.5: IO RCX/RSI/RDI/RIP 真实编码为 0x6402/0x6404/0x6406/0x6408,
* 旧值整体右移了一格。guest-linear-address = 0x640A 不变。 */
#define VMCS_IO_RCX 0x6402
#define VMCS_IO_RSI 0x6404
#define VMCS_IO_RDI 0x6406
#define VMCS_IO_RIP 0x6408
#define VMCS_GUEST_LINEAR_ADDR 0x640A
#define VMCS_INSTRUCTION_LENGTH 0x440C
#define VMCS_VMX_INSTRUCTION_ERROR 0x4406
/* P8.5 关键修复:VM-instruction error 真实编码是 0x4400。
* 旧值 0x4406 是 "VM-exit interruption error code",
* 导致 vmlaunch 失败后错误码永远读出 0。 */
#define VMCS_VMX_INSTRUCTION_ERROR 0x4400
/* Guest-state fields */
#define VMCS_GUEST_ES_SELECTOR 0x0800
@@ -82,8 +84,8 @@
#define VMCS_GUEST_LDTR_LIMIT 0x480C
#define VMCS_GUEST_TR_LIMIT 0x480E
#define VMCS_GUEST_CS_ACCESS 0x4816
#define VMCS_GUEST_SS_ACCESS 0x481A
#define VMCS_GUEST_DS_ACCESS 0x4818
#define VMCS_GUEST_SS_ACCESS 0x4818
#define VMCS_GUEST_DS_ACCESS 0x481A
#define VMCS_GUEST_ES_ACCESS 0x4814
#define VMCS_GUEST_FS_ACCESS 0x481C
#define VMCS_GUEST_GS_ACCESS 0x481E
@@ -165,11 +167,13 @@
#define VMCS_VM_EXIT_CONTROLS 0x400C
#define VMCS_VM_ENTRY_CONTROLS 0x4012
#define VMCS_VM_ENTRY_INTERRUPT_INFO 0x4016
#define VMCS_VM_ENTRY_EXCEPTION_ERROR 0x4018
#define VMCS_SECONDARY_VM_EXEC_CONTROL 0x401E
/* ===== Pin-based controls ===== */
#define PIN_EXT_INTERRUPT_EXITING (1ULL << 0)
#define PIN_NMI_EXITING (1ULL << 3)
#define PIN_VIRTUAL_NMIS (1ULL << 5)
#define PIN_VMX_PREEMPTION_TIMER (1ULL << 6)
/* ===== Primary processor-based controls ===== */
@@ -269,11 +273,23 @@ int vmx_vmcs_load(u64 phys);
int vmx_vmcs_clear(u64 phys); /* P8.4: VMCLEAR before VMLAUNCH */
u64 vmx_vmcs_read(u64 field);
void vmx_vmcs_write(u64 field, u64 value);
/* P8.5: 带状态返回的 VMREAD。
* 返回 0 成功(CF=0 且 ZF=0);-1 = VMfailValid/VMfailInvalid
* (此时 *value 未更新,多为 current-VMCS 无效)。 */
int vmx_vmcs_read_checked(u64 field, u64 *value);
int vmx_vmlaunch(void);
int vmx_vmresume(void);
u64 vmx_read_msr(u32 msr);
/* Capability polarity used by KVM and verified under nested KVM:
* low bit=1 => must be 1; high bit=0 => must be 0.
* Formula: (desired | allowed0) & allowed1. */
u64 vmx_adjust_control(u64 value, u32 msr);
u64 vmx_get_host_cr3(void);
/* Convert a pointer inside the linked UTSM kernel image to its physical
* address using Limine's kernel-address response. Kernel-image mappings are
* distinct from the HHDM and must never be translated by subtracting HHDM. */
u64 vmx_kernel_virt_to_phys(const void *address);
/* ===== 虚拟中断注入(vmexit.c 实现) ===== */
/* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。
@@ -281,9 +297,9 @@ u64 vmx_get_host_cr3(void);
* 否则 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);
/* VMX preemption timer:返回 host polling 周期对应的 timer 计数值。
* 当前周期为 10ms,避免 nested KVM 在 1ms VM-exit 下失去前进性。 */
u64 vmx_preemption_quantum(void);
/* 检查 CPU 是否支持 VMX preemption timer。 */
int vmx_preemption_timer_supported(void);
+65
View File
@@ -0,0 +1,65 @@
/*
* Isolated scheduler placeholder.
*
* Deliberately not linked by the UTSM build. These functions make accidental
* use fail explicitly until the per-CPU bitmap scheduler and switch path are
* implemented.
*/
#include <utsm/sched.h>
int utsm_sched_init(void) {
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(queue);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id,
u32 process_slot, u32 priority, u64 affinity_mask) {
UTSM_UNUSED(task);
UTSM_UNUSED(task_id);
UTSM_UNUSED(process_slot);
UTSM_UNUSED(priority);
UTSM_UNUSED(affinity_mask);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_dequeue(utsm_sched_tcb *task) {
UTSM_UNUSED(task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state) {
UTSM_UNUSED(task);
UTSM_UNUSED(blocked_state);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_wake(utsm_sched_tcb *task) {
UTSM_UNUSED(task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(out_task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns,
utsm_sched_tcb **out_preempted_task) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(elapsed_ns);
UTSM_UNUSED(out_preempted_task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
+10 -8
View File
@@ -76,8 +76,9 @@ static u64 *ept_walk(u64 gpa, int alloc_missing) {
if (!alloc_missing) return (u64 *)0;
u64 new_phys = ept_alloc_page();
if (new_phys == 0) return (u64 *)0;
/* 中间页表项:RWX + WB 内存类型 */
table[indices[level]] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3);
/* 非叶 EPT 表项只含物理地址与 RWX。bits 5:3 在非叶项中
* 保留,写入 leaf memory type 会触发 EPT misconfiguration。 */
table[indices[level]] = new_phys | EPT_RWX;
/* 切换到新分配的下一级页表 */
table = (u64 *)phys_to_virt(new_phys);
} else {
@@ -145,7 +146,7 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) {
if (!(entry & EPT_READ)) {
u64 new_phys = ept_alloc_page();
if (new_phys == 0) return -3;
table[pml4_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3);
table[pml4_idx] = new_phys | EPT_RWX;
table = (u64 *)phys_to_virt(new_phys);
} else {
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
@@ -156,7 +157,7 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) {
if (!(entry & EPT_READ)) {
u64 new_phys = ept_alloc_page();
if (new_phys == 0) return -4;
table[pdpt_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3);
table[pdpt_idx] = new_phys | EPT_RWX;
table = (u64 *)phys_to_virt(new_phys);
} else {
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
@@ -210,7 +211,7 @@ void ept_flush_ept(void) {
int err;
__asm__ volatile(
"invept (%2), %1\n"
"jnc 1f\n"
"ja 1f\n"
"mov $1, %0\n"
"jmp 2f\n"
"1:\n"
@@ -234,7 +235,7 @@ void ept_flush_vpid(u16 vpid) {
int err;
__asm__ volatile(
"invvpid (%2), %1\n"
"jnc 1f\n"
"ja 1f\n"
"mov $1, %0\n"
"jmp 2f\n"
"1:\n"
@@ -296,7 +297,8 @@ u64 ept_gpa_to_hpa(u64 gpa) {
u64 ept_get_eptp(void) {
if (!g_ept_ready) return 0;
/* EPTP: bits [5:0] = memtype, bits [7:6] = walk length-1 (3 for 4-level),
* bits [51:12] = PML4 HPA */
/* EPTP: bits [2:0]=memory type WB(6), bits [5:3]=walk length-1 (3 => 4-level).
* Nested KVM accepted WB (eptp=...1e) on the passing self-test; forcing UC
* after a misread of EPT_VPID_CAP bit 8 regressed to error 7. */
return g_ept_pml4_phys | EPTP_WALK_LEN_4 | EPTP_MEMTYPE_WB;
}
+148 -40
View File
@@ -17,6 +17,7 @@
#include <utsm/ept.h>
#include <utsm/vmm.h>
#include <utsm/virtio_mmio.h>
#include <utsm/ipc_shm.h>
#include <utsm/log.h>
#include <utsm/types.h>
#include "../arch/x86_64/limine.h"
@@ -50,14 +51,6 @@ static inline u64 read_cr4_local(void) {
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, all zeros = don't intercept any MSR) */
static u8 g_linux_msr_bitmap[4096] __attribute__((aligned(4096)));
@@ -125,11 +118,13 @@ static void linux_vmcs_setup_host_state(void) {
/* ===== VMCS guest state for Linux ===== */
static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
/* CR0/CR3/CR4: long mode with paging */
/* CR0/CR3/CR4: long mode with paging.
* P8.5: CR4 必须含 VMXE(IA32_VMX_CR4_FIXED0=0x2000 强制,
* 旧代码漏掉会触发 VM-entry invalid guest state)。 */
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
/* CR3 points to guest PML4 (GPA) */
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_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
/* Segment selectors from guest GDT */
vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08);
@@ -165,14 +160,18 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28);
vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF);
/* Access rights */
/* Access rights.
* P8.5: selector=0 的段必须标记 unusable(access rights bit 16),
* 否则 VM-entry guest-state 检查失败:
* FS/GS selector=0 → 0x1C093(unusable)
* LDTR selector=0 → 0x10082(unusable,旧值 0x82 缺 bit 16) */
vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); /* 64-bit code, 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_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); /* busy 64-bit TSS */
/* Guest RIP = Linux 64-bit entry (startup_64) */
@@ -204,10 +203,22 @@ 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) {
log_hex64("[LINUX] TRUE_PINBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS));
log_hex64("[LINUX] TRUE_PROCBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PROCBASED_CTLS));
log_hex64("[LINUX] PROCBASED_CTLS2 raw=",
vmx_read_msr(IA32_VMX_PROCBASED_CTLS2));
log_hex64("[LINUX] TRUE_EXIT_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_EXIT_CTLS));
log_hex64("[LINUX] TRUE_ENTRY_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_ENTRY_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;
u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING |
PIN_VIRTUAL_NMIS;
if (vmx_preemption_timer_supported()) {
pin_want |= PIN_VMX_PREEMPTION_TIMER;
}
@@ -222,13 +233,12 @@ static void linux_vmcs_setup_controls(u64 eptp) {
| CPU_BASED_INVLPG_EXITING;
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
/* Secondary: EPT + VPID */
/* Secondary: EPT + VPID(经 capability MSR 调整;
* EPT 不可用则打日志——Linux guest 的 GPA 布局依赖 EPT)。 */
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;
cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2);
if (!(cpu2 & SEC_EXEC_ENABLE_EPT)) {
log_warn("[LINUX] EPT not available (shadow paging) - guest GPA map disabled");
}
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
@@ -249,20 +259,31 @@ static void linux_vmcs_setup_controls(u64 eptp) {
/* MSR bitmap */
{
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
u64 msr_bitmap_phys = (u64)g_linux_msr_bitmap - hhdm;
u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_linux_msr_bitmap);
log_hex64("[LINUX] MSR bitmap phys=", msr_bitmap_phys);
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
}
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
vmx_vmcs_write(VMCS_VPID, 2); /* VPID 1 = self-test, VPID 2 = Linux */
if (cpu2 & SEC_EXEC_ENABLE_EPT) {
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
} else {
vmx_vmcs_write(VMCS_EPT_POINTER, 0);
}
if (cpu2 & SEC_EXEC_ENABLE_VPID) {
vmx_vmcs_write(VMCS_VPID, 2); /* VPID 1 = self-test, VPID 2 = Linux */
} else {
vmx_vmcs_write(VMCS_VPID, 0);
}
/* Exception bitmap: catch #GP, #PF, #UD, #DF (triple fault handled separately) */
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6) | (1ULL << 8));
/* Linux owns its IDT. Intercepting #PF here livelocks: the guest
* page-fault handler never runs, so early mm faults spin in VM-exit. */
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, 0);
}
/* ===== Global for passing RSI to launch asm ===== */
static volatile u64 g_linux_rsi;
static volatile int g_linux_launch_failed;
static volatile u64 g_linux_launch_flags;
/* ===== Linux launch =====
*
@@ -306,17 +327,44 @@ 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());
/* 仅在 pin-based control 真正启用 timer 时 arm。 */
if (vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL) &
PIN_VMX_PREEMPTION_TIMER) {
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER,
vmx_preemption_quantum());
} else {
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, 0);
}
/* Set RSI for Linux boot_params (passed via global, loaded in asm) */
g_linux_rsi = gi->bootparams_gpa;
/* P8.5: VMCLEAR + VMPTRLD —— VMLAUNCH 要求 VMCS 处于 "clear" 态。
* 自检成功后 VMCS 状态为 "launched",不 VMCLEAR 会报
* "VMLAUNCH with non-clear VMCS"(error 4)。
* 内容不受影响:VMCLEAR 只回写内存并改状态,VMPTRLD 再挂载。 */
if (vmx_vmcs_clear(vmm_get_vmcs_phys()) != 0) {
log_error("[LINUX] vmclear failed");
return -4;
}
if (vmx_vmcs_load(vmm_get_vmcs_phys()) != 0) {
log_error("[LINUX] vmptrld after vmclear failed");
return -5;
}
log_hex64("[LINUX] RIP=", gi->kernel_entry);
log_hex64("[LINUX] RSP=", LINUX_GUEST_STACK_GPA + 0x1000);
log_hex64("[LINUX] RSI=", gi->bootparams_gpa);
log_hex64("[LINUX] CR3=", gi->pgt_gpa);
log_hex64("[LINUX] GDTR=", gi->gdt_gpa);
log_hex64("[LINUX] pin=", vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL));
log_hex64("[LINUX] cpu=", vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL));
log_hex64("[LINUX] cpu2=", vmx_vmcs_read(VMCS_SECONDARY_VM_EXEC_CONTROL));
log_hex64("[LINUX] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS));
log_hex64("[LINUX] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS));
log_hex64("[LINUX] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER));
log_hex64("[LINUX] msr_bitmap=", vmx_vmcs_read(VMCS_MSR_BITMAP));
log_hex64("[LINUX] vpid=", vmx_vmcs_read(VMCS_VPID));
log_info("[LINUX] vmlaunch");
/* vmlaunch with RSI = boot_params.
@@ -324,38 +372,98 @@ int linux_launch(void) {
* - On failure: failed=1, falls through
* - On success: enters guest, VM-Exit → vmx_vm_exit_handler
* - On terminate: handler restores RSP, jmps to post_guest label */
int failed;
g_linux_launch_failed = 1;
g_linux_launch_flags = 0;
__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"
/* Linux 64-bit boot protocol requires every GPR except RSI zero. */
"xorl %%eax, %%eax\n\t"
"xorl %%ebx, %%ebx\n\t"
"xorl %%ecx, %%ecx\n\t"
"xorl %%edx, %%edx\n\t"
"xorl %%edi, %%edi\n\t"
"xorl %%ebp, %%ebp\n\t"
"xorl %%r8d, %%r8d\n\t"
"xorl %%r9d, %%r9d\n\t"
"xorl %%r10d, %%r10d\n\t"
"xorl %%r11d, %%r11d\n\t"
"xorl %%r12d, %%r12d\n\t"
"xorl %%r13d, %%r13d\n\t"
"xorl %%r14d, %%r14d\n\t"
"xorl %%r15d, %%r15d\n\t"
"movq g_linux_rsi(%%rip), %%rsi\n\t" /* RSI = boot_params GPA */
"vmlaunch\n\t"
/* ---- failure path ---- */
"movl $1, %0\n\t"
"movl $1, g_linux_launch_failed(%%rip)\n\t"
"pushfq\n\t"
"popq g_linux_launch_flags(%%rip)\n\t"
"jmp 2f\n\t"
/* ---- post_guest: terminate path jmps here ---- */
"1:\n\t"
"movl $0, %0\n\t"
"movl $0, g_linux_launch_failed(%%rip)\n\t"
"2:\n\t"
: "=r"(failed)
:: "rax", "rcx", "rdx", "rsi", "rdi",
"r8", "r9", "r10", "r11", "memory"
:
:
: "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
"cc", "memory"
);
int failed = g_linux_launch_failed;
u64 vm_flags = g_linux_launch_flags;
if (failed) {
u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR);
/* CF=1 → VMfailInvalid;ZF=1 → VMfailValid(error field 有效)。 */
if (vm_flags & 0x1) {
log_error("[LINUX] vmlaunch VMfailInvalid (invalid current-VMCS)");
} else if (vm_flags & 0x40) {
log_error("[LINUX] vmlaunch VMfailValid");
}
u64 error = 0;
if (vm_flags & 0x40) {
vmx_vmcs_read_checked(VMCS_VMX_INSTRUCTION_ERROR, &error);
}
log_hex64("[LINUX] vmlaunch failed, error=", error);
g_linux_guest_active = 0;
return -3;
}
/* Guest parked (HLT) — Linux daemon 已驻留,等待 linux_resume() 唤醒 */
g_linux_guest_active = 0; /* 回到 host 上下文,清除 active 标志 */
g_guest_parked = 1; /* 标记 guest 已 park,linux_resume() 可唤醒 */
log_info("[LINUX] guest parked (daemon ready)");
/* Only handle_hlt() may mark the Linux guest parked. A fatal exception,
* triple fault, or EPT failure also returns through post_guest, but must
* not make the compatibility layer report a ready daemon. */
g_linux_guest_active = 0;
log_hex64("[LINUX] last exit reason=", g_last_exit_reason);
log_hex64("[LINUX] vmexit count=", vmexit_get_count());
if (!g_guest_parked) {
log_error("[LINUX] guest terminated before daemon park");
return -6;
}
/* A kernel idle/fatal HLT is indistinguishable from the daemon's PARK at
* the VM-exit reason level. The daemon must first publish EXEC_READY
* through /dev/utsm; this proves the driver PING, SHM mapping, userspace
* daemon, and Linux→UTSM ring are all alive. */
{
struct utsm_ipc_msg ready;
int got_ready = 0;
for (int i = 0; i < 8; i++) {
if (ipc_shm_recv(&ready) != 0) break;
if (ready.type == UTSM_MSG_EXEC_READY) {
got_ready = 1;
break;
}
log_hex64("[LINUX] pre-ready message type=", ready.type);
}
if (!got_ready) {
g_guest_parked = 0;
log_error("[LINUX] HLT without EXEC_READY handshake");
return -7;
}
}
log_info("[LINUX] EXEC_READY received from /dev/utsm");
log_info("[LINUX] guest parked (daemon ready)");
return 0;
}
+122 -47
View File
@@ -8,26 +8,36 @@
extern volatile struct limine_hhdm_request g_hhdm_request;
/* ===== VMX 指令错误码 ===== */
/* ===== VMX 指令错误码(Intel SDM Vol 3D, "VM Instruction Error Numbers") ===== */
static const char *vmx_error_str(u64 err) {
switch (err) {
case 1: return "VMCALL/VMCLEAR/VMLAUNCH/VMRESUME with non-launched VMCS";
case 2: return "VMRESUME after VMXOFF";
case 3: return "VMRESUME with corrupted VMCS";
case 4: return "vmlaunch with non-clear VMCS";
case 5: return "VM operation invalid";
case 6: return "VMCS revision mismatch";
case 7: return "VMCS shadowing mismatch";
case 8: return "invalid guest state";
case 9: return "host state invalid";
case 10: return "VM-execution control invalid";
case 11: return "VM-exit control invalid";
case 12: return "VM-entry control invalid";
case 13: return "VM-exit control fields invalid";
case 15: return "address out of width";
case 16: return "MSR bitmap addr invalid";
case 17: return "VMREAD/VMWRITE from unsupported field";
case 18: return "VMCS addr invalid";
case 1: return "VMCALL executed in VMX root operation";
case 2: return "VMCLEAR with invalid physical address";
case 3: return "VMCLEAR with VMXON pointer";
case 4: return "VMLAUNCH with non-clear VMCS";
case 5: return "VMRESUME with non-launched VMCS";
case 6: return "VMRESUME after VMXOFF";
case 7: return "VM entry with invalid VM-execution control fields";
case 8: return "VM entry with invalid host-state fields";
case 9: return "VMPTRLD with invalid physical address";
case 10: return "VMPTRLD with VMXON pointer";
case 11: return "VMPTRLD with incorrect VMCS revision identifier";
case 12: return "VMREAD from unsupported VMCS component";
case 13: return "VMWRITE to read-only VMCS component";
case 15: return "VM entry with invalid guest-state fields";
case 16: return "VM entry with invalid executive-VMCS pointer";
case 17: return "VM entry with non-launched executive VMCS";
case 18: return "VM entry with executive-VMCS inconsistency";
case 19: return "VM entry with invalid VM-exit control fields";
case 20: return "VM entry with invalid MSEG VMCS revision";
case 22: return "VMCALL with non-clear VMCS";
case 23: return "VMCALL with invalid VM-exit control fields";
case 24: return "VMCALL with incorrect MSEG VMCS revision";
case 25: return "VM switch with invalid VM-exit control fields";
case 26: return "VM switch with invalid VM-exit MSEG VMCS revision";
case 28: return "VMRETURN with non-launched VMCS";
case 29: return "VMRETURN with invalid executive VMCS";
case 30: return "VM entry with invalid VM-execution control fields in event injection";
default: return "unknown";
}
}
@@ -78,6 +88,17 @@ static u8 g_guest_code[4096] __attribute__((aligned(4096))) = {
/* guest 栈页 */
static u8 g_guest_stack[4096] __attribute__((aligned(4096)));
/* P8.5: guest 自有页表(EPT 模式下 guest CR3 不能指向 host 页表——
* host 页表页未在 EPT 映射,页表遍历本身就会 EPT violation)。
* 布局(GPA):
* 0x6000 PML4 [0] -> 0x7000 | P|RW
* 0x7000 PDPT [0] -> 0x9000 | P|RW
* 0x9000 PD [0] -> 2MB 大页 | P|RW|PS (GPA 0..2MB 恒等映射)
* guest 代码/栈/GDT/TSS/IDT 页全部落在低 2MB 内。 */
static u64 g_guest_pml4[512] __attribute__((aligned(4096)));
static u64 g_guest_pdpt[512] __attribute__((aligned(4096)));
static u64 g_guest_pd[512] __attribute__((aligned(4096)));
/* MSR bitmap(4KB 全 0 = 不拦截 MSR) */
static u8 g_msr_bitmap[4096] __attribute__((aligned(4096)));
@@ -162,14 +183,6 @@ static inline u64 read_cr3_local(void) {
/* ===== VMCS 配置 ===== */
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;
}
static void 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());
@@ -235,10 +248,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 页表。
* P8.4: CR4 must include VMXE (CR4_FIXED0 requires it under KVM). */
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE。
* P8.5: CR3 指向 guest 自有页表(GPA 0x6000),
* 不再借用 host CR3(EPT 下 host 页表页未映射,遍历会失败)。 */
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_CR3, 0x6000);
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
/* 段选择子:flat 模型。
@@ -324,7 +338,19 @@ static void vmcs_setup_guest_state_phase1(void) {
}
static void vmcs_setup_controls(u64 eptp) {
u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING,
log_hex64("[VMM] TRUE_PINBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS));
log_hex64("[VMM] TRUE_PROCBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PROCBASED_CTLS));
log_hex64("[VMM] PROCBASED_CTLS2 raw=",
vmx_read_msr(IA32_VMX_PROCBASED_CTLS2));
log_hex64("[VMM] TRUE_EXIT_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_EXIT_CTLS));
log_hex64("[VMM] TRUE_ENTRY_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_ENTRY_CTLS));
u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING |
PIN_VIRTUAL_NMIS,
IA32_VMX_TRUE_PINBASED_CTLS);
u64 cpu = CPU_BASED_HLT_EXITING
@@ -334,11 +360,12 @@ static void vmcs_setup_controls(u64 eptp) {
| CPU_BASED_INVLPG_EXITING;
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
/* 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 */
/* P8.5: 请求 EPT(KVM 报告的 allowed-1 集合含 EPT 位)。
* 自检 guest 用 GPA 0x1000..0x9000 + 自有页表,必须开 EPT
* 才能让 ept_map_range 的映射真正生效。
* EPT 不可用时 cpu2 会失去 EPT 位并打日志,自检将失败。 */
u64 cpu2 = SEC_EXEC_ENABLE_EPT;
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
@@ -358,8 +385,8 @@ static void vmcs_setup_controls(u64 eptp) {
/* MSR bitmap 物理地址 */
{
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
u64 msr_bitmap_phys = (u64)g_msr_bitmap - hhdm;
u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_msr_bitmap);
log_hex64("[VMM] MSR bitmap phys=", msr_bitmap_phys);
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
}
@@ -371,6 +398,8 @@ static void vmcs_setup_controls(u64 eptp) {
}
if (cpu2 & SEC_EXEC_ENABLE_VPID) {
vmx_vmcs_write(VMCS_VPID, 1);
} else {
vmx_vmcs_write(VMCS_VPID, 0);
}
/* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */
@@ -430,6 +459,9 @@ void vmm_shutdown(void) {
int vmm_is_ready(void) { return g_vmm_ready; }
u64 vmm_get_eptp(void) { return ept_get_eptp(); }
/* P8.5: 暴露 VMCS 物理地址,供 linux_launch() 在 vmlaunch 前
* 执行 VMCLEAR+VMPTRLD(VMCS 状态机:VMLAUNCH 要求 clear 态)。 */
u64 vmm_get_vmcs_phys(void) { return g_vmcs_phys; }
/* P8.4: Dump all guest-state VMCS fields for KVM nested VMX debugging.
* Called before VMLAUNCH to identify field encoding/value errors. */
@@ -532,12 +564,12 @@ int vmm_self_test(void) {
/* EPT 映射 guest 代码页(GPA 0x1000)、栈页(GPA 0x8000)、
* GDT 页(GPA 0x3000)、TSS 页(GPA 0x4000)。
* g_guest_* 是 UTSM 虚拟地址,物理地址 = virt - HHDM */
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
u64 guest_code_phys = (u64)g_guest_code - hhdm;
u64 guest_stack_phys = (u64)g_guest_stack - hhdm;
u64 guest_gdt_phys = (u64)g_guest_gdt - hhdm;
u64 guest_tss_phys = (u64)g_guest_tss - hhdm;
* g_guest_* 属于高半 UTSM 内核映像,必须使用 Limine 提供的
* kernel virtual/physical base 转换;它们不是 HHDM 指针。 */
u64 guest_code_phys = vmx_kernel_virt_to_phys(g_guest_code);
u64 guest_stack_phys = vmx_kernel_virt_to_phys(g_guest_stack);
u64 guest_gdt_phys = vmx_kernel_virt_to_phys(g_guest_gdt);
u64 guest_tss_phys = vmx_kernel_virt_to_phys(g_guest_tss);
if (ept_map_range(0x1000, guest_code_phys, 4096, EPT_RWX) != 0) {
log_error("[VMM] map guest code failed");
@@ -557,7 +589,7 @@ int vmm_self_test(void) {
}
/* P8.4: Map IDT page for KVM nested VMX (IDTR base = 0x5000) */
{
u64 guest_idt_phys = (u64)g_guest_idt - hhdm;
u64 guest_idt_phys = vmx_kernel_virt_to_phys(g_guest_idt);
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");
@@ -565,6 +597,29 @@ int vmm_self_test(void) {
}
}
/* P8.5: 构建 guest 自有页表(GPA 低 2MB 恒等,2MB 大页)并 EPT 映射。
* 页表项:P(bit0)|RW(bit1),大页加 PS(bit7);NX=0(可执行)。 */
{
for (int i = 0; i < 512; i++) {
g_guest_pml4[i] = 0;
g_guest_pdpt[i] = 0;
g_guest_pd[i] = 0;
}
g_guest_pml4[0] = 0x7000 | 0x3; /* P|RW -> PDPT @ GPA 0x7000 */
g_guest_pdpt[0] = 0x9000 | 0x3; /* P|RW -> PD @ GPA 0x9000 */
g_guest_pd[0] = 0x0000 | 0x83; /* P|RW|PS -> 2MB page @ GPA 0 */
u64 pml4_phys = vmx_kernel_virt_to_phys(g_guest_pml4);
u64 pdpt_phys = vmx_kernel_virt_to_phys(g_guest_pdpt);
u64 pd_phys = vmx_kernel_virt_to_phys(g_guest_pd);
if (ept_map_range(0x6000, pml4_phys, 4096, EPT_READ | EPT_WRITE) != 0 ||
ept_map_range(0x7000, pdpt_phys, 4096, EPT_READ | EPT_WRITE) != 0 ||
ept_map_range(0x9000, pd_phys, 4096, EPT_READ | EPT_WRITE) != 0) {
log_error("[VMM] map guest page tables failed");
return -12;
}
}
/* 配置 VMCS */
vmcs_setup_guest_state_phase1();
vmcs_setup_controls(ept_get_eptp());
@@ -590,36 +645,56 @@ int vmm_self_test(void) {
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] msr_bitmap=", vmx_vmcs_read(VMCS_MSR_BITMAP));
log_hex64("[VMM] vpid=", vmx_vmcs_read(VMCS_VPID));
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
* - 失败:执行下一条指令,failed=1(同时捕获 RFLAGS 区分
* VMfailValid CF=1 / VMfailInvalid ZF=1)
* - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。
* - guest 终止时,handler 恢复 saved_rsp 并 jmp 到 post_guest 标签。
*
* 我们在 vmlaunch 前保存 RSP 与"vmlaunch 后"的 RIP(= post_guest 标签地址)。 */
int failed;
u64 vm_flags = 0;
__asm__ volatile(
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */
"leaq 1f(%%rip), %%rax\n\t" /* 取 post_guest 标签地址 */
"movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */
"vmlaunch\n\t" /* 启动 guest */
/* ---- 失败路径 ---- */
/* ---- 失败路径(mov 不改 flags,先存 failed 再抓 RFLAGS) ---- */
"movl $1, %0\n\t"
"pushfq\n\t"
"popq %1\n\t"
"jmp 2f\n\t"
/* ---- post_guest 标签:终止路径 jmp 到这里 ---- */
"1:\n\t"
"movl $0, %0\n\t"
"2:\n\t"
: "=r"(failed)
: "=r"(failed), "=r"(vm_flags)
:: "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory"
);
if (failed) {
u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR);
/* CF(bit0)=1 → VMfailInvalid(current-VMCS 无效);
* ZF(bit6)=1 → VMfailValid(错误码在 VMCS 0x4400)。 */
if (vm_flags & 0x1) {
log_error("[VMM] vmlaunch VMfailInvalid (invalid current-VMCS)");
} else if (vm_flags & 0x40) {
log_error("[VMM] vmlaunch VMfailValid");
} else {
log_error("[VMM] vmlaunch failed (unexpected flags)");
}
u64 error = 0;
if (!(vm_flags & 0x40)) {
log_error("[VMM] VM-instruction error unavailable");
} else if (vmx_vmcs_read_checked(VMCS_VMX_INSTRUCTION_ERROR, &error) != 0) {
log_error("[VMM] VMREAD error field failed (current-VMCS invalid)");
}
log_hex64("[VMM] vmlaunch failed, error=", error);
serial_write("[VMM] ");
serial_write(vmx_error_str(error));
+60 -5
View File
@@ -6,6 +6,7 @@
#include "../arch/x86_64/limine.h"
extern volatile struct limine_hhdm_request g_hhdm_request;
extern volatile struct limine_kernel_address_request g_kernel_address_request;
/* ===== MSR / CR helpers ===== */
@@ -15,6 +16,41 @@ u64 vmx_read_msr(u32 msr) {
return ((u64)hi << 32) | lo;
}
u64 vmx_adjust_control(u64 value, u32 msr) {
u64 raw = vmx_read_msr(msr);
u32 allowed0 = (u32)raw;
u32 allowed1 = (u32)(raw >> 32);
/* KVM / nested-KVM polarity (verified 2026-09-06):
* low bit=1 => must be 1
* high bit=0 => must be 0
* Secondary CTLS2 reports allowed0=0; inverting low would force every
* allowed1 feature on and fail VM-entry check 7. */
u32 adjusted = ((u32)value | allowed0) & allowed1;
log_hex64("[VMX] adjust msr=", msr);
log_hex64("[VMX] adjust desired=", value);
log_hex64("[VMX] adjust allowed0=", allowed0);
log_hex64("[VMX] adjust allowed1=", allowed1);
log_hex64("[VMX] adjust result=", adjusted);
return adjusted;
}
u64 vmx_kernel_virt_to_phys(const void *address) {
const struct limine_kernel_address_response *response =
g_kernel_address_request.response;
u64 virt = (u64)address;
if (!response) {
log_error("[VMX] Limine kernel-address response missing");
return 0;
}
if (virt < response->virtual_base) {
log_error("[VMX] address is below kernel virtual base");
return 0;
}
return response->physical_base + (virt - response->virtual_base);
}
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
u32 lo = (u32)(value & 0xFFFFFFFFULL);
u32 hi = (u32)(value >> 32);
@@ -176,6 +212,8 @@ static int vmx_truly_available(void) {
return 0;
}
/* P8.5: VMX 指令成功条件是 CF=0 且 ZF=0(即 ja)。
* 旧代码只查 CF(jnc):VMfailInvalid(ZF=1, CF=0)会被误判为成功。 */
int vmx_enable(void) {
if (g_vmx_enabled) return 0;
@@ -221,7 +259,7 @@ int vmx_enable(void) {
int err;
__asm__ volatile(
"vmxon %1\n"
"jnc 1f\n"
"ja 1f\n"
"jmp 2f\n"
"1:\n"
"mov $0, %0\n"
@@ -276,7 +314,7 @@ int vmx_vmcs_load(u64 phys) {
int err;
__asm__ volatile(
"vmptrld %1\n"
"jnc 1f\n"
"ja 1f\n"
"jmp 2f\n"
"1:\n"
"mov $0, %0\n"
@@ -300,7 +338,7 @@ int vmx_vmcs_clear(u64 phys) {
int err;
__asm__ volatile(
"vmclear %1\n"
"jnc 1f\n"
"ja 1f\n"
"jmp 2f\n"
"1:\n"
"mov $0, %0\n"
@@ -330,6 +368,23 @@ u64 vmx_vmcs_read(u64 field) {
return value;
}
int vmx_vmcs_read_checked(u64 field, u64 *value) {
u64 v;
u8 ok;
__asm__ volatile(
"vmread %2, %1\n"
"seta %0\n" /* CF=0 且 ZF=0 才是 VMsucceed */
: "=q"(ok), "=r"(v)
: "r"(field)
: "memory"
);
if (ok) {
if (value) *value = v;
return 0;
}
return -1;
}
void vmx_vmcs_write(u64 field, u64 value) {
__asm__ volatile(
"vmwrite %1, %0\n"
@@ -342,7 +397,7 @@ int vmx_vmlaunch(void) {
int err;
__asm__ volatile(
"vmlaunch\n"
"jnc 1f\n"
"ja 1f\n"
"jmp 2f\n"
"1:\n"
"mov $0, %0\n"
@@ -360,7 +415,7 @@ int vmx_vmresume(void) {
int err;
__asm__ volatile(
"vmresume\n"
"jnc 1f\n"
"ja 1f\n"
"jmp 2f\n"
"1:\n"
"mov $0, %0\n"
+6 -6
View File
@@ -48,7 +48,7 @@ static inline u64 read_cr4_local(void) {
return v;
}
static u64 vmx_adjust_control(u64 value, u32 msr) {
static u64 xj380_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;
@@ -186,14 +186,14 @@ static void xj380_vmcs_setup_controls(u64 eptp) {
if (vmx_preemption_timer_supported()) {
pin_want |= PIN_VMX_PREEMPTION_TIMER;
}
u64 pin = vmx_adjust_control(pin_want, IA32_VMX_TRUE_PINBASED_CTLS);
u64 pin = xj380_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);
cpu = xj380_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
{
@@ -206,12 +206,12 @@ static void xj380_vmcs_setup_controls(u64 eptp) {
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);
exit_ctrl = xj380_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);
entry_ctrl = xj380_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);
@@ -270,7 +270,7 @@ int xj380_launch(void) {
xj380_vmcs_setup_host_state();
/* 初始 arm preemption timer(1ms 后首次周期 exit) */
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum());
/* KernelMain 3 个 SysV 参数(HHDM 虚拟地址) */
g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET;