ohhhhhhhhhhh (exclude linux-extra-rootfs.img build artifact)
This commit is contained in:
+145
-10
@@ -60,6 +60,14 @@ int ept_init(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Leaf EPT entry: HPA + R/W/X + EPT memory type + Ignore PAT.
|
||||
* Nested KVM combines guest PAT with EPT type unless bit 6 is set;
|
||||
* WB+WC is reserved and VM-exits as EPT misconfiguration. */
|
||||
static u64 ept_make_leaf(u64 hpa, u64 flags, u64 memtype) {
|
||||
return (hpa & 0x000FFFFFFFFFF000ULL) | (flags & 7ULL) |
|
||||
((memtype & 7ULL) << 3) | EPT_IGNORE_PAT;
|
||||
}
|
||||
|
||||
/* 读取 EPT 表项指针,必要时分配中间页表。
|
||||
* level: 0=PT, 1=PD, 2=PDPT, 3=PML4 */
|
||||
static u64 *ept_walk(u64 gpa, int alloc_missing) {
|
||||
@@ -81,10 +89,29 @@ static u64 *ept_walk(u64 gpa, int alloc_missing) {
|
||||
table[indices[level]] = new_phys | EPT_RWX;
|
||||
/* 切换到新分配的下一级页表 */
|
||||
table = (u64 *)phys_to_virt(new_phys);
|
||||
} else if ((entry & EPT_LARGE_PAGE) && level == 1) {
|
||||
/* PD 2MB leaf: split into 512×4K before installing a 4K PTE.
|
||||
* Treating the 2MB HPA as a page-table pointer would write PTEs
|
||||
* into guest RAM and leave the PD entry as a large page. */
|
||||
if (!alloc_missing) return (u64 *)0;
|
||||
u64 new_pt = ept_alloc_page();
|
||||
if (new_pt == 0) return (u64 *)0;
|
||||
u64 *pt = (u64 *)phys_to_virt(new_pt);
|
||||
u64 hpa_2m = entry & 0x000FFFFFFFE00000ULL;
|
||||
u64 page_flags = entry & 7ULL;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
pt[i] = ept_make_leaf(hpa_2m + (u64)i * EPT_PAGE_SIZE,
|
||||
page_flags, EPT_MEMORY_TYPE_WB);
|
||||
}
|
||||
table[indices[level]] = new_pt | EPT_RWX;
|
||||
table = pt;
|
||||
} else {
|
||||
/* 清除标志位取出物理地址 */
|
||||
u64 child_phys = entry & 0x000FFFFFFFFFF000ULL;
|
||||
table = (u64 *)phys_to_virt(child_phys);
|
||||
/* Strip reserved memory-type / IPAT / PS bits from non-leaf
|
||||
* table pointers. Those bits are only legal on page leaves. */
|
||||
u64 cleaned = entry & ~0xF8ULL;
|
||||
if (cleaned != entry)
|
||||
table[indices[level]] = cleaned;
|
||||
table = (u64 *)phys_to_virt(cleaned & 0x000FFFFFFFFFF000ULL);
|
||||
}
|
||||
}
|
||||
return &table[indices[0]];
|
||||
@@ -107,7 +134,7 @@ int ept_map_range(u64 gpa, u64 hpa, u64 size, u64 flags) {
|
||||
log_error("[EPT] walk failed");
|
||||
return -3;
|
||||
}
|
||||
*entry = cur_hpa | flags | (EPT_MEMORY_TYPE_WB << 3);
|
||||
*entry = ept_make_leaf(cur_hpa, flags, EPT_MEMORY_TYPE_WB);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -163,11 +190,12 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) {
|
||||
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
|
||||
}
|
||||
|
||||
/* PD:直接写 2MB 大页项 */
|
||||
/* PD:直接写 2MB 大页项(Ignore PAT so nested KVM does not combine PAT) */
|
||||
u64 pd_entry = (hpa & 0x000FFFFFFFE00000ULL) /* 物理地址(2MB 对齐) */
|
||||
| flags /* R/W/X 权限 */
|
||||
| EPT_LARGE_PAGE /* bit 7 = 大页标志 */
|
||||
| (memtype << 3); /* memory type */
|
||||
| ((memtype & 7ULL) << 3) /* memory type */
|
||||
| EPT_IGNORE_PAT;
|
||||
table[pd_idx] = pd_entry;
|
||||
return 0;
|
||||
}
|
||||
@@ -205,10 +233,12 @@ void ept_check_vpid_support(void) {
|
||||
/* 执行 INVEPT(EPT TLB 刷新)。真机要求在修改 EPT 后调用。 */
|
||||
void ept_flush_ept(void) {
|
||||
if (!g_invept_supported) return;
|
||||
/* INVEPT 类型 2: all-context invalidate
|
||||
* 操作数:{EPTP=0, reserved=0} 表示刷新所有 EPT 上下文 */
|
||||
u64 desc[2] = {0, 0};
|
||||
/* INVEPT 描述符必须 16 字节对齐;未对齐时指令失败,nested KVM
|
||||
* 会一直用缓存里的坏 EPT 项,表现为同一 GPA 的 misconfig 死循环。 */
|
||||
_Alignas(16) u64 desc[2];
|
||||
int err;
|
||||
desc[0] = ept_get_eptp();
|
||||
desc[1] = 0;
|
||||
__asm__ volatile(
|
||||
"invept (%2), %1\n"
|
||||
"ja 1f\n"
|
||||
@@ -218,9 +248,25 @@ void ept_flush_ept(void) {
|
||||
"mov $0, %0\n"
|
||||
"2:\n"
|
||||
: "=r"(err)
|
||||
: "r"((u64)2), "r"(desc)
|
||||
: "r"((u64)1), "r"(desc)
|
||||
: "memory"
|
||||
);
|
||||
if (err) {
|
||||
desc[0] = 0;
|
||||
desc[1] = 0;
|
||||
__asm__ volatile(
|
||||
"invept (%2), %1\n"
|
||||
"ja 1f\n"
|
||||
"mov $1, %0\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"2:\n"
|
||||
: "=r"(err)
|
||||
: "r"((u64)2), "r"(desc)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
if (err) {
|
||||
log_warn("[EPT] INVEPT failed (non-critical on QEMU)");
|
||||
}
|
||||
@@ -292,9 +338,98 @@ u64 ept_gpa_to_hpa(u64 gpa) {
|
||||
entry = table[pt_idx];
|
||||
if (!(entry & EPT_READ)) return 0;
|
||||
u64 hpa = entry & 0x000FFFFFFFFFF000ULL;
|
||||
if (hpa == 0 || hpa >= 0x100000000ULL) return 0;
|
||||
return hpa + offset;
|
||||
}
|
||||
|
||||
int ept_repair_leaf(u64 gpa, u64 flags) {
|
||||
if (!g_ept_ready) return -1;
|
||||
|
||||
u64 *table = (u64 *)g_ept_pml4_virt;
|
||||
u64 pml4_idx = (gpa >> 39) & 0x1FF;
|
||||
u64 pdpt_idx = (gpa >> 30) & 0x1FF;
|
||||
u64 pd_idx = (gpa >> 21) & 0x1FF;
|
||||
u64 pt_idx = (gpa >> 12) & 0x1FF;
|
||||
|
||||
u64 entry = table[pml4_idx];
|
||||
if (!(entry & EPT_READ)) return -1;
|
||||
{
|
||||
u64 cleaned = entry & ~0xF8ULL;
|
||||
if (cleaned != entry)
|
||||
table[pml4_idx] = cleaned;
|
||||
table = (u64 *)phys_to_virt(cleaned & 0x000FFFFFFFFFF000ULL);
|
||||
}
|
||||
|
||||
entry = table[pdpt_idx];
|
||||
if (!(entry & EPT_READ)) return -1;
|
||||
if (entry & EPT_LARGE_PAGE) {
|
||||
u64 hpa = entry & 0x000FFFFFC0000000ULL;
|
||||
table[pdpt_idx] = hpa | (flags & 7ULL) | EPT_LARGE_PAGE |
|
||||
(EPT_MEMORY_TYPE_WB << 3) | EPT_IGNORE_PAT;
|
||||
return 0;
|
||||
}
|
||||
{
|
||||
u64 cleaned = entry & ~0xF8ULL;
|
||||
if (cleaned != entry)
|
||||
table[pdpt_idx] = cleaned;
|
||||
table = (u64 *)phys_to_virt(cleaned & 0x000FFFFFFFFFF000ULL);
|
||||
}
|
||||
|
||||
entry = table[pd_idx];
|
||||
if (!(entry & EPT_READ)) return -1;
|
||||
if (entry & EPT_LARGE_PAGE) {
|
||||
u64 hpa = entry & 0x000FFFFFFFE00000ULL;
|
||||
table[pd_idx] = hpa | (flags & 7ULL) | EPT_LARGE_PAGE |
|
||||
(EPT_MEMORY_TYPE_WB << 3) | EPT_IGNORE_PAT;
|
||||
return 0;
|
||||
}
|
||||
{
|
||||
u64 cleaned = entry & ~0xF8ULL;
|
||||
if (cleaned != entry)
|
||||
table[pd_idx] = cleaned;
|
||||
table = (u64 *)phys_to_virt(cleaned & 0x000FFFFFFFFFF000ULL);
|
||||
}
|
||||
|
||||
entry = table[pt_idx];
|
||||
if (!(entry & 7ULL)) return -1;
|
||||
{
|
||||
u64 hpa = entry & 0x000FFFFFFFFFF000ULL;
|
||||
/* DMA pool is low 4G. A 4K leaf with PS, execute-only, or an
|
||||
* out-of-range HPA is leftover RAM interpreted as a PTE — keeping
|
||||
* that HPA (0xa5000008000 on the last boot) loops EPT misconfig. */
|
||||
if ((entry & EPT_LARGE_PAGE) || hpa == 0 || hpa >= 0x100000000ULL ||
|
||||
(entry & 7ULL) == EPT_EXECUTE)
|
||||
return -1;
|
||||
table[pt_idx] = ept_make_leaf(hpa, flags, EPT_MEMORY_TYPE_WB);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ept_log_walk(u64 gpa) {
|
||||
if (!g_ept_ready) return;
|
||||
u64 *table = (u64 *)g_ept_pml4_virt;
|
||||
u64 pml4_idx = (gpa >> 39) & 0x1FF;
|
||||
u64 pdpt_idx = (gpa >> 30) & 0x1FF;
|
||||
u64 pd_idx = (gpa >> 21) & 0x1FF;
|
||||
u64 pt_idx = (gpa >> 12) & 0x1FF;
|
||||
u64 e;
|
||||
log_hex64("[EPT] walk gpa=", gpa);
|
||||
e = table[pml4_idx];
|
||||
log_hex64("[EPT] pml4e=", e);
|
||||
if (!(e & EPT_READ)) return;
|
||||
table = (u64 *)phys_to_virt(e & 0x000FFFFFFFFFF000ULL);
|
||||
e = table[pdpt_idx];
|
||||
log_hex64("[EPT] pdpte=", e);
|
||||
if (!(e & EPT_READ) || (e & EPT_LARGE_PAGE)) return;
|
||||
table = (u64 *)phys_to_virt(e & 0x000FFFFFFFFFF000ULL);
|
||||
e = table[pd_idx];
|
||||
log_hex64("[EPT] pde=", e);
|
||||
if (!(e & EPT_READ) || (e & EPT_LARGE_PAGE)) return;
|
||||
table = (u64 *)phys_to_virt(e & 0x000FFFFFFFFFF000ULL);
|
||||
e = table[pt_idx];
|
||||
log_hex64("[EPT] pte=", e);
|
||||
}
|
||||
|
||||
u64 ept_get_eptp(void) {
|
||||
if (!g_ept_ready) return 0;
|
||||
/* EPTP: bits [2:0]=memory type WB(6), bits [5:3]=walk length-1 (3 => 4-level).
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
vmm/ept.o: vmm\ept.c include\utsm\ept.h include\utsm\types.h \
|
||||
include\utsm\vmx.h include\utsm\log.h include\utsm\dma.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\ept.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -46,10 +46,16 @@ static void *gpa_to_hva(u64 gpa) {
|
||||
return (void *)(hhdm + hpa);
|
||||
}
|
||||
|
||||
static void *guest_ptr_to_hva(u64 guest_ptr) {
|
||||
u64 gpa = vmx_guest_gva_to_gpa(guest_ptr);
|
||||
if (gpa == 0) gpa = guest_ptr;
|
||||
return gpa_to_hva(gpa);
|
||||
}
|
||||
|
||||
/* Write a u64 to guest memory at the given GPA.
|
||||
* Returns 0 on success, -1 on failure. */
|
||||
static int guest_write_u64(u64 gpa, u64 value) {
|
||||
void *hva = gpa_to_hva(gpa);
|
||||
void *hva = guest_ptr_to_hva(gpa);
|
||||
if (!hva) return -1;
|
||||
*(volatile u64 *)hva = value;
|
||||
return 0;
|
||||
@@ -58,14 +64,14 @@ static int guest_write_u64(u64 gpa, u64 value) {
|
||||
/* Copy bytes from guest memory (GVA=GPA) to host buffer.
|
||||
* Handles cross-page boundaries by translating each page separately.
|
||||
* Returns 0 on success, -1 on failure. */
|
||||
static int guest_read_buf(u64 gpa, void *host_dst, u64 len) {
|
||||
static int guest_read_buf(u64 gva, void *host_dst, u64 len) {
|
||||
u8 *dst = (u8 *)host_dst;
|
||||
u64 copied = 0;
|
||||
while (copied < len) {
|
||||
void *hva = gpa_to_hva(gpa + copied);
|
||||
void *hva = guest_ptr_to_hva(gva + copied);
|
||||
if (!hva) return -1;
|
||||
u64 page_end = (gpa + copied + 4096) & ~0xFFFULL;
|
||||
u64 chunk = page_end - (gpa + copied);
|
||||
u64 page_off = (gva + copied) & 0xFFFULL;
|
||||
u64 chunk = 4096 - page_off;
|
||||
if (chunk > len - copied) chunk = len - copied;
|
||||
const u8 *src = (const u8 *)hva;
|
||||
for (u64 i = 0; i < chunk; i++) {
|
||||
@@ -111,6 +117,8 @@ static long hcall_shm_info(u64 a0, u64 a1, u64 a2) {
|
||||
/* Write results to guest memory */
|
||||
if (a0 != 0 && guest_write_u64(a0, shm_gpa) != 0) {
|
||||
log_error("[HCALL] SHM_INFO: failed to write gpa to guest");
|
||||
log_hex64("[HCALL] SHM_INFO: a0=", a0);
|
||||
log_hex64("[HCALL] SHM_INFO: walk=", vmx_guest_gva_to_gpa(a0));
|
||||
return UTSM_HCALL_INVAL;
|
||||
}
|
||||
if (a1 != 0 && guest_write_u64(a1, shm_size) != 0) {
|
||||
@@ -311,6 +319,13 @@ int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx,
|
||||
case UTSM_HCALL_HELLO:
|
||||
result = hcall_hello(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
case UTSM_HCALL_PARK:
|
||||
/* Daemon park: return to DSK. Idle HLT must not use this path. */
|
||||
g_guest_regs.rax = (u64)UTSM_HCALL_OK;
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, guest_rip + instr_len);
|
||||
g_guest_parked = 1;
|
||||
log_info("[HCALL] PARK - Linux guest parked");
|
||||
return 0;
|
||||
case UTSM_HCALL_SHM_INFO:
|
||||
result = hcall_shm_info(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
vmm/hypercall.o: vmm\hypercall.c include\utsm\hypercall.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h \
|
||||
include\utsm\linux_compat.h include\utsm\linux_loader.h \
|
||||
include\utsm\log.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\hypercall.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -1,13 +0,0 @@
|
||||
vmm/ipc_shm.o: vmm\ipc_shm.c include\utsm\ipc_shm.h include\utsm\types.h \
|
||||
..\utsm-ipc\ipc_proto.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\log.h include\utsm\dma.h include\utsm\linux_loader.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\ipc_shm.h:
|
||||
include\utsm\types.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
include\utsm\linux_loader.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
+207
-12
@@ -6,13 +6,14 @@
|
||||
* - RSP = guest stack top
|
||||
* - CR3 = guest page tables GPA (identity-mapped 4GB)
|
||||
* - GDT = guest GDT GPA (64-bit code/data/TSS)
|
||||
* - EFER.LME | EFER.LMA | EFER.NXE
|
||||
* - EFER.SCE | EFER.LME | EFER.LMA | EFER.NXE
|
||||
*
|
||||
* After vmlaunch, the VM-Exit handler (vmexit.c) processes exits and
|
||||
* resumes the guest.
|
||||
*/
|
||||
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/linux_xsave.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
@@ -54,6 +55,43 @@ static inline u64 read_cr4_local(void) {
|
||||
/* MSR bitmap (4KB, all zeros = don't intercept any MSR) */
|
||||
static u8 g_linux_msr_bitmap[4096] __attribute__((aligned(4096)));
|
||||
|
||||
int g_linux_cet_vmcs;
|
||||
int g_linux_cet_force;
|
||||
static u64 g_host_ucet;
|
||||
|
||||
struct vmx_msr_entry {
|
||||
u32 index;
|
||||
u32 reserved;
|
||||
u64 data;
|
||||
} __attribute__((packed));
|
||||
_Static_assert(sizeof(struct vmx_msr_entry) == 16, "VMX MSR entry is 16 bytes");
|
||||
|
||||
static struct vmx_msr_entry g_linux_msr_entry_load[1] __attribute__((aligned(16)));
|
||||
static u32 g_linux_msr_entry_count;
|
||||
|
||||
void linux_cet_restore_host(void) {
|
||||
if (!g_linux_cet_force) return;
|
||||
vmx_write_msr(0x6A0, g_host_ucet);
|
||||
}
|
||||
|
||||
void linux_cet_force_guest_off(void) {
|
||||
if (!g_linux_cet_force) return;
|
||||
vmx_write_msr(0x6A0, 0);
|
||||
}
|
||||
|
||||
void linux_cet_vmcs_sync_host(void) {
|
||||
u64 scet, ist, ssp = 0;
|
||||
if (!g_linux_cet_vmcs) return;
|
||||
scet = vmx_read_msr(0x6A2);
|
||||
ist = vmx_read_msr(0x6A8);
|
||||
if (scet & 1ULL) {
|
||||
__asm__ volatile(".byte 0xf3, 0x48, 0x0f, 0x1e, 0xc8" : "=a"(ssp));
|
||||
}
|
||||
vmx_vmcs_write(VMCS_HOST_S_CET, scet);
|
||||
vmx_vmcs_write(VMCS_HOST_SSP, ssp);
|
||||
vmx_vmcs_write(VMCS_HOST_INTR_SSP_TABLE, ist);
|
||||
}
|
||||
|
||||
/* ===== VMCS host state (same as vmm.c, but for Linux guest) ===== */
|
||||
|
||||
static void linux_vmcs_setup_host_state(void) {
|
||||
@@ -113,6 +151,7 @@ static void linux_vmcs_setup_host_state(void) {
|
||||
/* Host RSP/RIP: VM-Exit handler */
|
||||
vmx_vmcs_write(VMCS_HOST_RSP, (u64)&g_host_stack[sizeof(g_host_stack) - 16]);
|
||||
vmx_vmcs_write(VMCS_HOST_RIP, (u64)vmx_vm_exit_handler);
|
||||
linux_cet_vmcs_sync_host();
|
||||
}
|
||||
|
||||
/* ===== VMCS guest state for Linux ===== */
|
||||
@@ -121,10 +160,38 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
|
||||
/* 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);
|
||||
/* ET/MP + OSFXSR so the decompressor/kernel can use FXSAVE/SSE
|
||||
* before it programs CR0/CR4 itself. Missing OSFXSR is #UD with
|
||||
* IDTR=0, which becomes a triple fault. */
|
||||
u64 cr0 = CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_PG | CR0_WP;
|
||||
u64 cr4 = CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE |
|
||||
CR4_OSFXSR | CR4_OSXMMEXCPT;
|
||||
if (linux_xsave_available())
|
||||
cr4 |= CR4_OSXSAVE;
|
||||
vmx_vmcs_write(VMCS_GUEST_CR0, cr0);
|
||||
/* CR3 points to guest PML4 (GPA) */
|
||||
vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_gpa);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, cr4);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_S_CET, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_SSP, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_INTR_SSP_TABLE, 0);
|
||||
|
||||
/* startup_64 writes CR4 = PAE|PGE and drops VMXE. In VMX non-root
|
||||
* that is #GP; the decompressor IDT then #DFs. Own FIXED0 bits
|
||||
* (VMXE) so the write updates the shadow only. */
|
||||
{
|
||||
u64 cr0_fixed0 = vmx_read_msr(IA32_VMX_CR0_FIXED0) & 0xFFFFFFFFULL;
|
||||
u64 cr0_fixed1 = vmx_read_msr(IA32_VMX_CR0_FIXED1) & 0xFFFFFFFFULL;
|
||||
u64 cr4_fixed0 = vmx_read_msr(IA32_VMX_CR4_FIXED0) & 0xFFFFFFFFULL;
|
||||
u64 cr0_mask = cr0_fixed0 | (~cr0_fixed1 & 0xFFFFFFFFULL);
|
||||
vmx_vmcs_write(VMCS_CR0_GUEST_HOST_MASK, cr0_mask);
|
||||
vmx_vmcs_write(VMCS_CR0_READ_SHADOW, cr0);
|
||||
vmx_vmcs_write(VMCS_CR4_GUEST_HOST_MASK, cr4_fixed0);
|
||||
vmx_vmcs_write(VMCS_CR4_READ_SHADOW, cr4 & ~cr4_fixed0);
|
||||
log_hex64("[LINUX] CR0 mask=", cr0_mask);
|
||||
log_hex64("[LINUX] CR4 mask=", cr4_fixed0);
|
||||
}
|
||||
|
||||
/* Segment selectors from guest GDT */
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08);
|
||||
@@ -187,7 +254,10 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
|
||||
vmx_vmcs_write(VMCS_GUEST_RFLAGS, 0x2); /* interrupts disabled */
|
||||
|
||||
/* Guest EFER: long mode active */
|
||||
vmx_vmcs_write(VMCS_GUEST_IA32_EFER, EFER_LME | EFER_LMA | EFER_NXE);
|
||||
/* SCE must stay set across VM-exits or userspace SYSCALL #UDs
|
||||
* (busybox musl arch_prctl). Combined with VM_EXIT_SAVE_GUEST_EFER. */
|
||||
vmx_vmcs_write(VMCS_GUEST_IA32_EFER,
|
||||
EFER_SCE | EFER_LME | EFER_LMA | EFER_NXE);
|
||||
|
||||
/* Guest SYSENTER (unused but required by VMX) */
|
||||
vmx_vmcs_write(VMCS_GUEST_SYSENTER_CS, 0);
|
||||
@@ -230,12 +300,15 @@ static void linux_vmcs_setup_controls(u64 eptp) {
|
||||
| CPU_BASED_ACTIVATE_SECONDARY
|
||||
| CPU_BASED_USE_MSR_BITMAPS
|
||||
| CPU_BASED_UNCOND_IO_EXITING
|
||||
| CPU_BASED_INVLPG_EXITING;
|
||||
| CPU_BASED_INVLPG_EXITING
|
||||
| CPU_BASED_CR3_LOAD_EXITING
|
||||
| CPU_BASED_CR3_STORE_EXITING;
|
||||
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
||||
|
||||
/* Secondary: EPT + VPID(经 capability MSR 调整;
|
||||
* EPT 不可用则打日志——Linux guest 的 GPA 布局依赖 EPT)。 */
|
||||
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
|
||||
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID |
|
||||
SEC_EXEC_ENABLE_RDTSCP;
|
||||
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");
|
||||
@@ -243,27 +316,92 @@ static void linux_vmcs_setup_controls(u64 eptp) {
|
||||
|
||||
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
||||
| VM_EXIT_LOAD_HOST_EFER;
|
||||
| VM_EXIT_SAVE_GUEST_EFER
|
||||
| VM_EXIT_LOAD_HOST_EFER
|
||||
| VM_EXIT_SAVE_CET_STATE
|
||||
| VM_EXIT_LOAD_CET_STATE;
|
||||
exit_ctrl = vmx_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS);
|
||||
|
||||
u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS
|
||||
| VM_ENTRY_IA32E_MODE_GUEST
|
||||
| VM_ENTRY_LOAD_GUEST_EFER;
|
||||
| VM_ENTRY_LOAD_GUEST_EFER
|
||||
| VM_ENTRY_LOAD_CET_STATE;
|
||||
entry_ctrl = vmx_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS);
|
||||
|
||||
g_linux_cet_vmcs = 0;
|
||||
if ((exit_ctrl & VM_EXIT_SAVE_CET_STATE) &&
|
||||
(exit_ctrl & VM_EXIT_LOAD_CET_STATE) &&
|
||||
(entry_ctrl & VM_ENTRY_LOAD_CET_STATE)) {
|
||||
g_linux_cet_vmcs = 1;
|
||||
log_info("[LINUX] VMCS CET state switch enabled");
|
||||
} else {
|
||||
exit_ctrl &= ~(VM_EXIT_SAVE_CET_STATE | VM_EXIT_LOAD_CET_STATE);
|
||||
entry_ctrl &= ~VM_ENTRY_LOAD_CET_STATE;
|
||||
log_warn("[LINUX] VMCS CET switch not offered by nested VMX");
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin);
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu);
|
||||
vmx_vmcs_write(VMCS_SECONDARY_VM_EXEC_CONTROL, cpu2);
|
||||
vmx_vmcs_write(VMCS_VM_EXIT_CONTROLS, exit_ctrl);
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_CONTROLS, entry_ctrl);
|
||||
|
||||
/* MSR bitmap */
|
||||
/* MSR bitmap: intercept EFER (SCE must land in VMCS) and FS/GS bases
|
||||
* (arch_prctl). Nested KVM does not apply un-intercepted WRMSR
|
||||
* IA32_FS_BASE to GUEST_FS_BASE; ignoring those writes leaves TLS=0
|
||||
* and every libc/musl process SIGSEGVs at address 0. */
|
||||
{
|
||||
u32 efer_off = 0xC0000080u - 0xC0000000u;
|
||||
u32 fs_off = 0xC0000100u - 0xC0000000u;
|
||||
u32 gs_off = 0xC0000101u - 0xC0000000u;
|
||||
u32 kgs_off = 0xC0000102u - 0xC0000000u;
|
||||
g_linux_msr_bitmap[0x400 + efer_off / 8] |= (u8)(1u << (efer_off & 7));
|
||||
g_linux_msr_bitmap[0xC00 + efer_off / 8] |= (u8)(1u << (efer_off & 7));
|
||||
g_linux_msr_bitmap[0x400 + fs_off / 8] |= (u8)(1u << (fs_off & 7));
|
||||
g_linux_msr_bitmap[0xC00 + fs_off / 8] |= (u8)(1u << (fs_off & 7));
|
||||
g_linux_msr_bitmap[0x400 + gs_off / 8] |= (u8)(1u << (gs_off & 7));
|
||||
g_linux_msr_bitmap[0xC00 + gs_off / 8] |= (u8)(1u << (gs_off & 7));
|
||||
g_linux_msr_bitmap[0xC00 + kgs_off / 8] |= (u8)(1u << (kgs_off & 7));
|
||||
g_linux_msr_bitmap[0x400 + kgs_off / 8] |= (u8)(1u << (kgs_off & 7));
|
||||
/* Do not intercept CET MSRs: L1 CR4.CET=0 so we cannot WRMSR
|
||||
* them, but L2 hardware CET is on. The guest driver clears
|
||||
* IA32_U_CET with a passthrough WRMSR. */
|
||||
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);
|
||||
}
|
||||
|
||||
/* Nested KVM does not offer VMCS CET save/load, and it also hides
|
||||
* CET from L1 CPUID/CR4. L0 U_CET still leaks into L2 (busybox PUSH
|
||||
* to SSP=0). Always try a VM-entry MSR-load of IA32_U_CET=0; if
|
||||
* nested rejects the list, linux_launch retries with count=0. */
|
||||
{
|
||||
u64 host_cr4 = read_cr4_local();
|
||||
log_hex64("[LINUX] host CR4=", host_cr4);
|
||||
g_host_ucet = 0;
|
||||
g_linux_cet_force = 0;
|
||||
if (host_cr4 & CR4_CET) {
|
||||
g_host_ucet = vmx_read_msr(0x6A0);
|
||||
g_linux_cet_force = 1;
|
||||
log_hex64("[LINUX] host U_CET=", g_host_ucet);
|
||||
log_info("[LINUX] CR4.CET set: WRMSR U_CET=0 on each L2 entry");
|
||||
} else {
|
||||
log_warn("[LINUX] CR4.CET=0: cannot WRMSR CET; MSR-load list only");
|
||||
}
|
||||
|
||||
g_linux_msr_entry_load[0].index = 0x6A0;
|
||||
g_linux_msr_entry_load[0].reserved = 0;
|
||||
g_linux_msr_entry_load[0].data = 0;
|
||||
/* Nested KVM reports EXIT_ENTRY_FAIL_MSR_LOADING (34) for CET
|
||||
* MSRs. Do not arm the list; QEMU-process arch_prctl is the
|
||||
* working way to keep leaked U_CET off. */
|
||||
g_linux_msr_entry_count = 0;
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_MSR_LOAD_COUNT, 0);
|
||||
vmx_vmcs_write(VMCS_VM_EXIT_MSR_LOAD_COUNT, 0);
|
||||
vmx_vmcs_write(VMCS_VM_EXIT_MSR_STORE_COUNT, 0);
|
||||
log_info("[LINUX] CET MSR-load list not armed (nested rejects U_CET)");
|
||||
}
|
||||
|
||||
if (cpu2 & SEC_EXEC_ENABLE_EPT) {
|
||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
||||
} else {
|
||||
@@ -275,9 +413,12 @@ static void linux_vmcs_setup_controls(u64 eptp) {
|
||||
vmx_vmcs_write(VMCS_VPID, 0);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
/* Linux owns #PF while it grows the identity map. Intercepting it
|
||||
* plus serial dumps livelocks extract_kernel. Catch #UD/#GP/#DF only. */
|
||||
/* #UD #DF #GP #CP(CET). User #GP currently does not nested-exit;
|
||||
* still catch #CP if L0 delivers it to L1. */
|
||||
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP,
|
||||
(1ULL << 6) | (1ULL << 8) | (1ULL << 13) | (1ULL << 21));
|
||||
}
|
||||
|
||||
/* ===== Global for passing RSI to launch asm ===== */
|
||||
@@ -308,6 +449,7 @@ int linux_launch(void) {
|
||||
}
|
||||
|
||||
log_info("[LINUX] launch begin");
|
||||
linux_xsave_init();
|
||||
|
||||
/* 注册 virtio-mmio 后端(block + net),guest 经 cmdline
|
||||
* virtio_mmio.device= 发现。幂等:重复调用直接返回。 */
|
||||
@@ -365,7 +507,10 @@ int linux_launch(void) {
|
||||
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));
|
||||
linux_cet_vmcs_sync_host();
|
||||
linux_cet_force_guest_off();
|
||||
log_info("[LINUX] vmlaunch");
|
||||
linux_xsave_load_guest();
|
||||
|
||||
/* vmlaunch with RSI = boot_params.
|
||||
* Same control flow pattern as vmm_self_test:
|
||||
@@ -413,6 +558,56 @@ int linux_launch(void) {
|
||||
|
||||
int failed = g_linux_launch_failed;
|
||||
u64 vm_flags = g_linux_launch_flags;
|
||||
if (failed && g_linux_msr_entry_count) {
|
||||
u64 error = 0;
|
||||
if (vm_flags & 0x40)
|
||||
vmx_vmcs_read_checked(VMCS_VMX_INSTRUCTION_ERROR, &error);
|
||||
log_warn("[LINUX] vmlaunch failed with CET MSR-load; retry without");
|
||||
log_hex64("[LINUX] vmlaunch CET-load error=", error);
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_MSR_LOAD_COUNT, 0);
|
||||
g_linux_msr_entry_count = 0;
|
||||
g_linux_launch_failed = 1;
|
||||
g_linux_launch_flags = 0;
|
||||
linux_cet_force_guest_off();
|
||||
__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"
|
||||
"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"
|
||||
"vmlaunch\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"
|
||||
"1:\n\t"
|
||||
"movl $0, g_linux_launch_failed(%%rip)\n\t"
|
||||
"2:\n\t"
|
||||
:
|
||||
:
|
||||
: "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp",
|
||||
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
||||
"cc", "memory"
|
||||
);
|
||||
failed = g_linux_launch_failed;
|
||||
vm_flags = g_linux_launch_flags;
|
||||
}
|
||||
linux_cet_restore_host();
|
||||
linux_xsave_save_guest();
|
||||
linux_xsave_load_host();
|
||||
if (failed) {
|
||||
/* CF=1 → VMfailInvalid;ZF=1 → VMfailValid(error field 有效)。 */
|
||||
if (vm_flags & 0x1) {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
vmm/linux_boot.o: vmm\linux_boot.c include\utsm\linux_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\virtio_mmio.h include\utsm\log.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -73,32 +73,21 @@ static int lxc_is_available(void) {
|
||||
/* 前向声明:payload_pool 拷出助手(定义在文件传输段,exec 新协议复用) */
|
||||
static u64 lxc_pool_copyout(char *buf, u64 cap, u32 data_len);
|
||||
|
||||
/* 等待 Linux daemon 的响应消息。
|
||||
* linux_resume() 返回后,daemon 已 park,所有响应消息应已在 ring 中。
|
||||
* 我们循环 pop 直到拿到 EXEC_EXIT 或 ring 空(超时保护)。
|
||||
*
|
||||
* 新协议(P3):daemon 把 stdout 直写 payload_pool,EXEC_EXIT 为 16B
|
||||
* (exit_code + flags + stdout_len + stdout_total),flags&F_POOL 时
|
||||
* 从 pool 拷出;F_TRUNC/F_TIMEOUT 记串口日志。旧协议(8B exit_code)
|
||||
* 输出仍走 EXEC_STDOUT 内联,下方分支兼容。 */
|
||||
static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
u64 *exit_code) {
|
||||
u64 written = 0;
|
||||
/* Consume already-queued EXEC_* messages. Returns 1 if EXEC_EXIT was
|
||||
* seen, 0 if the ring is empty. stdout is accumulated across slices. */
|
||||
static int lxc_consume_exec_msgs(char *out_buf, u64 out_cap, u64 *written_inout,
|
||||
u64 *exit_code) {
|
||||
u64 written = written_inout ? *written_inout : 0;
|
||||
int got_exit = 0;
|
||||
u64 exit_val = 0;
|
||||
u64 exit_val = exit_code ? *exit_code : 0;
|
||||
|
||||
/* 最多消费 256 条消息(防止异常情况下死循环) */
|
||||
for (int i = 0; i < 256; i++) {
|
||||
struct utsm_ipc_msg msg;
|
||||
if (ipc_shm_recv(&msg) != 0) {
|
||||
/* ring 空 */
|
||||
break;
|
||||
}
|
||||
if (ipc_shm_recv(&msg) != 0) break;
|
||||
|
||||
switch (msg.type) {
|
||||
case UTSM_MSG_EXEC_STDOUT:
|
||||
case UTSM_MSG_EXEC_STDERR: {
|
||||
/* 旧式内联输出(新协议下 stdout 走 pool,正常不会再收到) */
|
||||
u32 n = msg.data_len;
|
||||
if (out_buf && out_cap > written) {
|
||||
u32 space = (u32)(out_cap - written);
|
||||
@@ -114,7 +103,6 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
my_memcpy(&ex, msg.data, sizeof(ex));
|
||||
exit_val = ex.exit_code;
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_POOL) {
|
||||
/* stdout 已 staged 在 payload_pool[0..stdout_len) */
|
||||
written = lxc_pool_copyout(out_buf, out_cap,
|
||||
ex.stdout_len);
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_TRUNC) {
|
||||
@@ -125,7 +113,6 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
log_warn("[LNXC] exec: child timed out, SIGKILLed");
|
||||
}
|
||||
} else if (msg.data_len >= 8) {
|
||||
/* 旧式 8B:exit_code + reserved */
|
||||
u32 ec = 0;
|
||||
my_memcpy(&ec, msg.data, 4);
|
||||
exit_val = ec;
|
||||
@@ -134,17 +121,39 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
break;
|
||||
}
|
||||
default:
|
||||
/* 忽略其他消息(HELLO/PONG 等) */
|
||||
break;
|
||||
}
|
||||
|
||||
if (got_exit) break;
|
||||
}
|
||||
|
||||
if (written_inout) *written_inout = written;
|
||||
if (exit_code) *exit_code = exit_val;
|
||||
return got_exit;
|
||||
}
|
||||
|
||||
#define LXC_MAX_SLICES 256
|
||||
|
||||
/* linux_resume() may return on a preemption timeslice before the daemon
|
||||
* parks. Keep running slices until EXEC_EXIT (or the slice budget). */
|
||||
static int lxc_wait_exec_exit(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
u64 *exit_code) {
|
||||
u64 written = 0;
|
||||
u64 exit_val = 0;
|
||||
for (int slice = 0; slice < LXC_MAX_SLICES; slice++) {
|
||||
int r = linux_resume();
|
||||
if (r != 0) {
|
||||
log_hex64("[LNXC] exec: linux_resume failed=", (u64)(i64)r);
|
||||
return -3;
|
||||
}
|
||||
if (lxc_consume_exec_msgs(out_buf, out_cap, &written, &exit_val)) {
|
||||
if (out_len) *out_len = written;
|
||||
if (exit_code) *exit_code = exit_val;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (out_len) *out_len = written;
|
||||
if (exit_code) *exit_code = exit_val;
|
||||
|
||||
return got_exit ? 0 : -4; /* -4 = 超时无 exit 响应 */
|
||||
return -4;
|
||||
}
|
||||
|
||||
/* 构建并发送 EXEC_REQUEST,唤醒 guest,然后 drain 响应。
|
||||
@@ -190,15 +199,13 @@ static int lxc_exec_internal(const char *path, int argc,
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 3. 唤醒 Linux guest 执行 */
|
||||
int r = linux_resume();
|
||||
if (r != 0) {
|
||||
log_hex64("[LNXC] exec: linux_resume failed=", (u64)(i64)r);
|
||||
return -3;
|
||||
/* 3. 唤醒 Linux guest;timeslice 归还时继续 resume 直到 EXEC_EXIT */
|
||||
int drc = lxc_wait_exec_exit(stdout_buf, stdout_cap, stdout_len, exit_code);
|
||||
if (drc != 0) {
|
||||
log_hex64("[LNXC] exec: no EXEC_EXIT after resume rc=", (u64)(i64)drc);
|
||||
ipc_shm_dump_stats();
|
||||
}
|
||||
|
||||
/* 4. 读取响应(daemon 已再次 park,响应在 ring 中) */
|
||||
return lxc_drain_responses(stdout_buf, stdout_cap, stdout_len, exit_code);
|
||||
return drc;
|
||||
}
|
||||
|
||||
static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
@@ -283,20 +290,20 @@ static int lxc_file_roundtrip(u32 msg_type, const char *path, u64 offset,
|
||||
return -2;
|
||||
}
|
||||
|
||||
int r = linux_resume();
|
||||
if (r != 0) {
|
||||
log_hex64("[LNXC] file: linux_resume failed=", (u64)(i64)r);
|
||||
return -3;
|
||||
}
|
||||
|
||||
/* 从 ring 中取 FILE_RESPONSE(忽略其间可能残留的 EXEC_* 消息) */
|
||||
for (int i = 0; i < 256; i++) {
|
||||
struct utsm_ipc_msg msg;
|
||||
if (ipc_shm_recv(&msg) != 0) break; /* ring 空 */
|
||||
if (msg.type == UTSM_MSG_FILE_RESPONSE &&
|
||||
msg.data_len >= sizeof(struct ipc_file_response)) {
|
||||
my_memcpy(resp, msg.data, sizeof(*resp));
|
||||
return 0;
|
||||
for (int slice = 0; slice < LXC_MAX_SLICES; slice++) {
|
||||
int r = linux_resume();
|
||||
if (r != 0) {
|
||||
log_hex64("[LNXC] file: linux_resume failed=", (u64)(i64)r);
|
||||
return -3;
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
struct utsm_ipc_msg msg;
|
||||
if (ipc_shm_recv(&msg) != 0) break;
|
||||
if (msg.type == UTSM_MSG_FILE_RESPONSE &&
|
||||
msg.data_len >= sizeof(struct ipc_file_response)) {
|
||||
my_memcpy(resp, msg.data, sizeof(*resp));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -4; /* 无响应 */
|
||||
@@ -569,6 +576,11 @@ static int lxc_gpu_get_scanout_info(struct linux_compat_scanout_info *out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxc_run_slice(void) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
return linux_resume();
|
||||
}
|
||||
|
||||
/* ===== 服务表 ===== */
|
||||
|
||||
static const linux_compat_service g_lxc_service = {
|
||||
@@ -585,6 +597,7 @@ static const linux_compat_service g_lxc_service = {
|
||||
lxc_input_forward_mouse,
|
||||
lxc_gpu_get_scanout_info,
|
||||
lxc_exec_async,
|
||||
lxc_run_slice,
|
||||
};
|
||||
|
||||
const linux_compat_service *linux_compat_get_service(void) {
|
||||
@@ -851,6 +864,7 @@ int lxc_vscode_install(void) {
|
||||
log_warn("[LNXC] vscode install: service not available");
|
||||
return -1;
|
||||
}
|
||||
log_info("[LNXC] vscode: probe /opt/vscode/bin/code");
|
||||
|
||||
/* 1. 已安装标记:overlay 持久化后二次启动直接跳过 */
|
||||
if (lxc_guest_path_exists("/opt/vscode/bin/code")) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
vmm/linux_compat.o: vmm\linux_compat.c include\utsm\linux_compat.h \
|
||||
include\utsm\types.h include\utsm\linux_resume.h \
|
||||
include\utsm\linux_loader.h include\utsm\ipc_shm.h \
|
||||
..\utsm-ipc\ipc_proto.h include\utsm\vmm.h include\utsm\virtio_mmio.h \
|
||||
include\utsm\log.h include\utsm\block.h vmm\..\..\tools\fat32_io.h \
|
||||
vmm\..\..\tools\fat32_lfn.h
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_resume.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\block.h:
|
||||
vmm\..\..\tools\fat32_io.h:
|
||||
vmm\..\..\tools\fat32_lfn.h:
|
||||
+112
-44
@@ -24,6 +24,7 @@
|
||||
extern volatile struct limine_module_request g_module_request;
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
extern volatile struct limine_memmap_request g_memmap_request;
|
||||
extern volatile struct limine_rsdp_request g_rsdp_request;
|
||||
|
||||
/* ===== Runtime helpers ===== */
|
||||
|
||||
@@ -186,7 +187,7 @@ struct __attribute__((packed)) e820_entry {
|
||||
u32 type;
|
||||
};
|
||||
|
||||
static int setup_e820(void *bootparams_virt) {
|
||||
static int setup_e820(void *bootparams_virt, u64 ram_size) {
|
||||
u8 *bp = (u8 *)bootparams_virt;
|
||||
|
||||
/* e820_entries count at offset 0x1e8 */
|
||||
@@ -213,7 +214,10 @@ static int setup_e820(void *bootparams_virt) {
|
||||
table[n].type = E820_TYPE_RESERVED;
|
||||
n++;
|
||||
|
||||
/* Entry 3: 0x00400000-0x03FFFFFF: usable RAM (kernel + initrd + low RAM) */
|
||||
/* Entry 3: 0x00400000-0x03FFFFFF: usable RAM (kernel + initrd + low RAM).
|
||||
* Only scattered pages in this window are pre-mapped in EPT. virtio-blk
|
||||
* READ buffers that land in the holes are demand-mapped in
|
||||
* virtio_gpa_to_host (guest CPU never stores those pages first). */
|
||||
table[n].addr = 0x00400000;
|
||||
table[n].size = 0x03C00000; /* 60MB */
|
||||
table[n].type = E820_TYPE_RAM;
|
||||
@@ -233,11 +237,11 @@ static int setup_e820(void *bootparams_virt) {
|
||||
table[n].type = E820_TYPE_RESERVED;
|
||||
n++;
|
||||
|
||||
/* Entry 6: 0x05000000-0x5FFFFFFF: usable RAM (general RAM, ~1.4GB)
|
||||
* VSCode integration Phase 1: expanded from 191MB to 1.4GB to fit
|
||||
* VSCode + Electron + GUI stack (Xorg/modesetting + GTK + mesa). */
|
||||
/* Entry 6: usable RAM at 0x05000000. Size MUST match the EPT mapping
|
||||
* (may be 1.4GB / 1GB / 512MB). Advertising more than is mapped makes
|
||||
* Linux put virtio-blk bounce buffers in a hole → I/O errors. */
|
||||
table[n].addr = LINUX_GUEST_RAM_GPA;
|
||||
table[n].size = LINUX_GUEST_RAM_SIZE; /* 1.4GB */
|
||||
table[n].size = ram_size;
|
||||
table[n].type = E820_TYPE_RAM;
|
||||
n++;
|
||||
|
||||
@@ -353,6 +357,21 @@ void *linux_find_rootfs_module(u64 *size_out) {
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
/* linux-extra-rootfs.img also contains "rootfs"; skip it so vdb
|
||||
* binds to linux-rootfs.img and vdc keeps the extra volume. */
|
||||
int extra = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
if ((s[0] == 'e' || s[0] == 'E') &&
|
||||
(s[1] == 'x' || s[1] == 'X') &&
|
||||
(s[2] == 't' || s[2] == 'T') &&
|
||||
(s[3] == 'r' || s[3] == 'R') &&
|
||||
(s[4] == 'a' || s[4] == 'A')) {
|
||||
extra = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (extra) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[LINUX] found rootfs module:");
|
||||
log_info(file->path);
|
||||
@@ -447,7 +466,7 @@ void *linux_find_vscode_module(u64 *size_out) {
|
||||
/* ===== bzImage parser ===== */
|
||||
|
||||
int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
u64 *payload_offset_out, u64 *payload_length_out,
|
||||
u64 *kernel_offset_out, u64 *kernel_length_out,
|
||||
u64 *init_size_out, u64 *entry_offset_out) {
|
||||
const u8 *img = (const u8 *)bzimage;
|
||||
|
||||
@@ -486,36 +505,49 @@ int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
return -5;
|
||||
}
|
||||
|
||||
/* Payload offset and length */
|
||||
u32 payload_offset = *(u32 *)&img[BZ_PAYLOAD_OFFSET_OFF];
|
||||
u32 payload_length = *(u32 *)&img[BZ_PAYLOAD_LENGTH_OFF];
|
||||
log_hex64("[LINUX] payload_offset=", payload_offset);
|
||||
log_hex64("[LINUX] payload_length=", payload_length);
|
||||
|
||||
if (payload_offset + payload_length > size) {
|
||||
log_error("[LINUX] payload exceeds image size");
|
||||
/* The bzImage's loadable protected-mode image starts after the setup
|
||||
* sectors. setup_header.payload_offset points to the compressed payload
|
||||
* *inside* that image; it is not a file offset and must not be used as
|
||||
* the source passed to startup_64. */
|
||||
u8 setup_sects = img[0x1f1];
|
||||
if (setup_sects == 0) setup_sects = 4;
|
||||
u64 setup_size = ((u64)setup_sects + 1) * 512;
|
||||
if (setup_size >= size) {
|
||||
log_error("[LINUX] setup sectors exceed bzImage");
|
||||
return -6;
|
||||
}
|
||||
|
||||
u32 payload_offset = *(u32 *)&img[BZ_PAYLOAD_OFFSET_OFF];
|
||||
u32 payload_length = *(u32 *)&img[BZ_PAYLOAD_LENGTH_OFF];
|
||||
u64 payload_file_offset = setup_size + payload_offset;
|
||||
if (payload_file_offset > size ||
|
||||
payload_length > size - payload_file_offset) {
|
||||
log_error("[LINUX] compressed payload exceeds bzImage");
|
||||
return -7;
|
||||
}
|
||||
log_hex64("[LINUX] payload_offset(in protected image)=", payload_offset);
|
||||
log_hex64("[LINUX] payload_file_offset=", payload_file_offset);
|
||||
log_hex64("[LINUX] payload_length=", payload_length);
|
||||
|
||||
u64 kernel_length = size - setup_size;
|
||||
log_hex64("[LINUX] protected kernel offset=", setup_size);
|
||||
log_hex64("[LINUX] protected kernel length=", kernel_length);
|
||||
|
||||
/* Init size (total memory needed for kernel init) */
|
||||
u32 init_size = *(u32 *)&img[BZ_INIT_SIZE_ACTUAL_OFF];
|
||||
if (init_size == 0) init_size = payload_length;
|
||||
if (init_size == 0) init_size = kernel_length;
|
||||
log_hex64("[LINUX] init_size=", init_size);
|
||||
|
||||
/* The 64-bit entry point is at offset 0x200 from the payload start.
|
||||
* This is the "startup_64" symbol. */
|
||||
u64 entry_offset = payload_offset + 0x200;
|
||||
/* Linux x86 boot protocol: when the protected-mode image is loaded at
|
||||
* code32_start/pref_address, its 64-bit entry startup_64 is at +0x200. */
|
||||
u64 entry_offset = 0x200;
|
||||
log_hex64("[LINUX] entry_offset=", entry_offset);
|
||||
|
||||
/* Setup sectors (for real-mode header size) */
|
||||
u8 setup_sects = img[0x1f1];
|
||||
if (setup_sects == 0) setup_sects = 4;
|
||||
u64 setup_size = (setup_sects + 1) * 512;
|
||||
log_hex64("[LINUX] setup_sects=", setup_sects);
|
||||
log_hex64("[LINUX] setup_size=", setup_size);
|
||||
|
||||
if (payload_offset_out) *payload_offset_out = payload_offset;
|
||||
if (payload_length_out) *payload_length_out = payload_length;
|
||||
if (kernel_offset_out) *kernel_offset_out = setup_size;
|
||||
if (kernel_length_out) *kernel_length_out = kernel_length;
|
||||
if (init_size_out) *init_size_out = init_size;
|
||||
if (entry_offset_out) *entry_offset_out = entry_offset;
|
||||
|
||||
@@ -525,11 +557,10 @@ int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
/* ===== Boot params setup ===== */
|
||||
|
||||
static int setup_boot_params(void *bp_virt, u64 bp_gpa,
|
||||
u64 kernel_entry_gpa,
|
||||
u64 kernel_load_gpa,
|
||||
u64 cmdline_gpa, u64 cmdline_len,
|
||||
u64 initrd_gpa, u64 initrd_size,
|
||||
const void *bzimage) {
|
||||
(void)bp_gpa; /* GPA is used by EPT mapping, not needed here */
|
||||
const void *bzimage, u64 ram_size) {
|
||||
const u8 *img = (const u8 *)bzimage;
|
||||
u8 *bp = (u8 *)bp_virt;
|
||||
|
||||
@@ -539,10 +570,8 @@ static int setup_boot_params(void *bp_virt, u64 bp_gpa,
|
||||
/* Copy setup_header from bzImage (offset 0x1f1, length 0x1ef = 0x200 - 0x11) */
|
||||
/* The setup_header in boot_params starts at 0x1f1 and goes to 0x2xx.
|
||||
* We copy from bzImage offset 0x1f1 to boot_params offset 0x1f1. */
|
||||
u64 hdr_copy_len = 0x210; /* copy a generous chunk */
|
||||
if (hdr_copy_len > BOOT_PARAMS_SIZE - SETUP_HEADER_OFFSET) {
|
||||
hdr_copy_len = BOOT_PARAMS_SIZE - SETUP_HEADER_OFFSET;
|
||||
}
|
||||
/* Stop before edd_mbr_sig_buffer (0x290) / e820_table (0x2d0). */
|
||||
u64 hdr_copy_len = 0x290 - SETUP_HEADER_OFFSET;
|
||||
mem_copy(&bp[SETUP_HEADER_OFFSET], &img[SETUP_HEADER_OFFSET], hdr_copy_len);
|
||||
|
||||
/* Override key fields */
|
||||
@@ -563,15 +592,32 @@ static int setup_boot_params(void *bp_virt, u64 bp_gpa,
|
||||
*(u32 *)&bp[BZ_RAMDISK_SIZE_OFF] = (u32)initrd_size;
|
||||
}
|
||||
|
||||
/* code32_start: set to kernel entry (informational) */
|
||||
*(u32 *)&bp[BZ_CODE32_START_OFF] = (u32)kernel_entry_gpa;
|
||||
/* code32_start is the base of the loaded protected-mode image.
|
||||
* The 64-bit entry used by UTSM is code32_start + 0x200. */
|
||||
*(u32 *)&bp[BZ_CODE32_START_OFF] = (u32)kernel_load_gpa;
|
||||
|
||||
/* The decompressor's ACPI fallback reads the legacy BDA at 0x40e, but
|
||||
* modern Linux intentionally leaves the zero page unmapped. Limine has
|
||||
* already found a validated RSDP, so copy it into this guest-owned page
|
||||
* and publish its GPA through boot_params.acpi_rsdp_addr (offset 0x70). */
|
||||
if (g_rsdp_request.response && g_rsdp_request.response->address) {
|
||||
mem_copy(&bp[BOOT_PARAMS_RSDP_COPY_OFF],
|
||||
g_rsdp_request.response->address,
|
||||
BOOT_PARAMS_RSDP_COPY_SIZE);
|
||||
*(u64 *)&bp[BOOT_PARAMS_ACPI_RSDP_ADDR_OFF] =
|
||||
bp_gpa + BOOT_PARAMS_RSDP_COPY_OFF;
|
||||
log_hex64("[LINUX] guest RSDP GPA=",
|
||||
bp_gpa + BOOT_PARAMS_RSDP_COPY_OFF);
|
||||
} else {
|
||||
log_warn("[LINUX] Limine RSDP unavailable");
|
||||
}
|
||||
|
||||
/* Heap end pointer (offset 0x224): end of real-mode heap.
|
||||
* Not critical for 64-bit boot, but set a reasonable value. */
|
||||
*(u16 *)&bp[0x224] = 0xFE00; /* heap ends at 0xFE00 */
|
||||
|
||||
/* Set up e820 memory map */
|
||||
int e820_count = setup_e820(bp_virt);
|
||||
int e820_count = setup_e820(bp_virt, ram_size);
|
||||
log_hex64("[LINUX] e820 entries=", (u64)e820_count);
|
||||
|
||||
return 0;
|
||||
@@ -591,9 +637,9 @@ int linux_loader_init(void) {
|
||||
}
|
||||
|
||||
/* 2. Parse bzImage header */
|
||||
u64 payload_offset, payload_length, init_size, entry_offset;
|
||||
u64 kernel_offset, kernel_length, init_size, entry_offset;
|
||||
if (linux_parse_bzimage(bzimage, bzimage_size,
|
||||
&payload_offset, &payload_length,
|
||||
&kernel_offset, &kernel_length,
|
||||
&init_size, &entry_offset) != 0) {
|
||||
log_error("[LINUX] bzImage parse failed");
|
||||
return -2;
|
||||
@@ -603,7 +649,7 @@ int linux_loader_init(void) {
|
||||
|
||||
/* Kernel: round up init_size to page boundary, align to 2MB */
|
||||
u64 kernel_alloc_size = ((init_size + 0xFFFFF) & ~0xFFFFFULL);
|
||||
if (kernel_alloc_size < payload_length) kernel_alloc_size = payload_length;
|
||||
if (kernel_alloc_size < kernel_length) kernel_alloc_size = kernel_length;
|
||||
dkm_dma_buffer kernel_buf;
|
||||
if (dma_alloc_pages(kernel_alloc_size / 4096, 2 * 1024 * 1024, 0, &kernel_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc kernel memory");
|
||||
@@ -670,9 +716,9 @@ int linux_loader_init(void) {
|
||||
log_hex64("[LINUX] guest RAM size=", ram_size);
|
||||
|
||||
/* 4. Load kernel image into allocated memory */
|
||||
const u8 *kernel_src = (const u8 *)bzimage + payload_offset;
|
||||
mem_copy(kernel_buf.virt, kernel_src, payload_length);
|
||||
log_hex64("[LINUX] kernel loaded, size=", payload_length);
|
||||
const u8 *kernel_src = (const u8 *)bzimage + kernel_offset;
|
||||
mem_copy(kernel_buf.virt, kernel_src, kernel_length);
|
||||
log_hex64("[LINUX] protected kernel loaded, size=", kernel_length);
|
||||
|
||||
/* 5. Set up guest page tables */
|
||||
g_guest.pgt_gpa = LINUX_GUEST_PGT_GPA;
|
||||
@@ -732,10 +778,10 @@ int linux_loader_init(void) {
|
||||
}
|
||||
|
||||
setup_boot_params(bp_buf.virt, g_guest.bootparams_gpa,
|
||||
g_guest.kernel_entry,
|
||||
g_guest.kernel_gpa,
|
||||
g_guest.cmdline_gpa, cmdline_len,
|
||||
g_guest.initrd_gpa, g_guest.initrd_size,
|
||||
bzimage);
|
||||
bzimage, ram_size);
|
||||
log_hex64("[LINUX] boot_params at GPA=", g_guest.bootparams_gpa);
|
||||
|
||||
/* 9. EPT-map all guest regions (GPA → HPA) */
|
||||
@@ -833,13 +879,35 @@ int linux_loader_init(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* e820 lists 0x00400000-0x03FFFFFF as RAM, but only kernel/initrd/boot
|
||||
* pages were mapped. Fill the holes so virtio-blk READ buffers (often
|
||||
* allocated here and never stored by the vCPU first) have a stable GPA→HPA. */
|
||||
{
|
||||
u64 gpa;
|
||||
u64 filled = 0;
|
||||
for (gpa = 0x00400000ULL; gpa < 0x04000000ULL; gpa += EPT_PAGE_SIZE) {
|
||||
if (ept_gpa_to_hpa(gpa) != 0) continue;
|
||||
dkm_dma_buffer hole;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0, &hole) != 0) {
|
||||
log_warn("[LINUX] low-RAM hole fill OOM");
|
||||
break;
|
||||
}
|
||||
if (ept_map_range(gpa, hole.phys, EPT_PAGE_SIZE, EPT_RWX) != 0) {
|
||||
log_warn("[LINUX] low-RAM hole fill map failed");
|
||||
break;
|
||||
}
|
||||
filled++;
|
||||
}
|
||||
log_hex64("[LINUX] low-RAM holes filled pages=", filled);
|
||||
}
|
||||
|
||||
/* Identity-map the first 1MB (BIOS area, needed by some Linux code paths) */
|
||||
if (ept_identity_map(0, 0x100000, EPT_RWX) != 0) {
|
||||
log_warn("[LINUX] EPT identity map first 1MB failed (non-critical)");
|
||||
}
|
||||
|
||||
/* 10. Record guest info */
|
||||
g_guest.kernel_size = payload_length;
|
||||
g_guest.kernel_size = kernel_length;
|
||||
g_guest.loaded = 1;
|
||||
|
||||
log_hex64("[LINUX] kernel GPA=", g_guest.kernel_gpa);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
vmm/linux_loader.o: vmm\linux_loader.c include\utsm\linux_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\log.h include\utsm\dma.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -12,11 +12,16 @@
|
||||
*/
|
||||
|
||||
#include <utsm/linux_resume.h>
|
||||
#include <utsm/linux_xsave.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* Must be RIP-relative, not a stack slot: vmexit_asm restores guest GPRs
|
||||
* (including RBP) before jumping here, so "=m"(local) can #PF at CR2≈0. */
|
||||
int g_linux_resume_failed;
|
||||
|
||||
int linux_is_parked(void) {
|
||||
return g_guest_parked ? 1 : 0;
|
||||
}
|
||||
@@ -31,34 +36,50 @@ int linux_resume(void) {
|
||||
g_linux_guest_active = 1;
|
||||
g_guest_terminated = 0;
|
||||
g_guest_parked = 0;
|
||||
vmx_linux_timeslice_reset();
|
||||
linux_xsave_save_host();
|
||||
linux_xsave_load_guest();
|
||||
vmx_linux_prepare_entry();
|
||||
|
||||
/* vmresume 控制流(与 linux_launch 的 vmlaunch 对称):
|
||||
* - 失败:执行下一条指令,failed=1
|
||||
* - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。
|
||||
* - guest park 时,handler 恢复 saved_rsp 并 jmp 到 post_resume 标签。 */
|
||||
int failed;
|
||||
/* vmresume 控制流(与 linux_launch 的 vmlaunch 对称)。
|
||||
* clobber 全 GPR:从 park 跳回时 CPU 上是 guest 寄存器。 */
|
||||
g_linux_resume_failed = 1;
|
||||
__asm__ volatile(
|
||||
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */
|
||||
"leaq 1f(%%rip), %%rax\n\t" /* 取 post_resume 标签地址 */
|
||||
"movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */
|
||||
"vmresume\n\t" /* 唤醒 guest */
|
||||
/* ---- 失败路径 ---- */
|
||||
"movl $1, %0\n\t"
|
||||
"pushq %%rbx\n\t"
|
||||
"pushq %%rbp\n\t"
|
||||
"pushq %%r12\n\t"
|
||||
"pushq %%r13\n\t"
|
||||
"pushq %%r14\n\t"
|
||||
"pushq %%r15\n\t"
|
||||
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t"
|
||||
"leaq 1f(%%rip), %%rax\n\t"
|
||||
"movq %%rax, g_saved_return_rip(%%rip)\n\t"
|
||||
"vmresume\n\t"
|
||||
"movl $1, g_linux_resume_failed(%%rip)\n\t"
|
||||
"jmp 2f\n\t"
|
||||
/* ---- post_resume: park 路径 jmp 到这里 ---- */
|
||||
"1:\n\t"
|
||||
"movl $0, %0\n\t"
|
||||
"movl $0, g_linux_resume_failed(%%rip)\n\t"
|
||||
"2:\n\t"
|
||||
: "=r"(failed)
|
||||
:: "rax", "rcx", "rdx", "rsi", "rdi",
|
||||
"r8", "r9", "r10", "r11", "memory"
|
||||
"popq %%r15\n\t"
|
||||
"popq %%r14\n\t"
|
||||
"popq %%r13\n\t"
|
||||
"popq %%r12\n\t"
|
||||
"popq %%rbp\n\t"
|
||||
"popq %%rbx\n\t"
|
||||
:
|
||||
:
|
||||
: "rax", "rcx", "rdx", "rsi", "rdi",
|
||||
"r8", "r9", "r10", "r11", "cc", "memory"
|
||||
);
|
||||
|
||||
/* 回到 host 上下文 */
|
||||
g_linux_guest_active = 0;
|
||||
g_guest_parked = 1; /* guest 再次 park */
|
||||
g_guest_parked = 1;
|
||||
vmx_linux_timeslice_disarm();
|
||||
linux_xsave_save_guest();
|
||||
linux_xsave_load_host();
|
||||
|
||||
if (failed) {
|
||||
if (g_linux_resume_failed) {
|
||||
u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR);
|
||||
log_hex64("[LINUX] vmresume failed, error=", error);
|
||||
return -2;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
vmm/linux_resume.o: vmm\linux_resume.c include\utsm\linux_resume.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\vmm.h \
|
||||
include\utsm\log.h
|
||||
include\utsm\linux_resume.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\log.h:
|
||||
@@ -0,0 +1,205 @@
|
||||
/* linux_xsave.c — L2 guest XSAVE save/restore.
|
||||
*
|
||||
* Host UTSM is compiled -mno-sse. A naive XRSTOR of guest AVX/SSE
|
||||
* state #GPs. Advertise XSAVE to the Linux guest only after we can
|
||||
* XSAVE the guest area before returning to DSK, and XRSTOR it again
|
||||
* on the next vmresume.
|
||||
*
|
||||
* RFBM/XCR0 = 7 (x87+SSE+AVX/YMM). persist glibc is x86-64-v3. */
|
||||
|
||||
#include <utsm/linux_xsave.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
#define LINUX_XSAVE_XCR0 0x7ULL
|
||||
#define LINUX_XSAVE_BYTES 832u
|
||||
#define LINUX_XSAVE_YMM_SIZE 256u
|
||||
#define LINUX_XSAVE_YMM_OFF 576u
|
||||
|
||||
static int g_xsave_ok;
|
||||
static u64 g_guest_xcr0 = LINUX_XSAVE_XCR0;
|
||||
static u64 g_host_xcr0 = LINUX_XSAVE_XCR0;
|
||||
static u8 g_guest_xsave[4096] __attribute__((aligned(64)));
|
||||
static u8 g_host_xsave[4096] __attribute__((aligned(64)));
|
||||
|
||||
static void xsave_zero(u8 *p, usize n) {
|
||||
for (usize i = 0; i < n; i++)
|
||||
p[i] = 0;
|
||||
}
|
||||
|
||||
static void xsave_enable_osxsave(void) {
|
||||
u64 cr4;
|
||||
__asm__ volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||
if (!(cr4 & CR4_OSXSAVE)) {
|
||||
cr4 |= CR4_OSXSAVE;
|
||||
__asm__ volatile("mov %0, %%cr4" : : "r"(cr4) : "memory");
|
||||
}
|
||||
/* VM-exit reloads HOST_CR4. Keep OSXSAVE so later XSAVE is legal. */
|
||||
vmx_vmcs_write(VMCS_HOST_CR4, cr4 | CR4_VMXE);
|
||||
}
|
||||
|
||||
static void xsave_xsetbv(u64 xcr0) {
|
||||
u32 lo = (u32)xcr0;
|
||||
u32 hi = (u32)(xcr0 >> 32);
|
||||
__asm__ volatile(".byte 0x0f, 0x01, 0xd1"
|
||||
:
|
||||
: "a"(lo), "c"(0), "d"(hi)
|
||||
: "memory");
|
||||
}
|
||||
|
||||
static void xsave_mem(void *ptr, u64 rfbm) {
|
||||
u32 lo = (u32)rfbm;
|
||||
u32 hi = (u32)(rfbm >> 32);
|
||||
__asm__ volatile(".byte 0x0f, 0xae, 0x27" /* xsave (%rdi) */
|
||||
:
|
||||
: "D"(ptr), "a"(lo), "d"(hi)
|
||||
: "memory");
|
||||
}
|
||||
|
||||
static void xrstor_mem(void *ptr, u64 rfbm) {
|
||||
u32 lo = (u32)rfbm;
|
||||
u32 hi = (u32)(rfbm >> 32);
|
||||
__asm__ volatile(".byte 0x0f, 0xae, 0x2f" /* xrstor (%rdi) */
|
||||
:
|
||||
: "D"(ptr), "a"(lo), "d"(hi)
|
||||
: "memory");
|
||||
}
|
||||
|
||||
int linux_xsave_available(void) {
|
||||
return g_xsave_ok;
|
||||
}
|
||||
|
||||
void linux_xsave_init(void) {
|
||||
u32 a, b, c, d;
|
||||
|
||||
if (g_xsave_ok)
|
||||
return;
|
||||
|
||||
__asm__ volatile("cpuid"
|
||||
: "=a"(a), "=b"(b), "=c"(c), "=d"(d)
|
||||
: "a"(1), "c"(0));
|
||||
if (!(c & (1u << 26))) {
|
||||
log_warn("[LINUX] XSAVE not in host CPUID; persist glibc stays hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
xsave_zero(g_guest_xsave, sizeof(g_guest_xsave));
|
||||
xsave_zero(g_host_xsave, sizeof(g_host_xsave));
|
||||
g_guest_xcr0 = LINUX_XSAVE_XCR0;
|
||||
g_host_xcr0 = LINUX_XSAVE_XCR0;
|
||||
|
||||
xsave_enable_osxsave();
|
||||
xsave_xsetbv(LINUX_XSAVE_XCR0);
|
||||
/* Init-state XRSTOR so the first vmlaunch does not inherit host junk. */
|
||||
xrstor_mem(g_guest_xsave, LINUX_XSAVE_XCR0);
|
||||
|
||||
g_xsave_ok = 1;
|
||||
log_info("[LINUX] XSAVE ready for L2 (x87+SSE+AVX, XCR0=7)");
|
||||
log_hex64("[LINUX] XSAVE area bytes=", LINUX_XSAVE_BYTES);
|
||||
}
|
||||
|
||||
void linux_xsave_save_guest(void) {
|
||||
if (!g_xsave_ok)
|
||||
return;
|
||||
xsave_enable_osxsave();
|
||||
xsave_xsetbv(g_guest_xcr0);
|
||||
xsave_mem(g_guest_xsave, g_guest_xcr0);
|
||||
}
|
||||
|
||||
void linux_xsave_load_guest(void) {
|
||||
if (!g_xsave_ok)
|
||||
return;
|
||||
xsave_enable_osxsave();
|
||||
xsave_xsetbv(g_guest_xcr0);
|
||||
xrstor_mem(g_guest_xsave, g_guest_xcr0);
|
||||
}
|
||||
|
||||
void linux_xsave_save_host(void) {
|
||||
if (!g_xsave_ok)
|
||||
return;
|
||||
xsave_enable_osxsave();
|
||||
xsave_xsetbv(g_host_xcr0);
|
||||
xsave_mem(g_host_xsave, g_host_xcr0);
|
||||
}
|
||||
|
||||
void linux_xsave_load_host(void) {
|
||||
if (!g_xsave_ok)
|
||||
return;
|
||||
xsave_enable_osxsave();
|
||||
xsave_xsetbv(g_host_xcr0);
|
||||
xrstor_mem(g_host_xsave, g_host_xcr0);
|
||||
}
|
||||
|
||||
void linux_xsave_set_guest_xcr0(u64 xcr0) {
|
||||
u64 allowed = LINUX_XSAVE_XCR0;
|
||||
u64 v = xcr0 & allowed;
|
||||
static int logged;
|
||||
|
||||
if (!g_xsave_ok)
|
||||
return;
|
||||
/* Intel requires x87. Keep SSE if this build advertises it. */
|
||||
if (!(v & 1ULL))
|
||||
v |= 1ULL;
|
||||
if ((allowed & 2ULL) && !(v & 2ULL))
|
||||
v |= 2ULL;
|
||||
if (!logged && v != xcr0) {
|
||||
logged = 1;
|
||||
log_hex64("[LINUX] XSETBV clamped from=", xcr0);
|
||||
log_hex64("[LINUX] XSETBV clamped to=", v);
|
||||
}
|
||||
g_guest_xcr0 = v;
|
||||
xsave_enable_osxsave();
|
||||
xsave_xsetbv(v);
|
||||
}
|
||||
|
||||
void linux_xsave_adjust_cpuid(u32 leaf, u32 subleaf,
|
||||
u32 *eax, u32 *ebx, u32 *ecx, u32 *edx,
|
||||
u64 guest_cr4) {
|
||||
if (!eax || !ebx || !ecx || !edx)
|
||||
return;
|
||||
|
||||
if (!g_xsave_ok) {
|
||||
if (leaf == 1) {
|
||||
*ecx &= ~((1u << 26) | (1u << 27) | (1u << 28));
|
||||
}
|
||||
if (leaf == 0xD) {
|
||||
*eax = 0;
|
||||
*ebx = 0;
|
||||
*ecx = 0;
|
||||
*edx = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (leaf == 1) {
|
||||
*ecx |= (1u << 26); /* XSAVE */
|
||||
if (guest_cr4 & CR4_OSXSAVE)
|
||||
*ecx |= (1u << 27);
|
||||
else
|
||||
*ecx &= ~(1u << 27);
|
||||
*ecx |= (1u << 28); /* AVX / XCR0 bit 2 */
|
||||
return;
|
||||
}
|
||||
|
||||
if (leaf != 0xD)
|
||||
return;
|
||||
|
||||
/* Hide XSAVEOPT/XSAVEC/XSAVES. Standard XSAVE only. */
|
||||
if (subleaf == 0) {
|
||||
*eax = (u32)LINUX_XSAVE_XCR0;
|
||||
*ebx = LINUX_XSAVE_BYTES;
|
||||
*ecx = LINUX_XSAVE_BYTES;
|
||||
*edx = 0;
|
||||
} else if (subleaf == 2) {
|
||||
*eax = LINUX_XSAVE_YMM_SIZE;
|
||||
*ebx = LINUX_XSAVE_YMM_OFF;
|
||||
*ecx = 0;
|
||||
*edx = 0;
|
||||
} else {
|
||||
*eax = 0;
|
||||
*ebx = 0;
|
||||
*ecx = 0;
|
||||
*edx = 0;
|
||||
}
|
||||
}
|
||||
+544
-246
@@ -19,6 +19,8 @@
|
||||
#include <utsm/block.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include "../../tools/fat32_lfn.h"
|
||||
#include "../../tools/fat32_part.h"
|
||||
|
||||
/* virtio-blk 请求头(Virtio Spec 1.1 §5.2.6.2) */
|
||||
struct virtio_blk_outhdr {
|
||||
@@ -42,20 +44,157 @@ struct virtio_blk_outhdr {
|
||||
#define VIRTIO_BLK_F_BLK_SIZE 6
|
||||
#define VIRTIO_BLK_F_FLUSH 9
|
||||
|
||||
/* device config 空间(VIRTIO_MMIO_CONFIG 起) */
|
||||
/* device config 空间须与 Linux uapi virtio_blk.h 布局一致:
|
||||
* capacity(8) + size_max(4) + seg_max(4) + geometry(4) + blk_size(4).
|
||||
* 旧 u16 geometry[4] 把 blk_size 推到 offset 24,guest 读到 0 → -EINVAL. */
|
||||
struct virtio_blk_config {
|
||||
u64 capacity; /* 总扇区数(512B) */
|
||||
u32 size_max;
|
||||
u32 seg_max;
|
||||
u16 geometry[4];
|
||||
u16 geometry_cylinders;
|
||||
u8 geometry_heads;
|
||||
u8 geometry_sectors;
|
||||
u32 blk_size;
|
||||
/* ... 其余字段省略 */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* Virtio spec: size_max is the max single *segment* in *bytes*, not sectors.
|
||||
* 128 made Linux split 512B reads into 128B fragments (or reject them). */
|
||||
#define VIRTIO_BLK_SIZE_MAX_BYTES 65535u
|
||||
#define VBLK_MAX_DATA_SEGS 32
|
||||
|
||||
/* 后端状态 */
|
||||
static u64 g_blk_capacity; /* 扇区数 */
|
||||
static u32 g_blk_sector_size = 512;
|
||||
static int g_blk_bound = -1; /* 绑定的 UTSM block 设备索引 */
|
||||
static u32 g_vblk_ioerr_logs;
|
||||
|
||||
static void vblk_memcpy(void *dst, const void *src, u64 n) {
|
||||
u8 *d = (u8 *)dst;
|
||||
const u8 *s = (const u8 *)src;
|
||||
while (n--) *d++ = *s++;
|
||||
}
|
||||
|
||||
static void vblk_memzero(u8 *p, u32 n) {
|
||||
while (n--) *p++ = 0;
|
||||
}
|
||||
|
||||
struct vblk_req {
|
||||
struct virtio_blk_outhdr hdr;
|
||||
u8 *status;
|
||||
u8 *data[VBLK_MAX_DATA_SEGS];
|
||||
u32 dlen[VBLK_MAX_DATA_SEGS];
|
||||
int ndata;
|
||||
};
|
||||
|
||||
/* Walk a split-virtqueue blk chain, including INDIRECT tables and
|
||||
* multi-segment data. Layout: [hdr R] [data...] [status W, 1 byte]. */
|
||||
static int vblk_parse_req(struct virtq_desc *vq, u32 qnum, u16 head,
|
||||
struct vblk_req *out) {
|
||||
u8 *raw = (u8 *)out;
|
||||
u32 z;
|
||||
for (z = 0; z < sizeof(*out); z++) raw[z] = 0;
|
||||
if (!vq || head >= qnum) return -1;
|
||||
|
||||
struct virtq_desc *desc = vq;
|
||||
u32 ntbl = qnum;
|
||||
u16 cur = head;
|
||||
|
||||
if (vq[head].flags & VIRTQ_DESC_F_INDIRECT) {
|
||||
u32 n = vq[head].len / (u32)sizeof(struct virtq_desc);
|
||||
if (n == 0 || n > 256) return -1;
|
||||
desc = (struct virtq_desc *)virtio_gpa_to_host(vq[head].addr);
|
||||
if (!desc) return -1;
|
||||
ntbl = n;
|
||||
cur = 0;
|
||||
}
|
||||
|
||||
u16 chain[VBLK_MAX_DATA_SEGS + 4];
|
||||
u16 nchain = 0;
|
||||
int hops = 0;
|
||||
for (;;) {
|
||||
if (cur >= ntbl) return -1;
|
||||
if (nchain >= (u16)(sizeof(chain) / sizeof(chain[0]))) return -1;
|
||||
chain[nchain++] = cur;
|
||||
if (!(desc[cur].flags & VIRTQ_DESC_F_NEXT)) break;
|
||||
cur = desc[cur].next;
|
||||
if (++hops > 64) return -1;
|
||||
}
|
||||
|
||||
int seen_hdr = 0;
|
||||
for (u16 i = 0; i < nchain; i++) {
|
||||
struct virtq_desc *d = &desc[chain[i]];
|
||||
void *host = virtio_gpa_to_host(d->addr);
|
||||
int wr = (d->flags & VIRTQ_DESC_F_WRITE) != 0;
|
||||
int last = (i + 1 == nchain);
|
||||
|
||||
if (!wr && !seen_hdr) {
|
||||
if (!host || d->len < sizeof(struct virtio_blk_outhdr)) return -1;
|
||||
vblk_memcpy(&out->hdr, host, sizeof(out->hdr));
|
||||
out->hdr.type &= 0xFFu; /* drop legacy T_BARRIER */
|
||||
seen_hdr = 1;
|
||||
continue;
|
||||
}
|
||||
if (last && wr && d->len <= 16) {
|
||||
out->status = (u8 *)host;
|
||||
continue;
|
||||
}
|
||||
if (!host) return -1;
|
||||
if (out->ndata >= VBLK_MAX_DATA_SEGS) return -1;
|
||||
out->data[out->ndata] = (u8 *)host;
|
||||
out->dlen[out->ndata] = d->len;
|
||||
out->ndata++;
|
||||
}
|
||||
return seen_hdr ? 0 : -1;
|
||||
}
|
||||
|
||||
static void vblk_log_ioerr(const char *tag, const struct vblk_req *r, u64 why) {
|
||||
if (g_vblk_ioerr_logs >= 8) return;
|
||||
g_vblk_ioerr_logs++;
|
||||
log_info(tag);
|
||||
log_hex64("[VBLK] ioerr why=", why);
|
||||
if (!r) return;
|
||||
log_hex64("[VBLK] ioerr type=", r->hdr.type);
|
||||
log_hex64("[VBLK] ioerr sector=", r->hdr.sector);
|
||||
log_hex64("[VBLK] ioerr ndata=", (u64)(u32)r->ndata);
|
||||
}
|
||||
|
||||
static u32 vblk_copy_from_image(const struct vblk_req *r, const u8 *img, u64 img_size) {
|
||||
u64 offset = r->hdr.sector * 512ULL;
|
||||
u64 pos = 0;
|
||||
for (int i = 0; i < r->ndata; i++) {
|
||||
u32 n = r->dlen[i];
|
||||
u64 src = offset + pos;
|
||||
u64 avail = (src < img_size) ? (img_size - src) : 0;
|
||||
u32 copy = (avail >= (u64)n) ? n : (u32)avail;
|
||||
if (copy) vblk_memcpy(r->data[i], img + src, copy);
|
||||
if (copy < n) vblk_memzero(r->data[i] + copy, n - copy);
|
||||
pos += n;
|
||||
}
|
||||
return (u32)pos;
|
||||
}
|
||||
|
||||
static u32 vblk_copy_to_image(const struct vblk_req *r, u8 *img, u64 img_size) {
|
||||
u64 offset = r->hdr.sector * 512ULL;
|
||||
u64 pos = 0;
|
||||
for (int i = 0; i < r->ndata; i++) {
|
||||
u32 n = r->dlen[i];
|
||||
u64 dst = offset + pos;
|
||||
u64 avail = (dst < img_size) ? (img_size - dst) : 0;
|
||||
u32 copy = (avail >= (u64)n) ? n : (u32)avail;
|
||||
if (copy) vblk_memcpy(img + dst, r->data[i], copy);
|
||||
pos += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vblk_write_id(const struct vblk_req *r, const char *id) {
|
||||
if (r->ndata == 0 || !r->data[0]) return;
|
||||
u32 cap = r->dlen[0];
|
||||
u32 i = 0;
|
||||
if (cap == 0) return;
|
||||
for (; id[i] && i < cap - 1; i++) r->data[0][i] = id[i];
|
||||
r->data[0][i] = 0;
|
||||
}
|
||||
|
||||
/* 读取 device config 空间 */
|
||||
static u32 blk_read_config(u32 offset, int width) {
|
||||
@@ -65,7 +204,7 @@ static u32 blk_read_config(u32 offset, int width) {
|
||||
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.size_max = VIRTIO_BLK_SIZE_MAX_BYTES;
|
||||
cfg.seg_max = 32;
|
||||
cfg.blk_size = g_blk_sector_size;
|
||||
|
||||
@@ -81,101 +220,68 @@ static u32 blk_read_config(u32 offset, int width) {
|
||||
|
||||
/* 处理一个块请求描述符链。返回写入 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);
|
||||
struct vblk_req req;
|
||||
if (vblk_parse_req(desc, qnum, head, &req) != 0) {
|
||||
vblk_log_ioerr("[VBLK] sata parse fail", &req, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (req.hdr.type) {
|
||||
case VIRTIO_BLK_T_IN: {
|
||||
if (req.ndata == 0 || g_blk_bound < 0 || !blk) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
vblk_log_ioerr("[VBLK] sata read ioerr", &req, 2);
|
||||
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;
|
||||
u64 sector = req.hdr.sector;
|
||||
for (int i = 0; i < req.ndata; i++) {
|
||||
u32 nsec = req.dlen[i] / 512;
|
||||
if (nsec == 0) nsec = 1;
|
||||
if (blk->read((u32)g_blk_bound, sector, nsec, req.data[i]) != 0) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
sector += nsec;
|
||||
bytes_done += req.dlen[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_OUT: {
|
||||
if (req.ndata == 0 || g_blk_bound < 0 || !blk) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
u64 sector = req.hdr.sector;
|
||||
for (int i = 0; i < req.ndata; i++) {
|
||||
u32 nsec = req.dlen[i] / 512;
|
||||
if (nsec == 0) nsec = 1;
|
||||
if (blk->write((u32)g_blk_bound, sector, nsec, req.data[i]) != 0) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
sector += nsec;
|
||||
}
|
||||
bytes_done = 0;
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_GET_ID:
|
||||
vblk_write_id(&req, "DESHAB-VIRTIO-BLK");
|
||||
bytes_done = (req.ndata > 0) ? req.dlen[0] : 0;
|
||||
break;
|
||||
case VIRTIO_BLK_T_FLUSH:
|
||||
result = VIRTIO_BLK_S_OK; /* 无写缓存,直接成功 */
|
||||
result = VIRTIO_BLK_S_OK;
|
||||
break;
|
||||
default:
|
||||
result = VIRTIO_BLK_S_UNSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 写状态字节 */
|
||||
if (status) *status = result;
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
if (req.status) *req.status = result;
|
||||
return bytes_done;
|
||||
}
|
||||
|
||||
@@ -194,6 +300,7 @@ static void blk_queue_notify(u32 queue_idx) {
|
||||
}
|
||||
|
||||
/* 处理自上次以来的所有新 avail 请求 */
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
u16 cur = g_blk_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
@@ -260,13 +367,6 @@ void virtio_blk_backend_init(void) {
|
||||
*
|
||||
* 读写直接操作内存(memcpy),无需块设备 API。 */
|
||||
|
||||
/* byte-wise memcpy helper (freestanding, no libc) */
|
||||
static void rf_memcpy(void *dst, const void *src, u64 n) {
|
||||
u8 *d = (u8 *)dst;
|
||||
const u8 *s = (const u8 *)src;
|
||||
while (n--) *d++ = *s++;
|
||||
}
|
||||
|
||||
static const u8 *g_rootfs_data; /* rootfs 镜像内存指针(HHDM 虚拟地址) */
|
||||
static u64 g_rootfs_size; /* rootfs 镜像大小(字节) */
|
||||
static u64 g_rootfs_capacity; /* rootfs 容量(扇区数,512B 单位) */
|
||||
@@ -277,17 +377,17 @@ static u32 rootfs_blk_read_config(u32 offset, int width) {
|
||||
u8 *c = (u8 *)&cfg;
|
||||
for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0;
|
||||
cfg.capacity = g_rootfs_capacity;
|
||||
cfg.size_max = 128;
|
||||
cfg.size_max = VIRTIO_BLK_SIZE_MAX_BYTES;
|
||||
cfg.seg_max = 32;
|
||||
cfg.blk_size = 512;
|
||||
if (offset + 4 <= sizeof(cfg)) {
|
||||
u32 val = 0;
|
||||
rf_memcpy(&val, c + offset, 4);
|
||||
vblk_memcpy(&val, c + offset, 4);
|
||||
return val;
|
||||
}
|
||||
if (offset < sizeof(cfg)) {
|
||||
u32 val = 0;
|
||||
rf_memcpy(&val, c + offset, 4);
|
||||
vblk_memcpy(&val, c + offset, 4);
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
@@ -295,89 +395,48 @@ static u32 rootfs_blk_read_config(u32 offset, int width) {
|
||||
|
||||
/* rootfs 请求处理:直接内存拷贝 */
|
||||
static u32 rootfs_blk_handle_chain(struct virtq_desc *desc, u16 head, u32 qnum) {
|
||||
(void)qnum;
|
||||
struct virtio_blk_outhdr hdr;
|
||||
u8 *hptr = (u8 *)virtio_gpa_to_host(desc[head].addr);
|
||||
if (!hptr) return 0;
|
||||
rf_memcpy(&hdr, hptr, sizeof(hdr));
|
||||
|
||||
u32 type = hdr.type;
|
||||
u64 sector = hdr.sector;
|
||||
|
||||
u16 data_idx = 0xFFFF;
|
||||
u16 status_idx = 0xFFFF;
|
||||
if (desc[head].flags & VIRTQ_DESC_F_NEXT) {
|
||||
data_idx = desc[head].next;
|
||||
u16 cur = data_idx;
|
||||
int hops = 0;
|
||||
while ((desc[cur].flags & VIRTQ_DESC_F_NEXT) && hops < 16) {
|
||||
cur = desc[cur].next;
|
||||
hops++;
|
||||
}
|
||||
status_idx = cur;
|
||||
}
|
||||
|
||||
u8 *status = (u8 *)0;
|
||||
if (status_idx != 0xFFFF && (desc[status_idx].flags & VIRTQ_DESC_F_WRITE)) {
|
||||
status = (u8 *)virtio_gpa_to_host(desc[status_idx].addr);
|
||||
struct vblk_req req;
|
||||
if (vblk_parse_req(desc, qnum, head, &req) != 0) {
|
||||
vblk_log_ioerr("[VBLK] rootfs parse fail", &req, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 bytes_done = 0;
|
||||
u8 result = VIRTIO_BLK_S_OK;
|
||||
|
||||
switch (type) {
|
||||
case VIRTIO_BLK_T_IN: { /* 读:rootfs 内存 → guest */
|
||||
if (data_idx == 0xFFFF || !g_rootfs_data) {
|
||||
switch (req.hdr.type) {
|
||||
case VIRTIO_BLK_T_IN:
|
||||
if (req.ndata == 0 || !g_rootfs_data) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
vblk_log_ioerr("[VBLK] rootfs read ioerr", &req, 2);
|
||||
break;
|
||||
}
|
||||
bytes_done = vblk_copy_from_image(&req, g_rootfs_data, g_rootfs_size);
|
||||
break;
|
||||
case VIRTIO_BLK_T_OUT:
|
||||
/* Rootfs is a Limine module in RAM; allow writes so Linux can
|
||||
* update the superblock after remount/errors. Ephemeral. */
|
||||
if (req.ndata == 0 || !g_rootfs_data) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
u8 *dbuf = (u8 *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
u32 dlen = desc[data_idx].len;
|
||||
if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; }
|
||||
u64 offset = sector * 512ULL;
|
||||
if (offset + dlen > g_rootfs_size) {
|
||||
/* 超出范围:截断或填零 */
|
||||
u64 avail = (offset < g_rootfs_size) ? (g_rootfs_size - offset) : 0;
|
||||
if (avail > 0) {
|
||||
rf_memcpy(dbuf, g_rootfs_data + offset, avail);
|
||||
}
|
||||
/* 剩余部分填零 */
|
||||
if (dlen > avail) {
|
||||
u8 *p = dbuf + avail;
|
||||
u32 rem = dlen - (u32)avail;
|
||||
while (rem--) *p++ = 0;
|
||||
}
|
||||
bytes_done = dlen;
|
||||
} else {
|
||||
rf_memcpy(dbuf, g_rootfs_data + offset, dlen);
|
||||
bytes_done = dlen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_OUT: /* 写:暂不支持(rootfs 只读) */
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
bytes_done = vblk_copy_to_image(&req, (u8 *)g_rootfs_data, g_rootfs_size);
|
||||
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-ROOTFS";
|
||||
u32 i = 0;
|
||||
for (; id[i] && i < desc[data_idx].len - 1; i++) idbuf[i] = id[i];
|
||||
idbuf[i] = 0;
|
||||
bytes_done = i + 1;
|
||||
}
|
||||
}
|
||||
vblk_write_id(&req, "DESHAB-ROOTFS");
|
||||
bytes_done = (req.ndata > 0) ? req.dlen[0] : 0;
|
||||
break;
|
||||
case VIRTIO_BLK_T_FLUSH:
|
||||
result = VIRTIO_BLK_S_OK;
|
||||
break;
|
||||
default:
|
||||
result = VIRTIO_BLK_S_UNSUPP;
|
||||
vblk_log_ioerr("[VBLK] rootfs unsupp", &req, 3);
|
||||
break;
|
||||
}
|
||||
|
||||
if (status) *status = result;
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
if (req.status) *req.status = result;
|
||||
return bytes_done;
|
||||
}
|
||||
|
||||
@@ -396,6 +455,7 @@ static void rootfs_blk_queue_notify(u32 queue_idx) {
|
||||
return;
|
||||
}
|
||||
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
u16 cur = g_rootfs_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
@@ -453,130 +513,362 @@ void virtio_rootfs_blk_init(void) {
|
||||
|
||||
/* ===== Extra rootfs virtio-blk 后端(VSCode Phase 4,可写持久卷) =====
|
||||
*
|
||||
* 第三个 virtio-blk 设备,后端为内存中的 linux-extra-rootfs.img
|
||||
* (Limine boot module,路径含 "extra")。guest 看到 /dev/vdc,
|
||||
* 用作 overlayfs 的 rw 上层或 VSCode 数据卷。
|
||||
*
|
||||
* 与 rootfs(vdb 只读)不同,本后端支持 VIRTIO_BLK_T_OUT 真正写入内存
|
||||
* (易失:重启丢失;Phase 7 再做写回 FAT32 持久化)。
|
||||
*
|
||||
* 无 extra-rootfs module 时安全不注册(guest cmdline 无 slot6 参数)。 */
|
||||
* guest /dev/vdc。优先用 Limine extra-rootfs boot module(内存后端)。
|
||||
* 模块缺省时把 ESP 上的 boot/linux-extra-rootfs.img 按 FAT32 簇映射到
|
||||
* AHCI:Limine 不必把 ~4GiB 打进 RAM(6G WSL QEMU 会在加载该 module 时死)。
|
||||
*/
|
||||
|
||||
static u8 *g_xrootfs_data;
|
||||
static u64 g_xrootfs_size;
|
||||
static u64 g_xrootfs_capacity;
|
||||
static u16 g_xrootfs_last_avail;
|
||||
|
||||
#define XROOTFS_MAX_CLUSTERS 131072u
|
||||
#define XROOTFS_FATWIN_SECS 256u
|
||||
|
||||
static u32 g_xrootfs_spc;
|
||||
static u32 g_xrootfs_nclus;
|
||||
static u32 g_xrootfs_clus_lba[XROOTFS_MAX_CLUSTERS];
|
||||
static u8 g_xrootfs_dirbuf[FAT32_MAX_CLUSTER_BYTES] __attribute__((aligned(16)));
|
||||
static u8 g_xrootfs_fatwin[XROOTFS_FATWIN_SECS * 512u] __attribute__((aligned(16)));
|
||||
static u32 g_xrootfs_fat_abs;
|
||||
static u32 g_xrootfs_spf;
|
||||
static u32 g_xrootfs_fatwin_sec0;
|
||||
static int g_xrootfs_fatwin_valid;
|
||||
|
||||
static int xrootfs_blk_rw(u32 index, u64 lba, u32 count, void *buf) {
|
||||
const dkm_block_api *blk = block_get_api();
|
||||
if (!blk || !buf || count == 0) return -1;
|
||||
return blk->read(index, lba, count, buf);
|
||||
}
|
||||
|
||||
static int xrootfs_blk_ww(u32 index, u64 lba, u32 count, const void *buf) {
|
||||
const dkm_block_api *blk = block_get_api();
|
||||
if (!blk || !buf || count == 0) return -1;
|
||||
if (!blk->write) return -2;
|
||||
return blk->write(index, lba, count, buf);
|
||||
}
|
||||
|
||||
static u32 xrootfs_fat_next(u32 clus) {
|
||||
u32 fat_byte, sec, off, win_secs, want;
|
||||
if (clus < 2) return 0x0FFFFFFFu;
|
||||
fat_byte = clus * 4u;
|
||||
sec = fat_byte / 512u;
|
||||
off = fat_byte % 512u;
|
||||
if (sec >= g_xrootfs_spf) return 0x0FFFFFFFu;
|
||||
if (!g_xrootfs_fatwin_valid ||
|
||||
sec < g_xrootfs_fatwin_sec0 ||
|
||||
sec >= g_xrootfs_fatwin_sec0 + XROOTFS_FATWIN_SECS) {
|
||||
want = g_xrootfs_spf - sec;
|
||||
win_secs = XROOTFS_FATWIN_SECS;
|
||||
if (want < win_secs) win_secs = want;
|
||||
if (win_secs == 0) return 0x0FFFFFFFu;
|
||||
if (xrootfs_blk_rw(0, (u64)g_xrootfs_fat_abs + sec, win_secs,
|
||||
g_xrootfs_fatwin) != 0) {
|
||||
return 0x0FFFFFFFu;
|
||||
}
|
||||
g_xrootfs_fatwin_sec0 = sec;
|
||||
g_xrootfs_fatwin_valid = 1;
|
||||
}
|
||||
{
|
||||
u32 idx = (sec - g_xrootfs_fatwin_sec0) * 512u + off;
|
||||
const u8 *p = g_xrootfs_fatwin + idx;
|
||||
return ((u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24))
|
||||
& 0x0FFFFFFFu;
|
||||
}
|
||||
}
|
||||
|
||||
static int xrootfs_find_in_dir(u32 vol_lba, u32 data_start, u32 start_clus,
|
||||
const char *long_name, int is_dir,
|
||||
u32 *out_clus, u32 *out_size) {
|
||||
fat32_lfn_buf lfn;
|
||||
u32 clus = start_clus;
|
||||
u32 spc = g_xrootfs_spc;
|
||||
if (spc == 0 || spc * 512u > FAT32_MAX_CLUSTER_BYTES) return -1;
|
||||
fat32_lfn_init(&lfn);
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8u) {
|
||||
u32 lba = vol_lba + data_start + (clus - 2u) * spc;
|
||||
const u8 *entries;
|
||||
u32 entry_count, e;
|
||||
if (xrootfs_blk_rw(0, lba, spc, g_xrootfs_dirbuf) != 0) return -3;
|
||||
entries = g_xrootfs_dirbuf;
|
||||
entry_count = (spc * 512u) / 32u;
|
||||
for (e = 0; e < entry_count; e++) {
|
||||
const u8 *entry = entries + e * 32u;
|
||||
const u8 *de;
|
||||
int is_short, entry_is_dir, matched;
|
||||
if (entry[0] == 0) return -4;
|
||||
if ((u8)entry[0] == 0xE5) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
de = entry;
|
||||
is_short = fat32_lfn_process(&lfn, entry);
|
||||
if (!is_short) continue;
|
||||
if (de[11] & 0x08) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
entry_is_dir = (de[11] & 0x10) ? 1 : 0;
|
||||
if (entry_is_dir != is_dir) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
matched = 0;
|
||||
if (lfn.valid && long_name) {
|
||||
char ascii[FAT32_LFN_MAX];
|
||||
int n = fat32_lfn_to_ascii(&lfn, ascii, sizeof(ascii));
|
||||
if (n >= 0 && fat32_lfn_streq_ci(ascii, long_name)) matched = 1;
|
||||
}
|
||||
if (!matched && long_name) {
|
||||
char short_disp[13];
|
||||
fat32_lfn_short_to_str(de, short_disp);
|
||||
if (fat32_lfn_streq_ci(short_disp, long_name)) matched = 1;
|
||||
}
|
||||
if (matched) {
|
||||
u16 clow = (u16)de[26] | ((u16)de[27] << 8);
|
||||
u16 chigh = (u16)de[20] | ((u16)de[21] << 8);
|
||||
*out_clus = (u32)clow | ((u32)chigh << 16);
|
||||
*out_size = (u32)de[28] | ((u32)de[29] << 8) |
|
||||
((u32)de[30] << 16) | ((u32)de[31] << 24);
|
||||
return 0;
|
||||
}
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
clus = xrootfs_fat_next(clus) & 0x0FFFFFFFu;
|
||||
}
|
||||
return -4;
|
||||
}
|
||||
|
||||
static int xrootfs_map_esp_file(void) {
|
||||
const dkm_block_api *blk = block_get_api();
|
||||
u8 bpbsec[512] __attribute__((aligned(16)));
|
||||
u64 vol;
|
||||
u32 rsvd, fc, spf, spc, root, data_start, file_clus, file_size, nclus, i, fc_cur;
|
||||
u32 boot_clus, dummy;
|
||||
if (!blk || blk->device_count() == 0 || blk->sector_size(0) != 512) {
|
||||
log_warn("[VBLK] extra-rootfs ESP map needs AHCI first");
|
||||
return -1;
|
||||
}
|
||||
vol = fat32_part_probe(xrootfs_blk_rw, 0);
|
||||
if (blk->read(0, vol, 1, bpbsec) != 0) return -2;
|
||||
if (!fat32_part_bpb_ok(bpbsec)) return -3;
|
||||
rsvd = (u32)bpbsec[14] | ((u32)bpbsec[15] << 8);
|
||||
fc = bpbsec[16];
|
||||
spc = bpbsec[13];
|
||||
spf = (u32)bpbsec[36] | ((u32)bpbsec[37] << 8) |
|
||||
((u32)bpbsec[38] << 16) | ((u32)bpbsec[39] << 24);
|
||||
root = (u32)bpbsec[44] | ((u32)bpbsec[45] << 8) |
|
||||
((u32)bpbsec[46] << 16) | ((u32)bpbsec[47] << 24);
|
||||
if (spc == 0 || spc > 64 || spf == 0 || root < 2) return -4;
|
||||
g_xrootfs_spc = spc;
|
||||
g_xrootfs_spf = spf;
|
||||
g_xrootfs_fat_abs = (u32)vol + rsvd;
|
||||
g_xrootfs_fatwin_valid = 0;
|
||||
data_start = rsvd + fc * spf;
|
||||
|
||||
boot_clus = 0;
|
||||
dummy = 0;
|
||||
if (xrootfs_find_in_dir((u32)vol, data_start, root, "boot", 1,
|
||||
&boot_clus, &dummy) != 0) {
|
||||
log_warn("[VBLK] extra-rootfs: ESP /boot not found");
|
||||
return -5;
|
||||
}
|
||||
file_clus = 0;
|
||||
file_size = 0;
|
||||
if (xrootfs_find_in_dir((u32)vol, data_start, boot_clus,
|
||||
"linux-extra-rootfs.img", 0,
|
||||
&file_clus, &file_size) != 0 ||
|
||||
file_clus < 2 || file_size == 0) {
|
||||
log_warn("[VBLK] extra-rootfs: linux-extra-rootfs.img not on ESP");
|
||||
return -6;
|
||||
}
|
||||
|
||||
nclus = (file_size + (spc * 512u) - 1u) / (spc * 512u);
|
||||
if (nclus == 0 || nclus > XROOTFS_MAX_CLUSTERS) {
|
||||
log_error("[VBLK] extra-rootfs cluster map too large");
|
||||
log_hex64("[VBLK] nclus=", nclus);
|
||||
return -7;
|
||||
}
|
||||
|
||||
fc_cur = file_clus;
|
||||
for (i = 0; i < nclus; i++) {
|
||||
if (fc_cur < 2 || fc_cur >= 0x0FFFFFF8u) {
|
||||
log_error("[VBLK] extra-rootfs FAT chain short");
|
||||
return -8;
|
||||
}
|
||||
g_xrootfs_clus_lba[i] = (u32)vol + data_start + (fc_cur - 2u) * spc;
|
||||
fc_cur = xrootfs_fat_next(fc_cur) & 0x0FFFFFFFu;
|
||||
}
|
||||
|
||||
g_xrootfs_nclus = nclus;
|
||||
g_xrootfs_data = 0;
|
||||
g_xrootfs_size = file_size;
|
||||
g_xrootfs_capacity = file_size / 512u;
|
||||
if (file_size % 512u) g_xrootfs_capacity++;
|
||||
log_info("[VBLK] extra-rootfs-blk backend bound (esp file)");
|
||||
log_hex64("[VBLK] extra-rootfs size=", file_size);
|
||||
log_hex64("[VBLK] extra-rootfs nclus=", nclus);
|
||||
log_hex64("[VBLK] extra-rootfs first_lba=", g_xrootfs_clus_lba[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xrootfs_disk_xfer(u64 img_sector, u32 nsec, u8 *buf, int is_write) {
|
||||
const dkm_block_api *blk = block_get_api();
|
||||
u32 spc = g_xrootfs_spc;
|
||||
if (!blk || !buf || nsec == 0 || spc == 0) return -1;
|
||||
while (nsec) {
|
||||
u32 ci, off, chunk, c;
|
||||
u64 disk;
|
||||
if (img_sector / spc >= g_xrootfs_nclus) return -2;
|
||||
ci = (u32)(img_sector / spc);
|
||||
off = (u32)(img_sector % spc);
|
||||
disk = g_xrootfs_clus_lba[ci] + off;
|
||||
chunk = spc - off;
|
||||
if (chunk > nsec) chunk = nsec;
|
||||
c = ci;
|
||||
while (chunk < nsec && (c + 1u) < g_xrootfs_nclus &&
|
||||
g_xrootfs_clus_lba[c + 1u] == g_xrootfs_clus_lba[c] + spc) {
|
||||
u32 add = spc;
|
||||
if (add > nsec - chunk) add = nsec - chunk;
|
||||
chunk += add;
|
||||
c++;
|
||||
}
|
||||
if (is_write) {
|
||||
if (xrootfs_blk_ww(0, disk, chunk, buf) != 0) return -3;
|
||||
} else {
|
||||
if (xrootfs_blk_rw(0, disk, chunk, buf) != 0) return -3;
|
||||
}
|
||||
buf += chunk * 512u;
|
||||
img_sector += chunk;
|
||||
nsec -= chunk;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xrootfs_disk_xfer_bytes(u64 off, u32 n, u8 *buf, int is_write) {
|
||||
static u8 bounce[512];
|
||||
while (n) {
|
||||
u64 sector = off / 512ULL;
|
||||
u32 so = (u32)(off % 512ULL);
|
||||
u32 chunk;
|
||||
if (so == 0 && n >= 512u) {
|
||||
u32 nsec = n / 512u;
|
||||
if (xrootfs_disk_xfer(sector, nsec, buf, is_write) != 0)
|
||||
return -1;
|
||||
chunk = nsec * 512u;
|
||||
} else {
|
||||
chunk = 512u - so;
|
||||
if (chunk > n)
|
||||
chunk = n;
|
||||
if (xrootfs_disk_xfer(sector, 1, bounce, 0) != 0)
|
||||
return -1;
|
||||
if (is_write) {
|
||||
vblk_memcpy(bounce + so, buf, chunk);
|
||||
if (xrootfs_disk_xfer(sector, 1, bounce, 1) != 0)
|
||||
return -1;
|
||||
} else {
|
||||
vblk_memcpy(buf, bounce + so, chunk);
|
||||
}
|
||||
}
|
||||
buf += chunk;
|
||||
off += chunk;
|
||||
n -= chunk;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 xrootfs_copy_disk(const struct vblk_req *r, int is_write) {
|
||||
u64 offset = r->hdr.sector * 512ULL;
|
||||
u32 pos = 0;
|
||||
for (int i = 0; i < r->ndata; i++) {
|
||||
u32 n = r->dlen[i];
|
||||
if (n == 0)
|
||||
continue;
|
||||
if (xrootfs_disk_xfer_bytes(offset + pos, n, r->data[i], is_write) != 0)
|
||||
return (u32)-1;
|
||||
pos += n;
|
||||
}
|
||||
return is_write ? 0 : pos;
|
||||
}
|
||||
|
||||
static u32 xrootfs_blk_read_config(u32 offset, int width) {
|
||||
(void)width;
|
||||
struct virtio_blk_config cfg;
|
||||
u8 *c = (u8 *)&cfg;
|
||||
for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0;
|
||||
cfg.capacity = g_xrootfs_capacity;
|
||||
cfg.size_max = 128;
|
||||
cfg.size_max = VIRTIO_BLK_SIZE_MAX_BYTES;
|
||||
cfg.seg_max = 32;
|
||||
cfg.blk_size = 512;
|
||||
if (offset < sizeof(cfg)) {
|
||||
u32 val = 0;
|
||||
rf_memcpy(&val, c + offset, (offset + 4 <= sizeof(cfg)) ? 4 : (sizeof(cfg) - offset));
|
||||
vblk_memcpy(&val, c + offset, (offset + 4 <= sizeof(cfg)) ? 4 : (sizeof(cfg) - offset));
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 xrootfs_blk_handle_chain(struct virtq_desc *desc, u16 head, u32 qnum) {
|
||||
(void)qnum;
|
||||
struct virtio_blk_outhdr hdr;
|
||||
u8 *hptr = (u8 *)virtio_gpa_to_host(desc[head].addr);
|
||||
if (!hptr) return 0;
|
||||
rf_memcpy(&hdr, hptr, sizeof(hdr));
|
||||
|
||||
u32 type = hdr.type;
|
||||
u64 sector = hdr.sector;
|
||||
|
||||
u16 data_idx = 0xFFFF;
|
||||
u16 status_idx = 0xFFFF;
|
||||
if (desc[head].flags & VIRTQ_DESC_F_NEXT) {
|
||||
data_idx = desc[head].next;
|
||||
u16 cur = data_idx;
|
||||
int hops = 0;
|
||||
while ((desc[cur].flags & VIRTQ_DESC_F_NEXT) && hops < 16) {
|
||||
cur = desc[cur].next;
|
||||
hops++;
|
||||
}
|
||||
status_idx = cur;
|
||||
}
|
||||
|
||||
u8 *status = (u8 *)0;
|
||||
if (status_idx != 0xFFFF && (desc[status_idx].flags & VIRTQ_DESC_F_WRITE)) {
|
||||
status = (u8 *)virtio_gpa_to_host(desc[status_idx].addr);
|
||||
struct vblk_req req;
|
||||
if (vblk_parse_req(desc, qnum, head, &req) != 0) {
|
||||
vblk_log_ioerr("[VBLK] xrootfs parse fail", &req, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 bytes_done = 0;
|
||||
u8 result = VIRTIO_BLK_S_OK;
|
||||
|
||||
switch (type) {
|
||||
case VIRTIO_BLK_T_IN: { /* 读:镜像内存 → guest */
|
||||
if (data_idx == 0xFFFF || !g_xrootfs_data) {
|
||||
switch (req.hdr.type) {
|
||||
case VIRTIO_BLK_T_IN:
|
||||
if (req.ndata == 0) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
vblk_log_ioerr("[VBLK] xrootfs read ioerr", &req, 2);
|
||||
break;
|
||||
}
|
||||
u8 *dbuf = (u8 *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
u32 dlen = desc[data_idx].len;
|
||||
if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; }
|
||||
u64 offset = sector * 512ULL;
|
||||
u64 avail = (offset < g_xrootfs_size) ? (g_xrootfs_size - offset) : 0;
|
||||
u32 copied = 0;
|
||||
if (avail > 0) {
|
||||
u32 n = (dlen < avail) ? dlen : (u32)avail;
|
||||
rf_memcpy(dbuf, g_xrootfs_data + offset, n);
|
||||
copied = n;
|
||||
}
|
||||
/* 越界部分填零 */
|
||||
while (copied < dlen) dbuf[copied++] = 0;
|
||||
bytes_done = dlen;
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_OUT: { /* 写:guest → 镜像内存(可写卷) */
|
||||
if (data_idx == 0xFFFF || !g_xrootfs_data) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
const u8 *dbuf = (const u8 *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
u32 dlen = desc[data_idx].len;
|
||||
if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; }
|
||||
u64 offset = sector * 512ULL;
|
||||
if (offset + dlen > g_xrootfs_size) {
|
||||
/* 越界写:只写卷内部分,其余丢弃 */
|
||||
u64 avail = (offset < g_xrootfs_size) ? (g_xrootfs_size - offset) : 0;
|
||||
if (avail > 0) rf_memcpy(g_xrootfs_data + offset, dbuf, avail);
|
||||
} else {
|
||||
rf_memcpy(g_xrootfs_data + offset, dbuf, dlen);
|
||||
}
|
||||
bytes_done = 0; /* 写操作 used.len=0 */
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_GET_ID:
|
||||
if (data_idx != 0xFFFF) {
|
||||
char *idbuf = (char *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
if (idbuf) {
|
||||
const char *id = "DESHAB-EXTRA-ROOTFS";
|
||||
u32 i = 0;
|
||||
for (; id[i] && i < desc[data_idx].len - 1; i++) idbuf[i] = id[i];
|
||||
idbuf[i] = 0;
|
||||
bytes_done = i + 1;
|
||||
if (g_xrootfs_data) {
|
||||
bytes_done = vblk_copy_from_image(&req, g_xrootfs_data, g_xrootfs_size);
|
||||
} else if (g_xrootfs_nclus) {
|
||||
bytes_done = xrootfs_copy_disk(&req, 0);
|
||||
if (bytes_done == (u32)-1) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
bytes_done = 0;
|
||||
vblk_log_ioerr("[VBLK] xrootfs disk read ioerr", &req, 2);
|
||||
}
|
||||
} else {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
vblk_log_ioerr("[VBLK] xrootfs read ioerr", &req, 2);
|
||||
}
|
||||
break;
|
||||
case VIRTIO_BLK_T_OUT:
|
||||
if (req.ndata == 0) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
if (g_xrootfs_data) {
|
||||
bytes_done = vblk_copy_to_image(&req, g_xrootfs_data, g_xrootfs_size);
|
||||
} else if (g_xrootfs_nclus) {
|
||||
if (xrootfs_copy_disk(&req, 1) == (u32)-1)
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
} else {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_BLK_T_GET_ID:
|
||||
vblk_write_id(&req, "DESHAB-EXTRA-ROOTFS");
|
||||
bytes_done = (req.ndata > 0) ? req.dlen[0] : 0;
|
||||
break;
|
||||
case VIRTIO_BLK_T_FLUSH:
|
||||
result = VIRTIO_BLK_S_OK;
|
||||
break;
|
||||
default:
|
||||
result = VIRTIO_BLK_S_UNSUPP;
|
||||
vblk_log_ioerr("[VBLK] xrootfs unsupp", &req, 3);
|
||||
break;
|
||||
}
|
||||
|
||||
if (status) *status = result;
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
if (req.status) *req.status = result;
|
||||
return bytes_done;
|
||||
}
|
||||
|
||||
@@ -592,6 +884,7 @@ static void xrootfs_blk_queue_notify(u32 queue_idx) {
|
||||
return;
|
||||
}
|
||||
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
u16 cur = g_xrootfs_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
@@ -630,18 +923,23 @@ static struct virtio_backend g_xrootfs_blk_backend = {
|
||||
void virtio_extra_rootfs_blk_init(void) {
|
||||
u64 size = 0;
|
||||
void *img = linux_find_extra_rootfs_module(&size);
|
||||
if (!img || size == 0) {
|
||||
log_warn("[VBLK] no extra-rootfs module, slot6 backend not registered");
|
||||
|
||||
g_xrootfs_nclus = 0;
|
||||
g_xrootfs_data = 0;
|
||||
g_xrootfs_size = 0;
|
||||
g_xrootfs_capacity = 0;
|
||||
|
||||
if (img && size != 0) {
|
||||
g_xrootfs_data = (u8 *)img;
|
||||
g_xrootfs_size = size;
|
||||
g_xrootfs_capacity = size / 512;
|
||||
if (size % 512) g_xrootfs_capacity++;
|
||||
log_info("[VBLK] extra-rootfs-blk backend bound (rw module)");
|
||||
log_hex64("[VBLK] extra-rootfs size=", size);
|
||||
} else if (xrootfs_map_esp_file() != 0) {
|
||||
log_warn("[VBLK] no extra-rootfs module or ESP file, slot6 not registered");
|
||||
return;
|
||||
}
|
||||
|
||||
g_xrootfs_data = (u8 *)img;
|
||||
g_xrootfs_size = size;
|
||||
g_xrootfs_capacity = size / 512;
|
||||
if (size % 512) g_xrootfs_capacity++;
|
||||
|
||||
log_info("[VBLK] extra-rootfs-blk backend bound (rw)");
|
||||
log_hex64("[VBLK] extra-rootfs size=", size);
|
||||
|
||||
virtio_mmio_register(&g_xrootfs_blk_backend);
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
vmm/virtio_blk.o: vmm\virtio_blk.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\block.h \
|
||||
include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\block.h:
|
||||
include\utsm\log.h:
|
||||
+154
-88
@@ -8,10 +8,10 @@
|
||||
*
|
||||
* Device layout: slot 5 @ 0xF4005000 IRQ10, two queues (ctrlq + cursorq).
|
||||
*
|
||||
* Supported 2D command set (Virtio 1.1 §5.4.4):
|
||||
* GET_NUM_SCANOUTS, GET_DISPLAY_INFO, RESOURCE_CREATE_2D, RESOURCE_DESTROY,
|
||||
* RESOURCE_ATTACH_BACKING, RESOURCE_DETACH_BACKING, SET_SCANOUT,
|
||||
* TRANSFER_TO_HOST_2D, RESOURCE_FLUSH.
|
||||
* Supported 2D command set (Linux uapi / Virtio 1.2 §5.7.6):
|
||||
* GET_DISPLAY_INFO, RESOURCE_CREATE_2D, RESOURCE_UNREF, SET_SCANOUT,
|
||||
* RESOURCE_FLUSH, TRANSFER_TO_HOST_2D, RESOURCE_ATTACH_BACKING,
|
||||
* RESOURCE_DETACH_BACKING.
|
||||
*
|
||||
* Cursor commands (UPDATE_CURSOR / MOVE_CURSOR) are acknowledged but not
|
||||
* rendered — the Deshab desktop draws its own PS/2 cursor over the blit.
|
||||
@@ -119,29 +119,42 @@ struct virtio_gpu_resource_flush {
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* P7.6: cursor queue commands (UPDATE_CURSOR / MOVE_CURSOR share struct) */
|
||||
/* Cursor VQ (Linux uapi virtio_gpu.h). pos is scanout_id/x/y, not a 2D rect. */
|
||||
struct virtio_gpu_cursor_pos {
|
||||
u32 scanout_id;
|
||||
u32 x;
|
||||
u32 y;
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_update_cursor {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect pos; /* x, y on scanout */
|
||||
struct virtio_gpu_cursor_pos pos;
|
||||
u32 resource_id; /* 0 = hide cursor */
|
||||
u32 hot_x;
|
||||
u32 hot_y;
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define VGPU_CURSOR_MAX 64 /* max cursor image dimension (pixels) */
|
||||
|
||||
/* command types */
|
||||
#define VIRTIO_GPU_CMD_GET_NUM_SCANOUTS 0x0102
|
||||
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0103
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0104
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_DESTROY 0x0105
|
||||
/* Command types MUST match Linux include/uapi/linux/virtio_gpu.h. */
|
||||
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102
|
||||
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104
|
||||
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING 0x0107
|
||||
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0108
|
||||
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0109
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x010a
|
||||
#define VIRTIO_GPU_CMD_UPDATE_CURSOR 0x0110
|
||||
#define VIRTIO_GPU_CMD_MOVE_CURSOR 0x0111
|
||||
#define VIRTIO_GPU_CMD_GET_CAPSET_INFO 0x0108
|
||||
#define VIRTIO_GPU_CMD_GET_CAPSET 0x0109
|
||||
#define VIRTIO_GPU_CMD_GET_EDID 0x010a
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID 0x010b
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB 0x010c
|
||||
#define VIRTIO_GPU_CMD_SET_SCANOUT_BLOB 0x010d
|
||||
#define VIRTIO_GPU_CMD_UPDATE_CURSOR 0x0300
|
||||
#define VIRTIO_GPU_CMD_MOVE_CURSOR 0x0301
|
||||
|
||||
/* response types */
|
||||
#define VIRTIO_GPU_RESP_OK_NODATA 0x1100
|
||||
@@ -152,11 +165,15 @@ struct virtio_gpu_update_cursor {
|
||||
#define VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID 0x1203
|
||||
#define VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER 0x1205
|
||||
|
||||
/* formats we accept (32bpp). Copied raw — no endian swap. */
|
||||
/* formats we accept (32bpp). Values match Linux virtio_gpu_formats. */
|
||||
#define VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM 1
|
||||
#define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2
|
||||
#define VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM 3
|
||||
#define VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM 4
|
||||
#define VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM 3
|
||||
#define VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM 4
|
||||
#define VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM 67
|
||||
#define VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM 68
|
||||
#define VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM 121
|
||||
#define VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM 134
|
||||
|
||||
/* fence flag (we just ack fences, no real fencing) */
|
||||
#define VIRTIO_GPU_FLAG_FENCE (1 << 0)
|
||||
@@ -170,7 +187,8 @@ struct virtio_gpu_update_cursor {
|
||||
#define VGPU_SCANOUT_SIZE (VGPU_SCANOUT_STRIDE * VGPU_SCANOUT_H) /* 3 MB */
|
||||
|
||||
#define VGPU_MAX_RESOURCES 16
|
||||
#define VGPU_MAX_BACKING_ENTRIES 8
|
||||
/* 1024×768×4 is 768 pages; keep headroom for larger dumb buffers. */
|
||||
#define VGPU_MAX_BACKING_ENTRIES 2048
|
||||
|
||||
struct vgpu_resource {
|
||||
int in_use;
|
||||
@@ -179,11 +197,10 @@ struct vgpu_resource {
|
||||
u32 width;
|
||||
u32 height;
|
||||
int has_backing;
|
||||
/* Backing memory (contiguous assumption: first entry GPA + total len).
|
||||
* Multi-entry attach is summed; guest framebuffer allocations are
|
||||
* physically contiguous so this holds in practice. */
|
||||
u64 backing_gpa;
|
||||
u64 backing_len;
|
||||
u64 backing_gpa; /* first entry (debug / cursor fallback) */
|
||||
u64 backing_len; /* sum of all entries */
|
||||
u32 nr_entries;
|
||||
struct virtio_gpu_mem_entry entries[VGPU_MAX_BACKING_ENTRIES];
|
||||
};
|
||||
|
||||
/* Per-device state. Single instance (one virtio-gpu device). */
|
||||
@@ -228,6 +245,11 @@ static struct {
|
||||
u16 last_avail_ctrl;
|
||||
u16 last_avail_cursor;
|
||||
|
||||
/* P9: RESOURCE_FLUSH 计数(只计 scanout resource 的 flush)。
|
||||
* verify 脚本据 \[VGPU\] FLUSH_N 判断 Plasma/Xorg 是否真的推帧——
|
||||
* SET_SCANOUT 只证明 fbdev 绑定了 resource,不代表有新像素。 */
|
||||
u64 flush_count;
|
||||
|
||||
struct virtio_backend backend;
|
||||
} g_gpu;
|
||||
|
||||
@@ -259,6 +281,7 @@ static void res_release(struct vgpu_resource *r) {
|
||||
r->has_backing = 0;
|
||||
r->backing_gpa = 0;
|
||||
r->backing_len = 0;
|
||||
r->nr_entries = 0;
|
||||
}
|
||||
|
||||
/* ===== surface pool access =====
|
||||
@@ -276,6 +299,7 @@ static int surface_ready(void) {
|
||||
* fills cmd/resp host vaddrs + lens; resp may be NULL if driver omitted it. */
|
||||
struct chain_bufs {
|
||||
void *cmd; u32 cmd_len;
|
||||
void *cmd_extra; u32 cmd_extra_len; /* 2nd readable desc (attach mem_entries) */
|
||||
void *resp; u32 resp_len;
|
||||
u16 head; /* chain head index (for used ring) */
|
||||
};
|
||||
@@ -285,6 +309,7 @@ static int walk_chain(struct virtq_desc *desc, u32 qnum, u16 head,
|
||||
u16 cur = head;
|
||||
int steps = 0;
|
||||
out->cmd = 0; out->cmd_len = 0;
|
||||
out->cmd_extra = 0; out->cmd_extra_len = 0;
|
||||
out->resp = 0; out->resp_len = 0;
|
||||
out->head = head;
|
||||
|
||||
@@ -297,7 +322,11 @@ static int walk_chain(struct virtq_desc *desc, u32 qnum, u16 head,
|
||||
if (d->flags & VIRTQ_DESC_F_WRITE) {
|
||||
if (!out->resp) { out->resp = host; out->resp_len = d->len; }
|
||||
} else {
|
||||
if (!out->cmd) { out->cmd = host; out->cmd_len = d->len; }
|
||||
if (!out->cmd) {
|
||||
out->cmd = host; out->cmd_len = d->len;
|
||||
} else if (!out->cmd_extra) {
|
||||
out->cmd_extra = host; out->cmd_extra_len = d->len;
|
||||
}
|
||||
}
|
||||
if (!(d->flags & VIRTQ_DESC_F_NEXT)) break;
|
||||
cur = d->next;
|
||||
@@ -348,9 +377,15 @@ static u32 handle_resource_create_2d(struct virtio_gpu_resource_create_2d *cmd)
|
||||
u32 id = cmd->resource_id;
|
||||
if (id == 0) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
if (res_find(id)) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
/* accept 32bpp formats; reject exotic sizes */
|
||||
if (cmd->format < VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM ||
|
||||
cmd->format > VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM)
|
||||
/* accept 32bpp formats used by Linux DRM/dumb buffers */
|
||||
if (cmd->format != VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM &&
|
||||
cmd->format != VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM &&
|
||||
cmd->format != VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM &&
|
||||
cmd->format != VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM &&
|
||||
cmd->format != VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM &&
|
||||
cmd->format != VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM &&
|
||||
cmd->format != VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM &&
|
||||
cmd->format != VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM)
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
if (cmd->width == 0 || cmd->height == 0 ||
|
||||
cmd->width > 4096 || cmd->height > 4096)
|
||||
@@ -379,7 +414,7 @@ static u32 handle_resource_destroy(u32 resource_id) {
|
||||
}
|
||||
|
||||
static u32 handle_attach_backing(struct virtio_gpu_resource_attach_backing *cmd,
|
||||
u32 cmd_len) {
|
||||
u32 cmd_len, void *extra, u32 extra_len) {
|
||||
if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
u32 id = cmd->resource_id;
|
||||
struct vgpu_resource *r = res_find(id);
|
||||
@@ -388,21 +423,25 @@ static u32 handle_attach_backing(struct virtio_gpu_resource_attach_backing *cmd,
|
||||
if (n == 0 || n > VGPU_MAX_BACKING_ENTRIES)
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
|
||||
/* entries immediately follow the attach_backing command in the same buffer */
|
||||
u32 hdr_off = (u32)sizeof(struct virtio_gpu_resource_attach_backing);
|
||||
if (cmd_len < hdr_off + n * (u32)sizeof(struct virtio_gpu_mem_entry))
|
||||
u32 need = n * (u32)sizeof(struct virtio_gpu_mem_entry);
|
||||
struct virtio_gpu_mem_entry *entries = 0;
|
||||
if (cmd_len >= hdr_off + need) {
|
||||
entries = (struct virtio_gpu_mem_entry *)((u8 *)cmd + hdr_off);
|
||||
} else if (extra && extra_len >= need) {
|
||||
entries = (struct virtio_gpu_mem_entry *)extra;
|
||||
} else {
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
struct virtio_gpu_mem_entry *entries =
|
||||
(struct virtio_gpu_mem_entry *)((u8 *)cmd + hdr_off);
|
||||
|
||||
/* MVP: record first entry GPA + summed length (contiguous assumption). */
|
||||
u64 base = entries[0].addr;
|
||||
u64 total = 0;
|
||||
for (u32 i = 0; i < n; i++) total += entries[i].length;
|
||||
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
r->entries[i] = entries[i];
|
||||
total += entries[i].length;
|
||||
}
|
||||
r->nr_entries = n;
|
||||
r->has_backing = 1;
|
||||
r->backing_gpa = base;
|
||||
r->backing_gpa = entries[0].addr;
|
||||
r->backing_len = total;
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
@@ -413,6 +452,7 @@ static u32 handle_detach_backing(u32 resource_id) {
|
||||
r->has_backing = 0;
|
||||
r->backing_gpa = 0;
|
||||
r->backing_len = 0;
|
||||
r->nr_entries = 0;
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
@@ -437,9 +477,44 @@ static u32 handle_set_scanout(struct virtio_gpu_set_scanout *cmd) {
|
||||
g_gpu.scanout_y = cmd->r.y;
|
||||
g_gpu.scanout_w = cmd->r.width;
|
||||
g_gpu.scanout_h = cmd->r.height;
|
||||
g_gpu.dirty = 1;
|
||||
log_info("[VGPU] SET_SCANOUT");
|
||||
log_hex64("[VGPU] scanout w=", g_gpu.scanout_w);
|
||||
log_hex64("[VGPU] scanout h=", g_gpu.scanout_h);
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
/* Copy `len` bytes at resource-relative `off` from scatter-gather backing. */
|
||||
static int backing_copy(struct vgpu_resource *r, u64 off, u32 len, u8 *dst) {
|
||||
u64 pos = 0;
|
||||
u32 i;
|
||||
if (!r || !dst || !r->has_backing || len == 0) return -1;
|
||||
for (i = 0; i < r->nr_entries && len; i++) {
|
||||
u64 elen = r->entries[i].length;
|
||||
if (off >= pos + elen) {
|
||||
pos += elen;
|
||||
continue;
|
||||
}
|
||||
{
|
||||
u64 skip = (off > pos) ? (off - pos) : 0;
|
||||
u64 take = elen - skip;
|
||||
void *host;
|
||||
u8 *s;
|
||||
u32 k;
|
||||
if (take > len) take = len;
|
||||
host = virtio_gpa_to_host(r->entries[i].addr + skip);
|
||||
if (!host) return -1;
|
||||
s = (u8 *)host;
|
||||
for (k = 0; k < (u32)take; k++) dst[k] = s[k];
|
||||
dst += take;
|
||||
len -= (u32)take;
|
||||
off += take;
|
||||
pos += elen;
|
||||
}
|
||||
}
|
||||
return (len == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Copy a rect of a resource from guest backing memory into the surface pool
|
||||
* scanout region. Called for both TRANSFER_TO_HOST_2D and (implicitly via flush)
|
||||
* — we do the actual copy on TRANSFER, flush just marks dirty. */
|
||||
@@ -449,9 +524,6 @@ static void composite_rect(struct vgpu_resource *r, struct virtio_gpu_rect *rect
|
||||
/* only composite the resource currently bound to the scanout */
|
||||
if (!g_gpu.scanout_enabled || g_gpu.scanout_res_id != r->resource_id) return;
|
||||
|
||||
void *backing_host = virtio_gpa_to_host(r->backing_gpa);
|
||||
if (!backing_host) return;
|
||||
|
||||
u32 src_stride = r->width * VGPU_BPP;
|
||||
u32 dst_stride = VGPU_SCANOUT_STRIDE; /* scanout is fixed 1024×768 */
|
||||
|
||||
@@ -469,30 +541,20 @@ static void composite_rect(struct vgpu_resource *r, struct virtio_gpu_rect *rect
|
||||
if (rw == 0 || rh == 0) return;
|
||||
|
||||
u8 *dst = (u8 *)g_gpu.surface_vaddr;
|
||||
u8 *src = (u8 *)backing_host + offset;
|
||||
u32 copy_bytes = rw * VGPU_BPP;
|
||||
|
||||
/* guard against backing_len overflow */
|
||||
u64 src_base = offset + (u64)ry * src_stride + (u64)rx * VGPU_BPP;
|
||||
u64 src_need = src_base + (u64)rh * src_stride;
|
||||
if (src_need > r->backing_len) {
|
||||
/* partial: clamp rows to what the backing covers */
|
||||
u64 avail_rows = (r->backing_len > offset) ? (r->backing_len - offset) / src_stride : 0;
|
||||
if (ry >= avail_rows) return;
|
||||
if (ry + rh > avail_rows) rh = (u32)(avail_rows - ry);
|
||||
if (rh == 0) return;
|
||||
}
|
||||
static u8 rowbuf[4096];
|
||||
if (copy_bytes > (u32)sizeof(rowbuf)) copy_bytes = (u32)sizeof(rowbuf);
|
||||
|
||||
for (u32 row = 0; row < rh; row++) {
|
||||
u8 *s = src + (u64)(ry + row) * src_stride + (u64)rx * VGPU_BPP;
|
||||
u64 src_off = offset + (u64)(ry + row) * src_stride + (u64)rx * VGPU_BPP;
|
||||
u8 *d = dst + (u64)(dy + row) * dst_stride + (u64)dx * VGPU_BPP;
|
||||
/* word-copy 4-byte pixels */
|
||||
u32 n = copy_bytes / 4;
|
||||
u32 *sp = (u32 *)s;
|
||||
u32 *dp = (u32 *)d;
|
||||
for (u32 i = 0; i < n; i++) dp[i] = sp[i];
|
||||
/* trailing bytes (shouldn't happen for 32bpp) */
|
||||
for (u32 i = n * 4; i < copy_bytes; i++) d[i] = s[i];
|
||||
if (backing_copy(r, src_off, copy_bytes, rowbuf) != 0) break;
|
||||
{
|
||||
u32 n = copy_bytes / 4;
|
||||
u32 *sp = (u32 *)rowbuf;
|
||||
u32 *dp = (u32 *)d;
|
||||
for (u32 i = 0; i < n; i++) dp[i] = sp[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,6 +572,13 @@ static u32 handle_resource_flush(struct virtio_gpu_resource_flush *cmd) {
|
||||
* resource (composite_rect already copied it on TRANSFER). */
|
||||
if (g_gpu.scanout_enabled && g_gpu.scanout_res_id == cmd->resource_id) {
|
||||
g_gpu.dirty = 1;
|
||||
g_gpu.flush_count++;
|
||||
/* P9: 每 64 次 flush 打一行计数。串口噪音远小于逐次打印;
|
||||
* verify 用 FLUSH_N 出现判断 guest 真的在推帧。 */
|
||||
if ((g_gpu.flush_count & 0x3F) == 1) {
|
||||
log_info("[VGPU] FLUSH_N");
|
||||
log_hex64("[VGPU] flush count=", g_gpu.flush_count);
|
||||
}
|
||||
/* Phase 7: 把 flush rect(resource 坐标系)并入累积 dirty rect
|
||||
* (scanout 坐标系并集),desktop 据此只 blit 变化区域。
|
||||
* 钳制规则与 composite_rect 一致。 */
|
||||
@@ -577,21 +646,6 @@ static void gpu_ctrlq_process(void) {
|
||||
} else {
|
||||
u32 t = hdr->type;
|
||||
switch (t) {
|
||||
case VIRTIO_GPU_CMD_GET_NUM_SCANOUTS:
|
||||
if (cb.resp && cb.resp_len >= (u32)sizeof(struct virtio_gpu_resp_num_scanouts)) {
|
||||
struct virtio_gpu_resp_num_scanouts *rn =
|
||||
(struct virtio_gpu_resp_num_scanouts *)cb.resp;
|
||||
rn->hdr.type = VIRTIO_GPU_RESP_OK_NODATA;
|
||||
rn->hdr.flags = 0; rn->hdr.fence_id = 0;
|
||||
rn->hdr.ctx_id = 0; rn->hdr.padding = 0;
|
||||
rn->num_scanouts = 1;
|
||||
rn->padding = 0;
|
||||
resp_type = VIRTIO_GPU_RESP_OK_NODATA;
|
||||
resp_len = (u32)sizeof(struct virtio_gpu_resp_num_scanouts);
|
||||
} else {
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_GET_DISPLAY_INFO:
|
||||
resp_type = handle_get_display_info(cb.resp, cb.resp_len);
|
||||
if (resp_type == VIRTIO_GPU_RESP_OK_DISPLAY_INFO)
|
||||
@@ -604,8 +658,7 @@ static void gpu_ctrlq_process(void) {
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_RESOURCE_DESTROY: {
|
||||
/* DESTROY has just hdr + resource_id after */
|
||||
case VIRTIO_GPU_CMD_RESOURCE_UNREF: {
|
||||
u32 rid = 0;
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_ctrl_hdr) + 4)
|
||||
rid = *(u32 *)((u8 *)cb.cmd + sizeof(struct virtio_gpu_ctrl_hdr));
|
||||
@@ -615,7 +668,8 @@ static void gpu_ctrlq_process(void) {
|
||||
case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_resource_attach_backing))
|
||||
resp_type = handle_attach_backing(
|
||||
(struct virtio_gpu_resource_attach_backing *)cb.cmd, cb.cmd_len);
|
||||
(struct virtio_gpu_resource_attach_backing *)cb.cmd,
|
||||
cb.cmd_len, cb.cmd_extra, cb.cmd_extra_len);
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
@@ -647,24 +701,35 @@ static void gpu_ctrlq_process(void) {
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
default:
|
||||
/* unknown / 3D / cursor commands on ctrlq: ack NODATA */
|
||||
case VIRTIO_GPU_CMD_GET_CAPSET_INFO:
|
||||
case VIRTIO_GPU_CMD_GET_CAPSET:
|
||||
case VIRTIO_GPU_CMD_GET_EDID:
|
||||
case VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID:
|
||||
case VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB:
|
||||
case VIRTIO_GPU_CMD_SET_SCANOUT_BLOB:
|
||||
/* No virgl/blob/EDID features advertised; reject cleanly. */
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC;
|
||||
break;
|
||||
default: {
|
||||
static u32 last_unknown;
|
||||
if (t != last_unknown) {
|
||||
last_unknown = t;
|
||||
log_warn("[VGPU] unknown ctrl cmd");
|
||||
log_hex64(" type=", t);
|
||||
}
|
||||
resp_type = VIRTIO_GPU_RESP_OK_NODATA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* write the response (unless GET_* already wrote a richer response) */
|
||||
if (resp_type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO &&
|
||||
resp_type != VIRTIO_GPU_RESP_OK_NODATA) {
|
||||
/* error: write NODATA with error type if we haven't already */
|
||||
if (hdr->type != VIRTIO_GPU_CMD_GET_NUM_SCANOUTS)
|
||||
write_resp_nodata(cb.resp, cb.resp_len, hdr->type, resp_type);
|
||||
write_resp_nodata(cb.resp, cb.resp_len, hdr->type, resp_type);
|
||||
resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr);
|
||||
} else if (resp_type == VIRTIO_GPU_RESP_OK_NODATA &&
|
||||
hdr->type != VIRTIO_GPU_CMD_GET_NUM_SCANOUTS &&
|
||||
hdr->type != VIRTIO_GPU_CMD_GET_DISPLAY_INFO) {
|
||||
/* plain NODATA success: write header */
|
||||
write_resp_nodata(cb.resp, cb.resp_len, hdr->type, resp_type);
|
||||
resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr);
|
||||
}
|
||||
@@ -825,6 +890,7 @@ static void gpu_reset(void) {
|
||||
g_gpu.cursor_w = 0; g_gpu.cursor_h = 0;
|
||||
g_gpu.last_avail_ctrl = 0;
|
||||
g_gpu.last_avail_cursor = 0;
|
||||
g_gpu.flush_count = 0;
|
||||
for (int i = 0; i < VGPU_MAX_RESOURCES; i++) res_release(&g_gpu.res[i]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
vmm/virtio_gpu.o: vmm\virtio_gpu.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
@@ -1,6 +0,0 @@
|
||||
vmm/virtio_input.o: vmm\virtio_input.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/dma.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
@@ -52,6 +53,23 @@ static int g_vio_ready;
|
||||
|
||||
void *virtio_gpa_to_host(u64 gpa) {
|
||||
u64 hpa = ept_gpa_to_hpa(gpa);
|
||||
/* Guest CPU stores fault in and demand-map a page. virtio-blk READ
|
||||
* buffers are often allocated but never stored by the vCPU first, so
|
||||
* the EPT walk misses and the device must map the page itself.
|
||||
* Skip GPA 0 (null) and MMIO (>= 0xF0000000). */
|
||||
if (hpa == 0 && gpa >= 0x1000ULL && gpa < 0xF0000000ULL) {
|
||||
u64 page = gpa & ~(EPT_PAGE_SIZE - 1);
|
||||
static u32 demand_logs;
|
||||
dkm_dma_buffer buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0, &buf) == 0 &&
|
||||
ept_map_range(page, buf.phys, EPT_PAGE_SIZE, EPT_RWX) == 0) {
|
||||
hpa = buf.phys + (gpa & (EPT_PAGE_SIZE - 1));
|
||||
if (demand_logs < 8) {
|
||||
demand_logs++;
|
||||
log_hex64("[VIO] demand-map gpa=", gpa);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hpa == 0) return (void *)0;
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
return (void *)(hhdm + hpa);
|
||||
@@ -127,6 +145,13 @@ u64 virtio_mmio_read(u64 gpa, int width) {
|
||||
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_SHM_LEN_LOW:
|
||||
case VIRTIO_MMIO_SHM_LEN_HIGH:
|
||||
/* No SHM windows. Linux treats (u64)-1 as "region absent". */
|
||||
return 0xFFFFFFFFu;
|
||||
case VIRTIO_MMIO_SHM_BASE_LOW:
|
||||
case VIRTIO_MMIO_SHM_BASE_HIGH:
|
||||
return 0;
|
||||
case VIRTIO_MMIO_CONFIG_GENERATION:return s->config_generation;
|
||||
default:
|
||||
/* device config 空间(0x100 起) */
|
||||
@@ -155,6 +180,9 @@ void virtio_mmio_write(u64 gpa, u64 value, int width) {
|
||||
case VIRTIO_MMIO_DEVICE_FEATURES_SEL:
|
||||
s->dev_features_sel = v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_SHM_SEL:
|
||||
/* No SHM regions; selector is ignored. Length reads stay -1. */
|
||||
break;
|
||||
case VIRTIO_MMIO_DRIVER_FEATURES:
|
||||
if (s->drv_features_sel == 0) {
|
||||
s->driver_features = (s->driver_features & 0xFFFFFFFF00000000ULL) | v32;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
vmm/virtio_mmio.o: vmm\virtio_mmio.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\ept.h \
|
||||
include\utsm\vmx.h include\utsm\log.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -1,8 +0,0 @@
|
||||
vmm/virtio_net.o: vmm\virtio_net.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\net.h \
|
||||
include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\net.h:
|
||||
include\utsm\log.h:
|
||||
+904
-53
@@ -7,6 +7,7 @@
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/linux_xsave.h>
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/instr.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
@@ -27,6 +28,8 @@
|
||||
*/
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
extern void outb(u16 port, u8 value);
|
||||
extern u8 inb(u16 port);
|
||||
|
||||
extern void vmx_vmresume_wrapper(void);
|
||||
|
||||
@@ -36,6 +39,10 @@ extern u64 serial_tsc_per_ms(void);
|
||||
extern void serial_putc(char c);
|
||||
|
||||
static u64 g_vmexit_count;
|
||||
static u64 g_linux_tracked_cr3;
|
||||
#define CR3_RING 16
|
||||
static u64 g_cr3_ring[CR3_RING];
|
||||
static u32 g_cr3_ring_n;
|
||||
|
||||
/* ---- 插桩: VM-Exit 统计计数器 (热路径,仅 STAT_INC) ---- */
|
||||
INSTR_STAT_DECL(vmexit_total);
|
||||
@@ -57,15 +64,15 @@ static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len
|
||||
|
||||
/* 处理 HLT:
|
||||
* - self-test guest:终止(guest 完成测试)
|
||||
* - Linux guest:park(推进 RIP 越过 HLT,exit-to-host,设 g_guest_parked=1)
|
||||
* host 调用 linux_resume() 时 vmresume 唤醒 guest 从 HLT 之后继续执行。 */
|
||||
* - Linux guest idle:推进 RIP 并留在 guest,等 PIT/virtio IRQ。
|
||||
* daemon PARK 走 UTSM_HCALL_PARK,不再用 HLT(idle=poll 会在
|
||||
* cpu_idle_poll 里把 current 读成 NULL 并 panic)。 */
|
||||
static int handle_hlt(u64 rip, u64 instr_len, int *out_resume) {
|
||||
*out_resume = 0; /* 三种情况默认 exit-to-host */
|
||||
if (g_linux_guest_active) {
|
||||
/* Linux guest: park — 推进 RIP 越过 HLT 指令 */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
g_guest_parked = 1;
|
||||
log_info("[VMEXIT] HLT - Linux guest parked");
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
} else if (g_xj380_guest_active) {
|
||||
/* OpenXJ380 guest: 推进 RIP 并立即继续执行(不 park 不 terminate) */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
@@ -118,9 +125,15 @@ static int g_pending_head, g_pending_tail;
|
||||
|
||||
/* guest 8259 PIC 状态(master) */
|
||||
static u8 g_pic_imr = 0xFF; /* 初始全 mask */
|
||||
static u8 g_pic_vec_base = 0x30; /* ICW2;x86_64 Linux ISA 向量基址 */
|
||||
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;
|
||||
static u32 g_irq_skip_logs;
|
||||
static u32 g_irq_inject_logs;
|
||||
|
||||
#define GUEST_INTR_STI (1ULL << 0)
|
||||
#define GUEST_INTR_MOVSS (1ULL << 1)
|
||||
|
||||
void vmx_guest_queue_irq(u32 vector) {
|
||||
/* 合并连续相同向量(PIT tick 合并 / COM1 RX 去重) */
|
||||
@@ -139,11 +152,17 @@ static void maybe_inject_irq(void) {
|
||||
|
||||
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;
|
||||
/* IMR:vector = ICW2 基址 + irq。基址未写入前按 0x30。 */
|
||||
if (vec >= g_pic_vec_base && vec < (u8)(g_pic_vec_base + 8)) {
|
||||
if (g_pic_imr & (1u << (vec - g_pic_vec_base))) {
|
||||
/* Keep the IRQ queued. virtio-mmio is edge-like: dropping it
|
||||
* here loses the completion forever if the guest parked while
|
||||
* the line was still masked. */
|
||||
if (g_irq_skip_logs < 8) {
|
||||
g_irq_skip_logs++;
|
||||
log_hex64("[IRQ] masked imr=", g_pic_imr);
|
||||
log_hex64("[IRQ] masked vec=", vec);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -152,8 +171,10 @@ static void maybe_inject_irq(void) {
|
||||
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 */
|
||||
/* External IRQ may be injected while virtual-NMI blocking is set.
|
||||
* Only STI-shadow and MOV-SS-shadow must delay delivery. Requiring
|
||||
* interruptibility==0 froze PIT after park: nanosleep never woke. */
|
||||
if ((rflags & 0x200) && !(intr_st & (GUEST_INTR_STI | GUEST_INTR_MOVSS))) {
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_INTERRUPT_INFO,
|
||||
VM_ENTRY_INTR_INFO_VALID |
|
||||
(VM_ENTRY_INTR_TYPE_HW_IRQ << 8) | vec);
|
||||
@@ -162,8 +183,18 @@ static void maybe_inject_irq(void) {
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
|
||||
cpu & ~CPU_BASED_INTR_WINDOW_EXITING);
|
||||
}
|
||||
if (g_irq_inject_logs < 4) {
|
||||
g_irq_inject_logs++;
|
||||
log_hex64("[IRQ] inject vec=", vec);
|
||||
log_hex64("[IRQ] inject base=", g_pic_vec_base);
|
||||
log_hex64("[IRQ] inject intr=", intr_st);
|
||||
}
|
||||
} else {
|
||||
/* 暂不可注入:arm interrupt-window exiting */
|
||||
if (g_irq_skip_logs < 8) {
|
||||
g_irq_skip_logs++;
|
||||
log_hex64("[IRQ] window if/sti rflags=", rflags);
|
||||
log_hex64("[IRQ] window intr=", intr_st);
|
||||
}
|
||||
if (!(cpu & CPU_BASED_INTR_WINDOW_EXITING)) {
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
|
||||
cpu | CPU_BASED_INTR_WINDOW_EXITING);
|
||||
@@ -216,7 +247,17 @@ static u64 com1_io(u32 port, int is_in, u64 value) {
|
||||
switch (reg) {
|
||||
case 0:
|
||||
if (dlab) g_com1_dll = (u8)value;
|
||||
else serial_putc((char)(value & 0xFF)); /* guest → host 串口 */
|
||||
else {
|
||||
/* Direct host UART write. serial_putc() waits on the same
|
||||
* COM1 LSR and can stall guest printk under file-backed serial. */
|
||||
char c = (char)(value & 0xFF);
|
||||
if (c == '\n') {
|
||||
for (int i = 0; i < 10000 && !(inb(0x3F8 + 5) & 0x20); i++) {}
|
||||
outb(0x3F8, '\r');
|
||||
}
|
||||
for (int i = 0; i < 10000 && !(inb(0x3F8 + 5) & 0x20); i++) {}
|
||||
outb(0x3F8, (u8)c);
|
||||
}
|
||||
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;
|
||||
@@ -293,7 +334,10 @@ static u64 misc_io(u32 port, int is_in, u64 value, int *out_terminate) {
|
||||
if (port == 0x21) {
|
||||
if (is_in) return g_pic_imr;
|
||||
switch (g_pic_icw_state) {
|
||||
case 1: /* ICW2(向量基址,忽略,固定 0x30) */
|
||||
case 1: /* ICW2 向量基址(低 3 位忽略) */
|
||||
g_pic_vec_base = (u8)value & 0xF8u;
|
||||
if (g_pic_vec_base == 0)
|
||||
g_pic_vec_base = 0x30;
|
||||
g_pic_icw_state = g_pic_icw_sngl ? (g_pic_icw_need4 ? 3 : 0) : 2;
|
||||
break;
|
||||
case 2: /* ICW3 */
|
||||
@@ -304,6 +348,7 @@ static u64 misc_io(u32 port, int is_in, u64 value, int *out_terminate) {
|
||||
break;
|
||||
default: /* OCW1 = IMR */
|
||||
g_pic_imr = (u8)value;
|
||||
maybe_inject_irq();
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
@@ -314,6 +359,18 @@ static u64 misc_io(u32 port, int is_in, u64 value, int *out_terminate) {
|
||||
/* 8254 PIT */
|
||||
if (port >= 0x40 && port <= 0x43) return pit_io(port, is_in, value);
|
||||
|
||||
/* Port 0x61: NMI/speaker/PIT2. Linux pit_hpet_ptimer_calibrate_cpu
|
||||
* waits for bit5 (PIT ch2 OUT). A constant 0 livelocks that loop. */
|
||||
if (port == 0x61) {
|
||||
static u8 port61_gate;
|
||||
if (is_in) {
|
||||
port61_gate ^= 0x10; /* bit4 refresh toggle */
|
||||
return (u8)((port61_gate & 0x13) | 0x20); /* bit5 = PIT2 OUT */
|
||||
}
|
||||
port61_gate = (u8)((port61_gate & ~0x03) | (value & 0x03));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* PS/2 键盘/鼠标(i8042):status 读 0(空),data 读 0,写吞掉。
|
||||
* i8042 自检将失败 → guest 无 PS/2 键鼠(console 输入走串口)。 */
|
||||
if (port == 0x60 || port == 0x64) return is_in ? 0 : 0;
|
||||
@@ -392,6 +449,14 @@ static int handle_io(u64 qualification, u64 rip, u64 instr_len, int *out_resume)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Nested KVM sometimes reports VM-exit instruction length 0 for IN/OUT.
|
||||
* Adding 0 leaves RIP on the same opcode and livelocks extract_kernel
|
||||
* earlyprintk (millions of exits at one RIP). Immediate-port encodings
|
||||
* are 2 bytes; DX-port encodings are 1. */
|
||||
if (instr_len == 0) {
|
||||
instr_len = ((qualification >> 6) & 1) ? 2 : 1;
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
@@ -417,8 +482,48 @@ static int handle_cpuid(u64 rip, u64 instr_len, int *out_resume) {
|
||||
|
||||
if (a == 1) {
|
||||
oc &= ~(1u << 5); /* VMX */
|
||||
oc &= ~(1u << 17); /* PCID: keep Linux on MOV-CR3 TLB flush */
|
||||
oc &= ~(1u << 12); /* FMA: requires AVX */
|
||||
oc &= ~(1u << 29); /* F16C */
|
||||
oc &= ~(1u << 31); /* hypervisor present */
|
||||
}
|
||||
linux_xsave_adjust_cpuid(a, c, &oa, &ob, &oc, &od,
|
||||
vmx_vmcs_read(VMCS_GUEST_CR4));
|
||||
if (a == 7 && c != 0) {
|
||||
/* Hide CET_SSS and other 7.1+ bits; Linux 6.6 probes subleaf 1. */
|
||||
oa = 0;
|
||||
ob = 0;
|
||||
oc = 0;
|
||||
od = 0;
|
||||
}
|
||||
if (a == 7 && c == 0) {
|
||||
/* Nested KVM: these insns #UD in the guest even when host CPUID
|
||||
* advertises them. INVPCID is used by __flush_tlb_all. */
|
||||
ob &= ~(1u << 0); /* FSGSBASE */
|
||||
ob &= ~(1u << 5); /* AVX2: keep IFUNC on SSE until YMM save is on */
|
||||
ob &= ~(1u << 10); /* INVPCID */
|
||||
ob &= ~(1u << 16); /* AVX512F */
|
||||
ob &= ~(1u << 17); /* AVX512DQ */
|
||||
ob &= ~(1u << 21); /* AVX512IFMA */
|
||||
ob &= ~(1u << 26); /* AVX512PF */
|
||||
ob &= ~(1u << 27); /* AVX512ER */
|
||||
ob &= ~(1u << 28); /* AVX512CD */
|
||||
ob &= ~(1u << 30); /* AVX512BW */
|
||||
ob &= ~(1u << 31); /* AVX512VL */
|
||||
oc &= ~(1u << 1); /* AVX512VBMI */
|
||||
oc &= ~(1u << 5); /* WAITPKG / TPAUSE */
|
||||
oc &= ~(1u << 6); /* AVX512VBMI2 */
|
||||
oc &= ~(1u << 7); /* CET_SS: SSP=0 makes PUSH fault at address 0 */
|
||||
oc &= ~(1u << 11); /* AVX512VNNI */
|
||||
oc &= ~(1u << 12); /* AVX512BITALG */
|
||||
oc &= ~(1u << 14); /* AVX512VPOPCNTDQ */
|
||||
oc &= ~(1u << 16); /* LA57 */
|
||||
od &= ~(1u << 2); /* AVX5124VNNIW */
|
||||
od &= ~(1u << 3); /* AVX5124FMAPS */
|
||||
od &= ~(1u << 8); /* AVX512VP2INTERSECT */
|
||||
od &= ~(1u << 20); /* CET_IBT */
|
||||
od &= ~(1u << 23); /* AVX512FP16 */
|
||||
}
|
||||
/* leaf 0x15/0x16 passthrough:QEMU 提供 TSC/总线频率,
|
||||
* guest 据此校准 TSC(绕过 PIT ch2 校准路径)。 */
|
||||
|
||||
@@ -462,23 +567,50 @@ static void pit_tick_update(void) {
|
||||
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(自动合并) */
|
||||
vmx_guest_queue_irq(g_pic_vec_base); /* PIT = IRQ0 */
|
||||
n++;
|
||||
}
|
||||
if (g_pit_accum >= g_pit_period_tsc) g_pit_accum = 0; /* 防积压 */
|
||||
}
|
||||
|
||||
static u64 g_preemption_quantum = 0;
|
||||
#define VMX_PREEMPTION_INTERVAL_MS 10ULL
|
||||
/* ~80ms of guest time, then return to DSK so blit/Esc can run without
|
||||
* requiring the exec daemon to HLT (HLT freezes virtio completions). */
|
||||
#define LINUX_HOST_SLICE_TICKS 8u
|
||||
static unsigned g_linux_slice_ticks;
|
||||
static int g_linux_timeslice_armed;
|
||||
static void vmexit_before_resume(void);
|
||||
|
||||
u64 vmx_preemption_quantum_1ms(void) {
|
||||
void vmx_linux_timeslice_reset(void) {
|
||||
g_linux_slice_ticks = 0;
|
||||
g_linux_timeslice_armed = 1;
|
||||
}
|
||||
|
||||
void vmx_linux_timeslice_disarm(void) {
|
||||
g_linux_timeslice_armed = 0;
|
||||
}
|
||||
|
||||
void vmx_linux_prepare_entry(void) {
|
||||
vmexit_before_resume();
|
||||
}
|
||||
|
||||
u64 vmx_preemption_quantum(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;
|
||||
/* IA32_VMX_MISC[4:0] is the VMX-preemption-timer rate. The timer
|
||||
* decrements once per 2^rate TSC ticks; IA32_VMX_BASIC[55:48] is not
|
||||
* this rate (it contains unrelated VMX-basic capabilities). */
|
||||
u64 misc = vmx_read_msr(IA32_VMX_MISC);
|
||||
u64 rate = misc & 0x1F;
|
||||
u64 tpm = serial_tsc_per_ms();
|
||||
if (tpm == 0) tpm = 2000000;
|
||||
g_preemption_quantum = tpm / scale;
|
||||
g_preemption_quantum =
|
||||
(tpm * VMX_PREEMPTION_INTERVAL_MS) >> rate;
|
||||
if (g_preemption_quantum == 0) g_preemption_quantum = 1;
|
||||
log_hex64("[VMX] preemption timer rate=", rate);
|
||||
log_hex64("[VMX] preemption timer interval_ms=",
|
||||
VMX_PREEMPTION_INTERVAL_MS);
|
||||
log_hex64("[VMX] preemption timer quantum=", g_preemption_quantum);
|
||||
return g_preemption_quantum;
|
||||
}
|
||||
|
||||
@@ -492,9 +624,30 @@ int vmx_preemption_timer_supported(void) {
|
||||
* 推进 PIT tick,注入 pending IRQ。
|
||||
* 同时轮询 virtio-net RX:guest 投满 RX buffer 后不再 notify,
|
||||
* host 网卡收到的包靠这里周期性填充并注入 IRQ6。 */
|
||||
static void linux_clear_guest_cet(void) {
|
||||
u64 cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
if (cr4 & CR4_CET) {
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, cr4 & ~CR4_CET);
|
||||
}
|
||||
}
|
||||
|
||||
static u64 g_pf_cr2;
|
||||
static int g_pf_need_cr2;
|
||||
|
||||
static void write_cr2_local(u64 v) {
|
||||
__asm__ volatile("mov %0, %%cr2" :: "r"(v));
|
||||
}
|
||||
|
||||
static void vmexit_before_resume(void) {
|
||||
if (!g_linux_guest_active) return;
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
if (g_pf_need_cr2) {
|
||||
write_cr2_local(g_pf_cr2);
|
||||
g_pf_need_cr2 = 0;
|
||||
}
|
||||
linux_clear_guest_cet();
|
||||
linux_cet_vmcs_sync_host();
|
||||
linux_cet_force_guest_off();
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum());
|
||||
poll_host_serial_to_guest();
|
||||
virtio_net_poll();
|
||||
virtio_input_poll(); /* VSCode Phase 2: drain pending kbd/mouse events */
|
||||
@@ -518,17 +671,158 @@ static void vmexit_before_resume(void) {
|
||||
* 用 GVA=EPT GPA 简化:guest 采用恒等页表,GUEST_LINEAR_ADDR 即 GPA。
|
||||
*/
|
||||
|
||||
/* 从 guest 内存读指令字节(GPA=GVA 恒等映射,经 EPT 翻译到 HPA)。 */
|
||||
static int guest_fetch(u64 gva, u8 *buf, u64 len) {
|
||||
/* Guest GVA → GPA via a CR3 value (GPA of the PGD). Linux PTI uses two
|
||||
* adjacent 4K PGDs; user and kernel CR3 differ by bit 12. */
|
||||
static u64 guest_walk_cr3_at(u64 gva, u64 cr3, int five_level) {
|
||||
int first = five_level ? 0 : 1;
|
||||
int shifts[5] = { 48, 39, 30, 21, 12 };
|
||||
u64 table_gpa = cr3 & 0x000FFFFFFFFFF000ULL;
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
if (!hhdm || !table_gpa) return 0;
|
||||
|
||||
for (int level = first; level < 5; level++) {
|
||||
u64 table_hpa = ept_gpa_to_hpa(table_gpa);
|
||||
if (!table_hpa) return 0;
|
||||
u64 *table = (u64 *)(hhdm + table_hpa);
|
||||
u64 entry = table[(gva >> shifts[level]) & 0x1FF];
|
||||
if (!(entry & 1)) return 0;
|
||||
if (shifts[level] == 30 && (entry & (1ULL << 7))) {
|
||||
return (entry & 0x000FFFFFC0000000ULL) + (gva & 0x3FFFFFFFULL);
|
||||
}
|
||||
if (shifts[level] == 21 && (entry & (1ULL << 7))) {
|
||||
return (entry & 0x000FFFFFFFE00000ULL) + (gva & 0x1FFFFFULL);
|
||||
}
|
||||
table_gpa = entry & 0x000FFFFFFFFFF000ULL;
|
||||
}
|
||||
return table_gpa + (gva & 0xFFFULL);
|
||||
}
|
||||
|
||||
static u64 guest_current_cr3(void) {
|
||||
if (g_linux_tracked_cr3)
|
||||
return g_linux_tracked_cr3;
|
||||
return vmx_vmcs_read(VMCS_GUEST_CR3);
|
||||
}
|
||||
|
||||
static int bytes_nonzero(const u8 *buf, u64 len) {
|
||||
u64 i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (buf[i]) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void track_guest_cr3(u64 val) {
|
||||
u32 i;
|
||||
g_linux_tracked_cr3 = val;
|
||||
vmx_vmcs_write(VMCS_GUEST_CR3, val);
|
||||
for (i = 0; i < g_cr3_ring_n && i < CR3_RING; i++) {
|
||||
if (g_cr3_ring[i] == val)
|
||||
return;
|
||||
}
|
||||
if (g_cr3_ring_n < CR3_RING) {
|
||||
g_cr3_ring[g_cr3_ring_n++] = val;
|
||||
} else {
|
||||
for (i = 1; i < CR3_RING; i++)
|
||||
g_cr3_ring[i - 1] = g_cr3_ring[i];
|
||||
g_cr3_ring[CR3_RING - 1] = val;
|
||||
}
|
||||
/* MOV CR3 did not execute; flush the guest VPID so L2 does not keep
|
||||
* user translations from the previous mm. */
|
||||
ept_flush_vpid(2);
|
||||
}
|
||||
|
||||
static u64 guest_walk_cr3(u64 gva, int five_level) {
|
||||
u64 cr3 = guest_current_cr3();
|
||||
u64 gpa = guest_walk_cr3_at(gva, cr3, five_level);
|
||||
if (gpa) return gpa;
|
||||
/* PTI: the other PGD is the adjacent 4K page. */
|
||||
return guest_walk_cr3_at(gva, cr3 ^ 0x1000ULL, five_level);
|
||||
}
|
||||
|
||||
u64 vmx_guest_gva_to_gpa(u64 gva) {
|
||||
u64 cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
int la57 = (cr4 & (1ULL << 12)) ? 1 : 0;
|
||||
u64 gpa = guest_walk_cr3(gva, la57);
|
||||
if (gpa) return gpa;
|
||||
/* Linux 6.x L4 direct map. Kernel stacks live here after paging_init. */
|
||||
if (gva >= 0xFFFF888000000000ULL && gva < 0xFFFFC88000000000ULL) {
|
||||
gpa = gva - 0xFFFF888000000000ULL;
|
||||
if (ept_gpa_to_hpa(gpa)) return gpa;
|
||||
}
|
||||
/* Retry the other paging mode if CR4.LA57 does not match the tables. */
|
||||
gpa = guest_walk_cr3(gva, !la57);
|
||||
if (gpa) return gpa;
|
||||
if (ept_gpa_to_hpa(gva)) return gva;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 从 guest 内存读指令字节(GVA→GPA→HPA)。
|
||||
* 若当前 CR3 走到全 0 页(PTI 走错 PGD / zeropage),改走 CR3^0x1000。 */
|
||||
static int guest_fetch_at_cr3(u64 gva, u8 *buf, u64 len, u64 cr3) {
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
u64 cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
int la57 = (cr4 & (1ULL << 12)) ? 1 : 0;
|
||||
if (!hhdm) return -1;
|
||||
for (u64 i = 0; i < len; i++) {
|
||||
u64 hpa = ept_gpa_to_hpa(gva + i);
|
||||
u64 gpa = guest_walk_cr3_at(gva + i, cr3, la57);
|
||||
if (!gpa) gpa = guest_walk_cr3_at(gva + i, cr3, !la57);
|
||||
u64 hpa = gpa ? ept_gpa_to_hpa(gpa) : 0;
|
||||
if (hpa == 0) return -1;
|
||||
buf[i] = *(volatile u8 *)(hhdm + hpa);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int guest_fetch(u64 gva, u8 *buf, u64 len) {
|
||||
u64 cr3s[CR3_RING + 4];
|
||||
u32 n = 0, i, j;
|
||||
u64 cur = guest_current_cr3();
|
||||
u64 vmcs = vmx_vmcs_read(VMCS_GUEST_CR3);
|
||||
|
||||
cr3s[n++] = cur;
|
||||
if (vmcs != cur)
|
||||
cr3s[n++] = vmcs;
|
||||
cr3s[n++] = cur ^ 0x1000ULL;
|
||||
for (i = g_cr3_ring_n; i > 0; i--) {
|
||||
int dup = 0;
|
||||
u64 c = g_cr3_ring[i - 1];
|
||||
for (j = 0; j < n; j++) {
|
||||
if (cr3s[j] == c) {
|
||||
dup = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup)
|
||||
cr3s[n++] = c;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
if (guest_fetch_at_cr3(gva, buf, len, cr3s[i]) != 0)
|
||||
continue;
|
||||
if (bytes_nonzero(buf, len))
|
||||
return 0;
|
||||
}
|
||||
/* Low identity window: PTI walk can hit the zeropage while the
|
||||
* CPU still executes the mapped ELF (busybox at 0x400000). */
|
||||
if (gva < 0x04000000ULL && len > 0) {
|
||||
u64 hhdm = g_hhdm_request.response ?
|
||||
g_hhdm_request.response->offset : 0;
|
||||
if (hhdm) {
|
||||
int ok = 1;
|
||||
for (i = 0; i < len; i++) {
|
||||
u64 hpa = ept_gpa_to_hpa(gva + i);
|
||||
if (!hpa) {
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
buf[i] = *(volatile u8 *)(hhdm + hpa);
|
||||
}
|
||||
if (ok && bytes_nonzero(buf, len))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 解码 MMIO 访问指令。
|
||||
* rip_gpa : guest RIP(GPA,恒等映射)
|
||||
* is_write_out : 输出 1=写 MMIO,0=读 MMIO
|
||||
@@ -560,34 +854,44 @@ static u64 decode_mmio_insn(u64 rip_gpa, int *is_write_out, int *width_out,
|
||||
}
|
||||
|
||||
u8 op = insn[off];
|
||||
u8 modrm = insn[off + 1];
|
||||
u64 payload = off + 1;
|
||||
if (op == 0x0F) payload = off + 2;
|
||||
|
||||
u8 modrm = insn[payload];
|
||||
int mod = (modrm >> 6) & 3;
|
||||
int rm = modrm & 7;
|
||||
int reg = ((modrm >> 3) & 7) | (rex_r << 3);
|
||||
u64 n = payload + 1; /* opcode(s) + ModRM */
|
||||
if (mod != 3 && rm == 4) {
|
||||
u8 sib = insn[n];
|
||||
n += 1;
|
||||
if (mod == 0 && (sib & 7) == 5) n += 4;
|
||||
}
|
||||
if (mod == 1) n += 1;
|
||||
else if (mod == 2) n += 4;
|
||||
else if (mod == 0 && rm == 5) n += 4;
|
||||
|
||||
/* 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) */
|
||||
return n;
|
||||
}
|
||||
/* 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;
|
||||
return n;
|
||||
}
|
||||
/* 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;
|
||||
*reg_field_out = reg;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 无法解码(如带 SIB/displacement 的复杂寻址)。
|
||||
* virtio-mmio 驱动用简单寄存器访问,暂不支持则返回 0。 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -614,6 +918,171 @@ static u64 *gpr_ptr(int idx) {
|
||||
return (u64 *)&g_guest_regs.rax;
|
||||
}
|
||||
|
||||
static u64 read_gpr_full(int idx) {
|
||||
if ((idx & 15) == 4)
|
||||
return vmx_vmcs_read(VMCS_GUEST_RSP);
|
||||
return *gpr_ptr(idx);
|
||||
}
|
||||
|
||||
static void write_gpr_full(int idx, u64 v) {
|
||||
if ((idx & 15) == 4)
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, v);
|
||||
else
|
||||
*gpr_ptr(idx) = v;
|
||||
}
|
||||
|
||||
static int guest_write_u64(u64 gva, u64 value) {
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
u8 bytes[8];
|
||||
u64 i;
|
||||
if (!hhdm) return -1;
|
||||
for (i = 0; i < 8; i++)
|
||||
bytes[i] = (u8)((value >> (i * 8)) & 0xFF);
|
||||
for (i = 0; i < 8; i++) {
|
||||
u64 gpa = vmx_guest_gva_to_gpa(gva + i);
|
||||
u64 hpa = gpa ? ept_gpa_to_hpa(gpa) : 0;
|
||||
if (!hpa && gpa) {
|
||||
ept_map_guest_ram_page(gpa & ~(EPT_PAGE_SIZE - 1));
|
||||
hpa = ept_gpa_to_hpa(gpa);
|
||||
}
|
||||
if (!hpa) return -1;
|
||||
*(volatile u8 *)(hhdm + hpa) = bytes[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int guest_read_u64(u64 gva, u64 *out) {
|
||||
u8 bytes[8];
|
||||
u64 i, v = 0;
|
||||
if (guest_fetch(gva, bytes, 8) != 0) return -1;
|
||||
for (i = 0; i < 8; i++)
|
||||
v |= ((u64)bytes[i]) << (i * 8);
|
||||
*out = v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int guest_is_user_rip(u64 rip) {
|
||||
return (rip & (1ULL << 63)) == 0;
|
||||
}
|
||||
|
||||
static u64 read_cr2_local(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr2, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Nested KVM leaks host SHSTK: CALL/RET/PUSH fault at SSP=0 (GVA 0).
|
||||
* Complete the data-stack side and skip the shadow-stack store. */
|
||||
static int cet_emulate_shstk_insn(u64 rip, u64 *ilen_hint) {
|
||||
u8 insn[16];
|
||||
u64 off = 0;
|
||||
int rex_b = 0;
|
||||
u64 rsp, next;
|
||||
static u32 emu_logs;
|
||||
|
||||
if (guest_fetch(rip, insn, sizeof(insn)) != 0)
|
||||
return -1;
|
||||
if (insn[0] == 0 && insn[1] == 0 && insn[2] == 0 && insn[3] == 0)
|
||||
return -1;
|
||||
|
||||
while (off < 4 && ((insn[off] >= 0x40 && insn[off] <= 0x4F))) {
|
||||
rex_b = insn[off] & 1;
|
||||
off++;
|
||||
}
|
||||
if (off >= sizeof(insn)) return -1;
|
||||
|
||||
rsp = vmx_vmcs_read(VMCS_GUEST_RSP);
|
||||
|
||||
/* PUSH r64: 50+rd */
|
||||
if (insn[off] >= 0x50 && insn[off] <= 0x57) {
|
||||
int rd = (insn[off] - 0x50) | (rex_b << 3);
|
||||
u64 val = read_gpr_full(rd);
|
||||
rsp -= 8;
|
||||
if (guest_write_u64(rsp, val) != 0) return -1;
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, rsp);
|
||||
next = rip + off + 1;
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, next);
|
||||
if (emu_logs < 12) {
|
||||
log_hex64("[VMEXIT] CET emulate PUSH r rip=", rip);
|
||||
emu_logs++;
|
||||
}
|
||||
*ilen_hint = off + 1;
|
||||
return 0;
|
||||
}
|
||||
/* POP r64: 58+rd — data stack only */
|
||||
if (insn[off] >= 0x58 && insn[off] <= 0x5F) {
|
||||
int rd = (insn[off] - 0x58) | (rex_b << 3);
|
||||
u64 val;
|
||||
if (guest_read_u64(rsp, &val) != 0) return -1;
|
||||
write_gpr_full(rd, val);
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, rsp + 8);
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + off + 1);
|
||||
if (emu_logs < 12) {
|
||||
log_hex64("[VMEXIT] CET emulate POP r rip=", rip);
|
||||
emu_logs++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* CALL rel32 */
|
||||
if (insn[off] == 0xE8) {
|
||||
u32 rel = (u32)insn[off + 1] | ((u32)insn[off + 2] << 8) |
|
||||
((u32)insn[off + 3] << 16) | ((u32)insn[off + 4] << 24);
|
||||
next = rip + off + 5;
|
||||
rsp -= 8;
|
||||
if (guest_write_u64(rsp, next) != 0) return -1;
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, rsp);
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, next + (u64)(int)rel);
|
||||
if (emu_logs < 12) {
|
||||
log_hex64("[VMEXIT] CET emulate CALL rel32 rip=", rip);
|
||||
emu_logs++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* RET */
|
||||
if (insn[off] == 0xC3) {
|
||||
u64 ret;
|
||||
if (guest_read_u64(rsp, &ret) != 0) return -1;
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, rsp + 8);
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, ret);
|
||||
if (emu_logs < 12) {
|
||||
log_hex64("[VMEXIT] CET emulate RET rip=", rip);
|
||||
emu_logs++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* CALL/PUSH/JMP r/m64: FF /2, /6, /4 with ModRM.mod==3 */
|
||||
if (insn[off] == 0xFF) {
|
||||
u8 modrm = insn[off + 1];
|
||||
int mod = (modrm >> 6) & 3;
|
||||
int reg = (modrm >> 3) & 7;
|
||||
int rm = (modrm & 7) | (rex_b << 3);
|
||||
if (mod == 3 && (reg == 2 || reg == 6)) {
|
||||
u64 tgt = read_gpr_full(rm);
|
||||
next = rip + off + 2;
|
||||
rsp -= 8;
|
||||
if (guest_write_u64(rsp, next) != 0) return -1;
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, rsp);
|
||||
if (reg == 2)
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, tgt);
|
||||
else
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, next);
|
||||
if (emu_logs < 12) {
|
||||
log_hex64("[VMEXIT] CET emulate FF r/m rip=", rip);
|
||||
emu_logs++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (emu_logs < 12) {
|
||||
log_hex64("[VMEXIT] CET emulate miss rip=", rip);
|
||||
log_hex64("[VMEXIT] CET emulate miss insn=",
|
||||
((u64)insn[0]) | ((u64)insn[1] << 8) |
|
||||
((u64)insn[2] << 16) | ((u64)insn[3] << 24));
|
||||
emu_logs++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 处理 virtio-mmio 设备 GPA 的访问:路由到设备模拟。返回 1=已处理(resume)。 */
|
||||
static int handle_virtio_mmio_access(u64 gpa, u64 rip, int *out_resume) {
|
||||
int is_write, width, reg;
|
||||
@@ -665,8 +1134,29 @@ static int handle_ept_violation(u64 qualification, u64 rip, int *out_resume) {
|
||||
return handle_virtio_mmio_access(gpa, rip, out_resume);
|
||||
}
|
||||
|
||||
/* 普通 guest RAM:按需分配 HPA 页并映射 */
|
||||
/* 普通 guest RAM:按需分配 HPA 页并映射。
|
||||
* Nested KVM can deliver spurious EPT violations on an already-mapped
|
||||
* GPA. Allocating a fresh page would discard libc/text that Linux
|
||||
* already wrote (PTE then points at a zero hole). Upgrade flags. */
|
||||
u64 page = gpa & ~(EPT_PAGE_SIZE - 1);
|
||||
u64 existing = ept_gpa_to_hpa(page);
|
||||
if (existing) {
|
||||
static u32 reuse_logs;
|
||||
if (ept_map_range(page, existing, EPT_PAGE_SIZE, EPT_RWX) != 0) {
|
||||
log_error("[VMEXIT] EPT upgrade existing page failed");
|
||||
log_hex64("[VMEXIT] gpa=", gpa);
|
||||
*out_resume = 0;
|
||||
return -1;
|
||||
}
|
||||
ept_flush_ept();
|
||||
if (reuse_logs < 8) {
|
||||
reuse_logs++;
|
||||
log_hex64("[VMEXIT] EPT reuse gpa=", gpa);
|
||||
log_hex64("[VMEXIT] EPT reuse hpa=", existing);
|
||||
}
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
if (ept_map_guest_ram_page(page) != 0) {
|
||||
log_error("[VMEXIT] EPT violation: guest RAM map failed");
|
||||
log_hex64("[VMEXIT] gpa=", gpa);
|
||||
@@ -677,12 +1167,232 @@ static int handle_ept_violation(u64 qualification, u64 rip, int *out_resume) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void log_guest_page_walk(u64 linear) {
|
||||
u64 cr3 = guest_current_cr3();
|
||||
u64 cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
int shifts[5] = { 48, 39, 30, 21, 12 };
|
||||
int first = (cr4 & (1ULL << 12)) ? 0 : 1; /* CR4.LA57 */
|
||||
u64 table_gpa = cr3 & 0x000FFFFFFFFFF000ULL;
|
||||
u64 hhdm = g_hhdm_request.response ?
|
||||
g_hhdm_request.response->offset : 0;
|
||||
|
||||
log_hex64("[VMEXIT] walk cr4=", cr4);
|
||||
for (int level = first; level < 5; level++) {
|
||||
u64 table_hpa = ept_gpa_to_hpa(table_gpa);
|
||||
log_hex64("[VMEXIT] walk table_gpa=", table_gpa);
|
||||
log_hex64("[VMEXIT] walk table_hpa=", table_hpa);
|
||||
if (!table_hpa || !hhdm) break;
|
||||
|
||||
u64 *table = (u64 *)(hhdm + table_hpa);
|
||||
u64 index = (linear >> shifts[level]) & 0x1FF;
|
||||
u64 entry = table[index];
|
||||
log_hex64("[VMEXIT] walk index=", index);
|
||||
log_hex64("[VMEXIT] walk entry=", entry);
|
||||
if (!(entry & 1)) break;
|
||||
if ((shifts[level] == 30 || shifts[level] == 21) &&
|
||||
(entry & (1ULL << 7))) {
|
||||
break;
|
||||
}
|
||||
table_gpa = entry & 0x000FFFFFFFFFF000ULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* MOV CR3: keep a software copy. Nested KVM leaves VMCS GUEST_CR3 stuck
|
||||
* on an early kernel PGD (zero pages for user GVA). */
|
||||
static int handle_cr_access(u64 qualification, u64 rip, u64 instr_len,
|
||||
int *out_resume) {
|
||||
u32 cr = (u32)(qualification & 0xF);
|
||||
u32 type = (u32)((qualification >> 4) & 3);
|
||||
u32 reg = (u32)((qualification >> 8) & 0xF);
|
||||
static u32 logs;
|
||||
|
||||
if (cr == 3 && type == 0) {
|
||||
u64 val = read_gpr_full((int)reg);
|
||||
track_guest_cr3(val);
|
||||
if (logs < 12) {
|
||||
log_hex64("[VMEXIT] MOV CR3=", val);
|
||||
logs++;
|
||||
}
|
||||
} else if (cr == 3 && type == 1) {
|
||||
write_gpr_full((int)reg, guest_current_cr3());
|
||||
}
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + (instr_len ? instr_len : 3));
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 处理通用异常:打印诊断信息,终止 guest。 */
|
||||
static int handle_exception(u64 qualification, u64 rip, int *out_resume) {
|
||||
u64 intr_info = vmx_vmcs_read(0x4404); /* VM_EXIT_INTR_INFO */
|
||||
log_hex64("[VMEXIT] exception intr_info=", intr_info);
|
||||
log_hex64("[VMEXIT] exception qual=", qualification);
|
||||
log_hex64("[VMEXIT] exception rip=", rip);
|
||||
static u32 g_exc_logs;
|
||||
static u32 g_user_exc_logs;
|
||||
u64 intr_info = vmx_vmcs_read(VMCS_EXIT_INTR_INFO);
|
||||
int verbose = g_exc_logs < 8;
|
||||
int user_rip = guest_is_user_rip(rip);
|
||||
if (verbose) g_exc_logs++;
|
||||
if (verbose) {
|
||||
log_hex64("[VMEXIT] exception intr_info=", intr_info);
|
||||
if (intr_info & (1ULL << 11)) {
|
||||
log_hex64("[VMEXIT] exception error_code=",
|
||||
vmx_vmcs_read(VMCS_EXIT_INTR_ERROR_CODE));
|
||||
}
|
||||
log_hex64("[VMEXIT] exception gla=",
|
||||
vmx_vmcs_read(VMCS_GUEST_LINEAR_ADDR));
|
||||
log_hex64("[VMEXIT] exception qual=", qualification);
|
||||
log_hex64("[VMEXIT] exception rip=", rip);
|
||||
log_hex64("[VMEXIT] exception cr3=", vmx_vmcs_read(VMCS_GUEST_CR3));
|
||||
log_hex64("[VMEXIT] exception rsp=", vmx_vmcs_read(VMCS_GUEST_RSP));
|
||||
log_hex64("[VMEXIT] exception rbx=", g_guest_regs.rbx);
|
||||
log_hex64("[VMEXIT] exception rsi=", g_guest_regs.rsi);
|
||||
log_hex64("[VMEXIT] exception rdi=", g_guest_regs.rdi);
|
||||
log_hex64("[VMEXIT] exception r11=", g_guest_regs.r11);
|
||||
log_hex64("[VMEXIT] exception r15=", g_guest_regs.r15);
|
||||
{
|
||||
u8 insn[8];
|
||||
if (guest_fetch(rip, insn, sizeof(insn)) == 0) {
|
||||
log_hex64("[VMEXIT] insn0=",
|
||||
((u64)insn[0]) | ((u64)insn[1] << 8) |
|
||||
((u64)insn[2] << 16) | ((u64)insn[3] << 24) |
|
||||
((u64)insn[4] << 32) | ((u64)insn[5] << 40) |
|
||||
((u64)insn[6] << 48) | ((u64)insn[7] << 56));
|
||||
}
|
||||
}
|
||||
if ((intr_info & 0xFF) == 14) {
|
||||
log_guest_page_walk(qualification);
|
||||
}
|
||||
}
|
||||
if (g_linux_guest_active && user_rip && g_user_exc_logs < 16) {
|
||||
u8 insn[8];
|
||||
g_user_exc_logs++;
|
||||
log_hex64("[VMEXIT] user-exc vec=", intr_info & 0xFF);
|
||||
log_hex64("[VMEXIT] user-exc err=",
|
||||
(intr_info & (1ULL << 11)) ?
|
||||
vmx_vmcs_read(VMCS_EXIT_INTR_ERROR_CODE) : 0);
|
||||
log_hex64("[VMEXIT] user-exc rip=", rip);
|
||||
log_hex64("[VMEXIT] user-exc cr3=", vmx_vmcs_read(VMCS_GUEST_CR3));
|
||||
log_hex64("[VMEXIT] user-exc tracked=", guest_current_cr3());
|
||||
log_hex64("[VMEXIT] user-exc csar=", vmx_vmcs_read(VMCS_GUEST_CS_ACCESS));
|
||||
log_hex64("[VMEXIT] user-exc ilen=",
|
||||
vmx_vmcs_read(VMCS_INSTRUCTION_LENGTH));
|
||||
log_hex64("[VMEXIT] user-exc walk=", vmx_guest_gva_to_gpa(rip));
|
||||
log_hex64("[VMEXIT] user-exc hpa=",
|
||||
ept_gpa_to_hpa(vmx_guest_gva_to_gpa(rip)));
|
||||
{
|
||||
u64 gpa = vmx_guest_gva_to_gpa(rip);
|
||||
u64 hpa = gpa ? ept_gpa_to_hpa(gpa) : 0;
|
||||
u64 hhdm = g_hhdm_request.response ?
|
||||
g_hhdm_request.response->offset : 0;
|
||||
if (hpa && hhdm) {
|
||||
u8 *p = (u8 *)(hhdm + hpa);
|
||||
log_hex64("[VMEXIT] user-exc mem=",
|
||||
((u64)p[0]) | ((u64)p[1] << 8) |
|
||||
((u64)p[2] << 16) | ((u64)p[3] << 24) |
|
||||
((u64)p[4] << 32) | ((u64)p[5] << 40) |
|
||||
((u64)p[6] << 48) | ((u64)p[7] << 56));
|
||||
}
|
||||
gpa = guest_walk_cr3_at(rip, guest_current_cr3() ^ 0x1000ULL, 0);
|
||||
hpa = gpa ? ept_gpa_to_hpa(gpa) : 0;
|
||||
log_hex64("[VMEXIT] user-exc xor-gpa=", gpa);
|
||||
log_hex64("[VMEXIT] user-exc xor-hpa=", hpa);
|
||||
if (hpa && hhdm) {
|
||||
u8 *p = (u8 *)(hhdm + hpa);
|
||||
log_hex64("[VMEXIT] user-exc xor-mem=",
|
||||
((u64)p[0]) | ((u64)p[1] << 8) |
|
||||
((u64)p[2] << 16) | ((u64)p[3] << 24) |
|
||||
((u64)p[4] << 32) | ((u64)p[5] << 40) |
|
||||
((u64)p[6] << 48) | ((u64)p[7] << 56));
|
||||
}
|
||||
}
|
||||
if (guest_fetch(rip, insn, sizeof(insn)) == 0) {
|
||||
log_hex64("[VMEXIT] user-exc insn0=",
|
||||
((u64)insn[0]) | ((u64)insn[1] << 8) |
|
||||
((u64)insn[2] << 16) | ((u64)insn[3] << 24) |
|
||||
((u64)insn[4] << 32) | ((u64)insn[5] << 40) |
|
||||
((u64)insn[6] << 48) | ((u64)insn[7] << 56));
|
||||
} else {
|
||||
log_info("[VMEXIT] user-exc fetch failed");
|
||||
}
|
||||
if (g_user_exc_logs == 1)
|
||||
log_guest_page_walk(rip);
|
||||
}
|
||||
if ((intr_info & 0xFF) == 8) {
|
||||
log_error("[VMEXIT] #DF (will not reinject)");
|
||||
log_hex64("[VMEXIT] df cr0=", vmx_vmcs_read(VMCS_GUEST_CR0));
|
||||
log_hex64("[VMEXIT] df cr3=", vmx_vmcs_read(VMCS_GUEST_CR3));
|
||||
log_hex64("[VMEXIT] df cr4=", vmx_vmcs_read(VMCS_GUEST_CR4));
|
||||
log_hex64("[VMEXIT] df idtr=", vmx_vmcs_read(VMCS_GUEST_IDTR_BASE));
|
||||
log_hex64("[VMEXIT] df idtl=", vmx_vmcs_read(VMCS_GUEST_IDTR_LIMIT));
|
||||
*out_resume = 0;
|
||||
return 0;
|
||||
}
|
||||
if (g_linux_guest_active) {
|
||||
u32 vec = (u32)(intr_info & 0xFF);
|
||||
u32 err = 0;
|
||||
u64 ilen = vmx_vmcs_read(VMCS_INSTRUCTION_LENGTH);
|
||||
if (intr_info & (1ULL << 11))
|
||||
err = (u32)vmx_vmcs_read(VMCS_EXIT_INTR_ERROR_CODE);
|
||||
|
||||
/* User #PF at page 0 / wrap: leaked SHSTK store with SSP=0.
|
||||
* Always stash CR2 so a reinjected #PF still sees the L2 address
|
||||
* (CR2 is not a VMCS field). */
|
||||
if (vec == 14) {
|
||||
u64 cr2 = read_cr2_local();
|
||||
u64 gla = vmx_vmcs_read(VMCS_GUEST_LINEAR_ADDR);
|
||||
u64 addr = cr2 ? cr2 : gla;
|
||||
g_pf_cr2 = addr;
|
||||
g_pf_need_cr2 = 1;
|
||||
if (guest_is_user_rip(rip) &&
|
||||
(addr < 0x1000ULL || addr >= 0xFFFFFFFFFFFFF000ULL)) {
|
||||
u64 dummy = 0;
|
||||
if (cet_emulate_shstk_insn(rip, &dummy) == 0) {
|
||||
g_pf_need_cr2 = 0;
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_INTERRUPT_INFO, 0);
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* IBT: skip only when the bytes are ENDBR or the four-NOP strip.
|
||||
* A 16-byte-aligned zero-fetch skip desynced utsm_exec_daemon. */
|
||||
if ((vec == 13 || vec == 6 || vec == 21) &&
|
||||
guest_is_user_rip(rip) &&
|
||||
(err == 0 || vec == 21)) {
|
||||
u8 insn[4];
|
||||
int skip4 = 0;
|
||||
if (guest_fetch(rip, insn, 4) == 0) {
|
||||
if (insn[0] == 0xF3 && insn[1] == 0x0F && insn[2] == 0x1E &&
|
||||
(insn[3] == 0xFA || insn[3] == 0xFB))
|
||||
skip4 = 1;
|
||||
if (insn[0] == 0x90 && insn[1] == 0x90 &&
|
||||
insn[2] == 0x90 && insn[3] == 0x90)
|
||||
skip4 = 1;
|
||||
}
|
||||
if (skip4) {
|
||||
static u32 endbr_skips;
|
||||
if (endbr_skips < 8) {
|
||||
log_hex64("[VMEXIT] skip landing-pad rip=", rip);
|
||||
log_hex64("[VMEXIT] skip landing-pad ilen=", ilen);
|
||||
endbr_skips++;
|
||||
}
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_INTERRUPT_INFO, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + 4);
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_linux_guest_active) {
|
||||
/* Linux's decompressor intentionally faults while extending its
|
||||
* identity map. Reinject the intercepted hardware exception with
|
||||
* its original error code so the guest IDT can service it. */
|
||||
if (intr_info & (1ULL << 11)) {
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_EXCEPTION_ERROR,
|
||||
vmx_vmcs_read(VMCS_EXIT_INTR_ERROR_CODE));
|
||||
}
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_INTERRUPT_INFO, intr_info);
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
*out_resume = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -696,24 +1406,70 @@ int vmexit_dispatch(void) {
|
||||
u64 instr_len = vmx_vmcs_read(VMCS_INSTRUCTION_LENGTH);
|
||||
int resume = 0;
|
||||
|
||||
if (g_linux_guest_active)
|
||||
linux_cet_restore_host();
|
||||
|
||||
g_vmexit_count++;
|
||||
INSTR_STAT_INC(vmexit_total);
|
||||
|
||||
if (g_linux_guest_active &&
|
||||
(g_vmexit_count <= 32 || (g_vmexit_count & 0xFFF) == 0)) {
|
||||
log_hex64("[VMEXIT] n=", g_vmexit_count);
|
||||
log_hex64("[VMEXIT] reason=", reason);
|
||||
log_hex64("[VMEXIT] rip=", rip);
|
||||
log_hex64("[VMEXIT] qual=", qualification);
|
||||
log_hex64("[VMEXIT] ilen=", instr_len);
|
||||
log_hex64("[VMEXIT] idtr=", vmx_vmcs_read(VMCS_GUEST_IDTR_BASE));
|
||||
if (reason == EXIT_EPT_VIOLATION) {
|
||||
log_hex64("[VMEXIT] ept gpa=",
|
||||
vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR));
|
||||
}
|
||||
}
|
||||
|
||||
switch (reason) {
|
||||
case EXIT_HLT:
|
||||
handle_hlt(rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_ENTRY_FAIL_MSR_LOADING:
|
||||
log_warn("[VMEXIT] VM-entry MSR-load failed; dropping CET MSR list");
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_MSR_LOAD_COUNT, 0);
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_EPT_VIOLATION:
|
||||
INSTR_STAT_INC(ept_violation);
|
||||
handle_ept_violation(qualification, rip, &resume);
|
||||
break;
|
||||
case EXIT_EPT_MISCONFIG:
|
||||
/* MMIO 区域权限配置错误或硬件 EPT 表损坏。诊断后终止。 */
|
||||
INSTR_PROBE(EPTM, vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR), rip, 0, 0);
|
||||
log_error("[VMEXIT] EPT misconfig");
|
||||
log_hex64("[VMEXIT] gpa=", vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR));
|
||||
resume = 0;
|
||||
case EXIT_EPT_MISCONFIG: {
|
||||
/* Nested KVM: guest PAT (e.g. WC for virtio-gpu shmem) combined
|
||||
* with EPT WB is a reserved effective type → misconfig. Rewrite
|
||||
* the leaf with Ignore PAT and resume; do not kill the vCPU. */
|
||||
u64 gpa = vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR);
|
||||
u64 page = gpa & ~(EPT_PAGE_SIZE - 1);
|
||||
static u32 misconfig_logs;
|
||||
INSTR_PROBE(EPTM, gpa, rip, 0, 0);
|
||||
if (misconfig_logs < 16) {
|
||||
misconfig_logs++;
|
||||
log_error("[VMEXIT] EPT misconfig");
|
||||
log_hex64("[VMEXIT] gpa=", gpa);
|
||||
ept_log_walk(page);
|
||||
}
|
||||
if (IS_VIRTIO_MMIO_GPA(gpa)) {
|
||||
resume = 0;
|
||||
break;
|
||||
}
|
||||
if (ept_repair_leaf(page, EPT_RWX) != 0) {
|
||||
if (ept_map_guest_ram_page(page) != 0) {
|
||||
log_error("[VMEXIT] EPT misconfig repair failed");
|
||||
resume = 0;
|
||||
break;
|
||||
}
|
||||
if (misconfig_logs <= 16)
|
||||
log_hex64("[VMEXIT] EPT misconfig replaced gpa=", page);
|
||||
}
|
||||
ept_flush_ept();
|
||||
resume = 1;
|
||||
break;
|
||||
}
|
||||
case EXIT_EXCEPTION_NMI:
|
||||
INSTR_PROBE(EXCP, qualification, rip, 0, 0);
|
||||
handle_exception(qualification, rip, &resume);
|
||||
@@ -721,6 +1477,12 @@ int vmexit_dispatch(void) {
|
||||
case EXIT_TRIPLE_FAULT:
|
||||
INSTR_PROBE(EXCP, 0, rip, 0, 0);
|
||||
log_error("[VMEXIT] triple fault");
|
||||
log_hex64("[VMEXIT] triple rip=", rip);
|
||||
log_hex64("[VMEXIT] triple cr0=", vmx_vmcs_read(VMCS_GUEST_CR0));
|
||||
log_hex64("[VMEXIT] triple cr3=", vmx_vmcs_read(VMCS_GUEST_CR3));
|
||||
log_hex64("[VMEXIT] triple cr4=", vmx_vmcs_read(VMCS_GUEST_CR4));
|
||||
log_hex64("[VMEXIT] triple idtr=", vmx_vmcs_read(VMCS_GUEST_IDTR_BASE));
|
||||
log_hex64("[VMEXIT] triple idtl=", vmx_vmcs_read(VMCS_GUEST_IDTR_LIMIT));
|
||||
resume = 0;
|
||||
break;
|
||||
case EXIT_EXTERNAL_INTERRUPT:
|
||||
@@ -742,6 +1504,14 @@ int vmexit_dispatch(void) {
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_INVLPG:
|
||||
/* Guest TLB invalidate. EPT owns the real translation; skip. */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_CONTROL_ACCESS:
|
||||
handle_cr_access(qualification, rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_MONITOR:
|
||||
case EXIT_MWAIT:
|
||||
/* MONITOR/MWAIT:在 noapic 模式下 Linux 不会主动使用,
|
||||
@@ -754,6 +1524,22 @@ int vmexit_dispatch(void) {
|
||||
/* host passthrough + 屏蔽 VMX/hypervisor 位(handle_cpuid 内推进 RIP) */
|
||||
handle_cpuid(rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_XSETBV:
|
||||
/* XSETBV always VM-exits. Linux fpu__init writes XCR0 here. */
|
||||
{
|
||||
u32 xcr = (u32)g_guest_regs.rcx;
|
||||
u64 val = ((u64)(u32)g_guest_regs.rdx << 32) |
|
||||
(u32)g_guest_regs.rax;
|
||||
if (xcr == 0)
|
||||
linux_xsave_set_guest_xcr0(val);
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + (instr_len ? instr_len : 3));
|
||||
resume = 1;
|
||||
}
|
||||
break;
|
||||
case EXIT_WBINVD:
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + (instr_len ? instr_len : 2));
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_IO_INSTRUCTION:
|
||||
INSTR_STAT_INC(io_exit);
|
||||
/* legacy 设备模拟:COM1 UART / PIC / PIT / 其余端口吞掉 */
|
||||
@@ -772,9 +1558,20 @@ int vmexit_dispatch(void) {
|
||||
break;
|
||||
case EXIT_VMX_PREEMPTION_TIMER:
|
||||
INSTR_STAT_INC(preempt_timer);
|
||||
/* 1ms 周期 exit:resume 前由 vmexit_before_resume 统一
|
||||
* 轮询 host 串口 RX / 推进 PIT tick / 注入 pending IRQ */
|
||||
/* 10ms 周期 exit:resume 前由 vmexit_before_resume 统一
|
||||
* 轮询 host 串口 RX / 推进 PIT tick / 注入 pending IRQ。
|
||||
* Also force IRQ0 here. TSC-based PIT accounting can miss ticks
|
||||
* after park/resume, and nanosleep/Xorg then freeze on noapic. */
|
||||
vmx_guest_queue_irq(g_pic_vec_base ? g_pic_vec_base : 0x30);
|
||||
resume = 1;
|
||||
if (g_linux_guest_active && g_linux_timeslice_armed) {
|
||||
g_linux_slice_ticks++;
|
||||
if (g_linux_slice_ticks >= LINUX_HOST_SLICE_TICKS) {
|
||||
g_linux_slice_ticks = 0;
|
||||
g_guest_parked = 1;
|
||||
resume = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EXIT_RDMSR:
|
||||
case EXIT_WRMSR: {
|
||||
@@ -790,6 +1587,55 @@ int vmexit_dispatch(void) {
|
||||
* 注意:WRMSR 写入某些 MSR 可能影响 host,需要白名单保护。
|
||||
*/
|
||||
u64 msr = g_guest_regs.rcx & 0xFFFFFFFFULL;
|
||||
if (msr == 0xC0000080ULL) { /* IA32_EFER */
|
||||
if (reason == EXIT_RDMSR) {
|
||||
u64 val = vmx_vmcs_read(VMCS_GUEST_IA32_EFER);
|
||||
g_guest_regs.rax = val & 0xFFFFFFFFULL;
|
||||
g_guest_regs.rdx = (val >> 32) & 0xFFFFFFFFULL;
|
||||
} else {
|
||||
u64 val = (g_guest_regs.rax & 0xFFFFFFFFULL) |
|
||||
(g_guest_regs.rdx << 32);
|
||||
val |= EFER_LME | EFER_LMA;
|
||||
val &= ~EFER_SVME;
|
||||
vmx_vmcs_write(VMCS_GUEST_IA32_EFER, val);
|
||||
}
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
break;
|
||||
}
|
||||
/* FS/GS bases live in the VMCS. Ignoring WRMSR leaves TLS at 0. */
|
||||
if (msr == 0xC0000100ULL || msr == 0xC0000101ULL || msr == 0xC0000102ULL) {
|
||||
static u64 guest_kernel_gs;
|
||||
if (reason == EXIT_RDMSR) {
|
||||
u64 val = 0;
|
||||
if (msr == 0xC0000100ULL) {
|
||||
val = vmx_vmcs_read(VMCS_GUEST_FS_BASE);
|
||||
} else if (msr == 0xC0000101ULL) {
|
||||
val = vmx_vmcs_read(VMCS_GUEST_GS_BASE);
|
||||
} else {
|
||||
val = guest_kernel_gs;
|
||||
}
|
||||
g_guest_regs.rax = val & 0xFFFFFFFFULL;
|
||||
g_guest_regs.rdx = (val >> 32) & 0xFFFFFFFFULL;
|
||||
} else {
|
||||
u64 val = (g_guest_regs.rax & 0xFFFFFFFFULL) |
|
||||
(g_guest_regs.rdx << 32);
|
||||
if (msr == 0xC0000100ULL) {
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_BASE, val);
|
||||
} else if (msr == 0xC0000101ULL) {
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_BASE, val);
|
||||
} else {
|
||||
u32 lo = (u32)val;
|
||||
u32 hi = (u32)(val >> 32);
|
||||
guest_kernel_gs = val;
|
||||
/* SWAPGS reads the hardware KERNEL_GS_BASE MSR. */
|
||||
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(0xC0000102u));
|
||||
}
|
||||
}
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
break;
|
||||
}
|
||||
if (reason == EXIT_RDMSR) {
|
||||
u64 val = 0;
|
||||
/* 白名单:允许读取的 MSR(passthrough host 值) */
|
||||
@@ -834,13 +1680,17 @@ int vmexit_dispatch(void) {
|
||||
} else {
|
||||
/* WRMSR:白名单保护,防止 guest 修改 host MSR */
|
||||
int allowed = 0;
|
||||
if (msr == 0xC0000100 || /* IA32_FS_BASE — VMCS 管理,忽略 */
|
||||
msr == 0xC0000101 || /* IA32_GS_BASE — VMCS 管理,忽略 */
|
||||
msr == 0xC0000102 || /* IA32_KERNEL_GS_BASE — 可安全 passthrough */
|
||||
if (msr == 0xC0000100 || /* IA32_FS_BASE — handled above */
|
||||
msr == 0xC0000101 ||
|
||||
msr == 0xC0000102 ||
|
||||
msr == 0xC0000103 || /* IA32_TSC_AUX */
|
||||
msr == 0x1B || /* IA32_APIC_BASE — 忽略(guest noapic) */
|
||||
msr == 0xD90 || /* IA32_BNDCFGS — 忽略 */
|
||||
msr == 0x199 /* IA32_PERF_CTL — 忽略 */
|
||||
msr == 0x199 || /* IA32_PERF_CTL — 忽略 */
|
||||
msr == 0x6A0 || /* IA32_U_CET */
|
||||
msr == 0x6A2 || /* IA32_S_CET */
|
||||
msr == 0x6A4 || msr == 0x6A5 || msr == 0x6A6 ||
|
||||
msr == 0x6A7 || msr == 0x6A8
|
||||
) {
|
||||
allowed = 1;
|
||||
}
|
||||
@@ -875,8 +1725,9 @@ int vmexit_dispatch(void) {
|
||||
|
||||
(void)qualification;
|
||||
|
||||
/* resume 前例行工作:arm preemption timer + host 串口 RX 轮询 +
|
||||
* PIT tick 推进 + pending IRQ 注入(仅 Linux guest active 时) */
|
||||
/* resume 前例行工作:仅在继续跑 guest 时注入 IRQ / 轮询设备。
|
||||
* 不要在 HLT/timeslice 归还 host 时调用——那会在 host 栈上走 guest
|
||||
* virtio poll,已触发 CR2=3 的 host #PF。下次 linux_resume 入口再准备。 */
|
||||
if (resume) {
|
||||
INSTR_STAT_INC(pre_resume);
|
||||
vmexit_before_resume();
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
vmm/vmexit.o: vmm\vmexit.c include\utsm\vmx.h include\utsm\types.h \
|
||||
include\utsm\ept.h include\utsm\vmm.h include\utsm\hypercall.h \
|
||||
include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h include\utsm\log.h \
|
||||
include\utsm\dma.h include\utsm\linux_loader.h \
|
||||
include\utsm\virtio_mmio.h include\utsm\instr.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\hypercall.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\instr.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -69,9 +69,10 @@ vmx_vm_exit_handler:
|
||||
movq g_guest_regs + 104(%rip), %r14
|
||||
movq g_guest_regs + 112(%rip), %r15
|
||||
|
||||
/* Step 4: Check terminate flag */
|
||||
movl g_guest_terminated(%rip), %eax
|
||||
testl %eax, %eax
|
||||
/* Step 4: Check terminate flag without clobbering guest RAX.
|
||||
* The old `movl g_guest_terminated, %eax` zeroed every IN/CPUID
|
||||
* result and livelocked Linux earlyprintk on COM1 LSR (0x3FD). */
|
||||
cmpl $0, g_guest_terminated(%rip)
|
||||
jnz .Lterminate
|
||||
|
||||
/* Resume guest: GPRs are now restored (with any C modifications).
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
vmm/vmm.o: vmm\vmm.c include\utsm\vmm.h include\utsm\types.h \
|
||||
include\utsm\vmx.h include\utsm\ept.h include\utsm\log.h \
|
||||
include\utsm\dma.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
+1
-1
@@ -51,7 +51,7 @@ u64 vmx_kernel_virt_to_phys(const void *address) {
|
||||
return response->physical_base + (virt - response->virtual_base);
|
||||
}
|
||||
|
||||
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
|
||||
void vmx_write_msr(u32 msr, u64 value) {
|
||||
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
||||
u32 hi = (u32)(value >> 32);
|
||||
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
vmm/vmx.o: vmm\vmx.c include\utsm\vmx.h include\utsm\types.h \
|
||||
include\utsm\ept.h include\utsm\log.h include\utsm\dma.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -1,10 +0,0 @@
|
||||
vmm/xj380_boot.o: vmm\xj380_boot.c include\utsm\xj380_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\log.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\xj380_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -1,10 +0,0 @@
|
||||
vmm/xj380_loader.o: vmm\xj380_loader.c include\utsm\xj380_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\log.h include\utsm\dma.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\xj380_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
Reference in New Issue
Block a user