FUCKYOU
This commit is contained in:
@@ -115,6 +115,140 @@ int ept_identity_map(u64 gpa, u64 size, u64 flags) {
|
||||
return ept_map_range(gpa, gpa, size, flags);
|
||||
}
|
||||
|
||||
/* ===== EPT 大页映射(2MB 大页,真机 MTRR 感知) =====
|
||||
*
|
||||
* 在 PD 层级直接映射 2MB 大页(bit 7 = EPT_LARGE_PAGE),
|
||||
* 减少 EPT walk 深度(4 级 → 3 级)和 TLB 压力。
|
||||
* 真机要求大页 memory type 与 MTRR 一致:
|
||||
* - RAM 区域用 WB (6)
|
||||
* - MMIO 区域用 UC (0)
|
||||
* QEMU 忽略 MTRR,但真机会检查。
|
||||
*
|
||||
* 用法:ept_map_2m_page(GPA, HPA, flags, memtype)
|
||||
* flags = EPT_RWX 等
|
||||
* memtype = EPT_MEMORY_TYPE_WB / EPT_MEMORY_TYPE_UC 等 */
|
||||
int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) {
|
||||
if (!g_ept_ready) return -1;
|
||||
/* GPA 和 HPA 必须 2MB 对齐 */
|
||||
if ((gpa & 0x1FFFFFULL) || (hpa & 0x1FFFFFULL)) {
|
||||
log_error("[EPT] 2MB page unaligned");
|
||||
return -2;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* PML4 */
|
||||
u64 entry = table[pml4_idx];
|
||||
if (!(entry & EPT_READ)) {
|
||||
u64 new_phys = ept_alloc_page();
|
||||
if (new_phys == 0) return -3;
|
||||
table[pml4_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3);
|
||||
table = (u64 *)phys_to_virt(new_phys);
|
||||
} else {
|
||||
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
|
||||
}
|
||||
|
||||
/* PDPT */
|
||||
entry = table[pdpt_idx];
|
||||
if (!(entry & EPT_READ)) {
|
||||
u64 new_phys = ept_alloc_page();
|
||||
if (new_phys == 0) return -4;
|
||||
table[pdpt_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3);
|
||||
table = (u64 *)phys_to_virt(new_phys);
|
||||
} else {
|
||||
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
|
||||
}
|
||||
|
||||
/* PD:直接写 2MB 大页项 */
|
||||
u64 pd_entry = (hpa & 0x000FFFFFFFE00000ULL) /* 物理地址(2MB 对齐) */
|
||||
| flags /* R/W/X 权限 */
|
||||
| EPT_LARGE_PAGE /* bit 7 = 大页标志 */
|
||||
| (memtype << 3); /* memory type */
|
||||
table[pd_idx] = pd_entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== INVVPID 支持(真机 VPID 刷新) =====
|
||||
*
|
||||
* 启用 VPID 后,真机要求在修改 EPT 映射后执行 INVVPID 刷新
|
||||
* stale TLB entry。QEMU 不检查,但真机会在以下场景触发 VMX abort:
|
||||
* - guest 修改了 CR3 但 TLB 中缓存了旧 VPID 的映射
|
||||
* - EPT 重映射后未刷新
|
||||
* - VMCS VPID 字段变更后未执行 INVVPID
|
||||
*
|
||||
* INVVPID 类型:
|
||||
* 0 = 不支持
|
||||
* 1 = 单个 VPID 刷新(INVVPID_DESC.vpid)
|
||||
* 2 = 全局 VPID 刷新(所有 VPID)
|
||||
* 3 = 单个 VPID + PCID 刷新
|
||||
*
|
||||
* 当前实现:使用类型 2(全局刷新),最安全但性能最差。
|
||||
* 优化路径:在 EPT 重映射时调用类型 1 刷新特定 VPID。 */
|
||||
static int g_invvpid_supported;
|
||||
static int g_invept_supported;
|
||||
|
||||
void ept_check_vpid_support(void) {
|
||||
u64 cap = vmx_read_msr(IA32_VMX_EPT_VPID_CAP);
|
||||
/* INVVPID 类型 2 (all-context) 支持:bit 32 */
|
||||
g_invvpid_supported = (cap & (1ULL << 32)) ? 1 : 0;
|
||||
/* INVEPT 类型 2 (all-context) 支持:bit 25 */
|
||||
g_invept_supported = (cap & (1ULL << 25)) ? 1 : 0;
|
||||
log_hex64("[EPT] VPID cap=", cap);
|
||||
log_info(g_invvpid_supported ? "[EPT] INVVPID supported" : "[EPT] INVVPID NOT supported");
|
||||
log_info(g_invept_supported ? "[EPT] INVEPT supported" : "[EPT] INVEPT NOT supported");
|
||||
}
|
||||
|
||||
/* 执行 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};
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"invept (%2), %1\n"
|
||||
"jnc 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)");
|
||||
}
|
||||
}
|
||||
|
||||
/* 执行 INVVPID(VPID TLB 刷新)。真机要求在 VPID 变更后调用。 */
|
||||
void ept_flush_vpid(u16 vpid) {
|
||||
if (!g_invvpid_supported) return;
|
||||
/* INVVPID 类型 2: all-context invalidate */
|
||||
u64 desc[2] = {0, 0};
|
||||
desc[0] = (u64)vpid;
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"invvpid (%2), %1\n"
|
||||
"jnc 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] INVVPID failed (non-critical on QEMU)");
|
||||
}
|
||||
}
|
||||
|
||||
/* Walk the EPT to translate a GPA to its mapped HPA.
|
||||
* Returns 0 if the GPA is not mapped (no present entry at any level).
|
||||
* Handles 4KB pages and 2MB large pages (bit 7 = PS in PD entry). */
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
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:
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/linux_compat.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <ipc_proto.h>
|
||||
@@ -179,6 +180,80 @@ static long hcall_console_read(u64 a0, u64 a1, u64 a2) {
|
||||
return UTSM_HCALL_NOSYS;
|
||||
}
|
||||
|
||||
/* ===== Phase 3: Linux guest ↔ UTSM FAT32 file transfer =====
|
||||
*
|
||||
* payload_pool at offset 0 is the bulk data buffer. ABI details are
|
||||
* documented in ipc_proto.h ("Linux ↔ UTSM FAT32 hypercalls").
|
||||
*
|
||||
* Return packing for FILE_READ (positive RAX):
|
||||
* low 20 bits = staged byte count (pool capacity < 1MB → fits)
|
||||
* RAX >> 20 = total file size (FAT32 root files ≤ 256KB → fits) */
|
||||
|
||||
/* Read a NUL-terminated file name (max 63 chars) from guest memory. */
|
||||
static int hcall_read_name(u64 gpa, char out[64]) {
|
||||
if (guest_read_buf(gpa, out, 64) != 0) return -1;
|
||||
out[63] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Map lxc_f32_* error codes to hypercall return codes. */
|
||||
static long hcall_f32_err(int rc) {
|
||||
if (rc == -10) return UTSM_HCALL_INVAL; /* illegal file name */
|
||||
if (rc == -11) return UTSM_HCALL_NOENT; /* not found */
|
||||
if (rc == -9) return UTSM_HCALL_IO; /* no block device */
|
||||
return UTSM_HCALL_IO;
|
||||
}
|
||||
|
||||
static long hcall_file_read(u64 a0, u64 a1, u64 a2) {
|
||||
/* a0 = name GPA, a1 = file offset, a2 = max bytes to stage */
|
||||
if (a0 == 0) return UTSM_HCALL_INVAL;
|
||||
|
||||
char name[64];
|
||||
if (hcall_read_name(a0, name) != 0) return UTSM_HCALL_INVAL;
|
||||
|
||||
u8 *fdata = (u8 *)0;
|
||||
u32 fsize = 0;
|
||||
int rc = lxc_f32_read_file(name, &fdata, &fsize);
|
||||
if (rc != 0) return hcall_f32_err(rc);
|
||||
|
||||
u64 cap = ipc_shm_payload_capacity();
|
||||
u64 want = a2;
|
||||
if (want > cap) want = cap;
|
||||
|
||||
u64 n = 0;
|
||||
if (a1 < (u64)fsize) {
|
||||
n = (u64)fsize - a1;
|
||||
if (n > want) n = want;
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
u8 *pool = (u8 *)ipc_shm_payload_ptr(0);
|
||||
if (!pool) return UTSM_HCALL_NOMEM;
|
||||
for (u64 i = 0; i < n; i++) pool[i] = fdata[a1 + i];
|
||||
}
|
||||
|
||||
return (long)(((u64)fsize << 20) | n);
|
||||
}
|
||||
|
||||
static long hcall_file_write(u64 a0, u64 a1, u64 a2) {
|
||||
/* a0 = name GPA, a1 = reserved (0), a2 = bytes staged at pool[0] */
|
||||
(void)a1;
|
||||
if (a0 == 0) return UTSM_HCALL_INVAL;
|
||||
if (a2 > ipc_shm_payload_capacity() || a2 > 262144) {
|
||||
return UTSM_HCALL_INVAL; /* fat32_io 单文件上限 256KB */
|
||||
}
|
||||
|
||||
char name[64];
|
||||
if (hcall_read_name(a0, name) != 0) return UTSM_HCALL_INVAL;
|
||||
|
||||
const u8 *pool = (const u8 *)ipc_shm_payload_ptr(0);
|
||||
if (!pool && a2 > 0) return UTSM_HCALL_NOMEM;
|
||||
|
||||
int rc = lxc_f32_write_file(name, pool, (u32)a2);
|
||||
if (rc != 0) return hcall_f32_err(rc);
|
||||
return (long)a2;
|
||||
}
|
||||
|
||||
/* ===== Main hypercall dispatcher ===== */
|
||||
|
||||
int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx,
|
||||
@@ -227,6 +302,12 @@ int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx,
|
||||
case UTSM_HCALL_CONSOLE_READ:
|
||||
result = hcall_console_read(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
case UTSM_HCALL_FILE_READ:
|
||||
result = hcall_file_read(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
case UTSM_HCALL_FILE_WRITE:
|
||||
result = hcall_file_write(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
default:
|
||||
log_hex64("[HCALL] unknown op: ", op);
|
||||
result = UTSM_HCALL_NOSYS;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
vmm/hypercall.o: vmm\hypercall.c include\utsm\hypercall.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h \
|
||||
include\utsm\linux_compat.h include\utsm\log.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\hypercall.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -121,6 +121,18 @@ u64 ipc_shm_get_size(void) {
|
||||
return UTSM_IPC_SHM_SIZE;
|
||||
}
|
||||
|
||||
/* ===== Payload pool access ===== */
|
||||
|
||||
void *ipc_shm_payload_ptr(u32 offset) {
|
||||
if (!g_shm_ready || !g_shm) return (void *)0;
|
||||
if (offset >= UTSM_IPC_PAYLOAD_SIZE) return (void *)0;
|
||||
return (void *)(g_shm->payload_pool + offset);
|
||||
}
|
||||
|
||||
u32 ipc_shm_payload_capacity(void) {
|
||||
return UTSM_IPC_PAYLOAD_SIZE;
|
||||
}
|
||||
|
||||
/* ===== Message API (UTSM-side) ===== */
|
||||
|
||||
int ipc_shm_send(u32 msg_type, const void *data, u32 data_len) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
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:
|
||||
@@ -292,6 +292,9 @@ int linux_launch(void) {
|
||||
* virtio_mmio.device= 发现。幂等:重复调用直接返回。 */
|
||||
virtio_mmio_init();
|
||||
|
||||
/* 真机 EPT TLB 刷新:在 vmlaunch 前确保 EPT 映射对 TLB 可见 */
|
||||
ept_flush_ept();
|
||||
|
||||
/* Reset VM-Exit state */
|
||||
g_guest_terminated = 0;
|
||||
g_last_exit_reason = 0xFFFFFFFFULL;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
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:
|
||||
@@ -20,7 +20,9 @@
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/block.h>
|
||||
#include <ipc_proto.h>
|
||||
#include "../../tools/fat32_io.h"
|
||||
|
||||
/* ===== 内部辅助 ===== */
|
||||
|
||||
@@ -184,13 +186,277 @@ static int lxc_status(char *buf, u64 cap) {
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
/* ===== Phase 3 文件传输(FILE_LIST / FILE_READ) =====
|
||||
*
|
||||
* 同步请求-响应模型(与 exec 相同):
|
||||
* 1. 构建 ipc_file_request(指定 payload_pool 写入偏移与容量)
|
||||
* 2. ipc_shm_send 入队 → linux_resume 唤醒 daemon
|
||||
* 3. daemon 把批量数据直接写进 payload_pool,回 FILE_RESPONSE 后 HLT
|
||||
* 4. 从 ring 取出响应头,按 data_len 从 payload_pool 拷出数据
|
||||
*/
|
||||
|
||||
/* 发送文件请求并等待响应(公共子流程)。
|
||||
* msg_type : UTSM_MSG_FILE_LIST_REQUEST / UTSM_MSG_FILE_READ_REQUEST
|
||||
* path : Linux 绝对路径
|
||||
* offset : FILE_READ 的文件偏移(LIST 传 0)
|
||||
* want_cap : 期望读取的最大字节数(会钳制到 payload_pool 容量)
|
||||
* 成功时 resp 填充并返回 0;失败返回负值(-2 发送失败, -3 resume 失败,
|
||||
* -4 无响应)。 */
|
||||
static int lxc_file_roundtrip(u32 msg_type, const char *path, u64 offset,
|
||||
u32 want_cap, struct ipc_file_response *resp) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!path || !resp) return -1;
|
||||
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
if (want_cap > pool_cap) want_cap = pool_cap;
|
||||
|
||||
struct ipc_file_request req;
|
||||
my_memzero(&req, sizeof(req));
|
||||
req.pool_offset = 0; /* 独占使用整个 pool(同步模型,无并发) */
|
||||
req.pool_capacity = want_cap;
|
||||
req.file_offset = offset;
|
||||
|
||||
u64 plen = my_strlen(path);
|
||||
if (plen >= UTSM_FILE_PATH_MAX) plen = UTSM_FILE_PATH_MAX - 1;
|
||||
my_memcpy(req.path, path, plen);
|
||||
req.path[plen] = 0;
|
||||
|
||||
if (ipc_shm_send(msg_type, &req, sizeof(req)) != 0) {
|
||||
log_warn("[LNXC] file: failed to enqueue request");
|
||||
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;
|
||||
}
|
||||
}
|
||||
return -4; /* 无响应 */
|
||||
}
|
||||
|
||||
/* 从 payload_pool 拷出数据到调用方缓冲。返回拷出的字节数。 */
|
||||
static u64 lxc_pool_copyout(char *buf, u64 cap, u32 data_len) {
|
||||
if (!buf || cap == 0 || data_len == 0) return 0;
|
||||
u8 *pool = (u8 *)ipc_shm_payload_ptr(0);
|
||||
if (!pool) return 0;
|
||||
u64 n = data_len;
|
||||
if (n > cap) n = cap;
|
||||
if (n > ipc_shm_payload_capacity()) n = ipc_shm_payload_capacity();
|
||||
my_memcpy(buf, pool, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
static int lxc_file_list(const char *path, char *buf, u64 cap, u64 *out_len) {
|
||||
struct ipc_file_response resp;
|
||||
/* 请求的容量取调用方缓冲与 pool 容量的较小值 */
|
||||
u32 want = (cap > 0xFFFFFFFFULL) ? 0xFFFFFFFFU : (u32)cap;
|
||||
int rc = lxc_file_roundtrip(UTSM_MSG_FILE_LIST_REQUEST, path, 0, want, &resp);
|
||||
if (rc != 0) {
|
||||
if (out_len) *out_len = 0;
|
||||
return rc;
|
||||
}
|
||||
if (resp.status != UTSM_FILE_OK) {
|
||||
if (out_len) *out_len = 0;
|
||||
log_hex64("[LNXC] file_list: linux side error=", resp.status);
|
||||
return -5;
|
||||
}
|
||||
u64 n = lxc_pool_copyout(buf, cap, resp.data_len);
|
||||
if (out_len) *out_len = n;
|
||||
/* Linux 侧写出的数据超过调用方缓冲 → 截断提示 */
|
||||
if (resp.data_len > n) return -6;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxc_file_read(const char *path, u64 offset, char *buf, u64 cap,
|
||||
u64 *out_len, u64 *out_total) {
|
||||
struct ipc_file_response resp;
|
||||
u32 want = (cap > 0xFFFFFFFFULL) ? 0xFFFFFFFFU : (u32)cap;
|
||||
int rc = lxc_file_roundtrip(UTSM_MSG_FILE_READ_REQUEST, path, offset, want, &resp);
|
||||
if (rc != 0) {
|
||||
if (out_len) *out_len = 0;
|
||||
if (out_total) *out_total = 0;
|
||||
return rc;
|
||||
}
|
||||
if (out_total) *out_total = resp.total_size;
|
||||
if (resp.status != UTSM_FILE_OK) {
|
||||
if (out_len) *out_len = 0;
|
||||
log_hex64("[LNXC] file_read: linux side error=", resp.status);
|
||||
return -5;
|
||||
}
|
||||
u64 n = lxc_pool_copyout(buf, cap, resp.data_len);
|
||||
if (out_len) *out_len = n;
|
||||
if (resp.data_len > n) return -6;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 写入 Linux 文件:把调用方数据先搬进 payload_pool,再走同步请求-响应。
|
||||
* 单次最多搬运 payload pool 容量(~1MB),大文件由调用方循环推进 offset。 */
|
||||
static int lxc_file_write(const char *path, u64 offset, const char *buf,
|
||||
u64 len, u64 *out_written) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!path || (!buf && len > 0)) return -1;
|
||||
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
u64 n = len;
|
||||
if (n > pool_cap) n = pool_cap;
|
||||
|
||||
if (n > 0) {
|
||||
u8 *pool = (u8 *)ipc_shm_payload_ptr(0);
|
||||
if (!pool) return -1;
|
||||
my_memcpy(pool, buf, n);
|
||||
utsm_ipc_mb(); /* 保证 pool 数据先于请求对 guest 可见 */
|
||||
}
|
||||
|
||||
/* 复用公共子流程:WRITE 时 pool_capacity 字段表示已 staged 的字节数 */
|
||||
struct ipc_file_response resp;
|
||||
int rc = lxc_file_roundtrip(UTSM_MSG_FILE_WRITE_REQUEST, path, offset,
|
||||
(u32)n, &resp);
|
||||
if (rc != 0) {
|
||||
if (out_written) *out_written = 0;
|
||||
return rc;
|
||||
}
|
||||
if (resp.status != UTSM_FILE_OK) {
|
||||
if (out_written) *out_written = 0;
|
||||
log_hex64("[LNXC] file_write: linux side error=", resp.status);
|
||||
return -5;
|
||||
}
|
||||
if (out_written) *out_written = resp.data_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== UTSM FAT32 系统盘接入(file_pull / file_push / hypercall 共用) =====
|
||||
*
|
||||
* 复用 tools/fat32_io.h(static inline 库,仅根目录、单文件 ≤256KB),
|
||||
* block 读写函数指针来自 UTSM block 注册表(block_get_api(),
|
||||
* 等价于 DSK 侧 dkm_kernel_api +0xA8 block_read / +0x18 block_write)。 */
|
||||
|
||||
static int g_f32_inited; /* 1 = f32_init 已完成 */
|
||||
|
||||
/* 惰性初始化 FAT32 库。返回 0 可用,-9 无块设备。 */
|
||||
static int lxc_f32_ensure(void) {
|
||||
const dkm_block_api *api = block_get_api();
|
||||
if (!api || !api->read || !api->write) return -9;
|
||||
if (api->device_count() == 0) return -9;
|
||||
if (!g_f32_inited) {
|
||||
f32_init(api->read, api->write);
|
||||
g_f32_inited = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lxc_f32_read_file(const char *name, u8 **out_data, u32 *out_size) {
|
||||
if (!name || !out_data || !out_size) return -10;
|
||||
int rc = lxc_f32_ensure();
|
||||
if (rc != 0) return rc;
|
||||
char n11[11];
|
||||
if (f32_name_to_83(name, n11) != 0) return -10;
|
||||
int fr = f32_read_root_file(n11, out_data, out_size);
|
||||
if (fr == -5) return -11; /* 根目录中未找到 */
|
||||
if (fr != 0) return -12; /* 磁盘 / 格式错误 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lxc_f32_write_file(const char *name, const u8 *data, u32 size) {
|
||||
if (!name || (!data && size > 0)) return -10;
|
||||
int rc = lxc_f32_ensure();
|
||||
if (rc != 0) return rc;
|
||||
char n11[11];
|
||||
if (f32_name_to_83(name, n11) != 0) return -10;
|
||||
if (f32_write_root_file(n11, data, size) != 0) return -12;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* pull 暂存缓冲:与 fat32_io 单文件上限一致(256KB) */
|
||||
static u8 g_pull_stage[262144];
|
||||
|
||||
/* 跨内核拉取:Linux 文件 → UTSM FAT32 根目录。
|
||||
* 先经 IPC 分块读完整文件到暂存缓冲(失败则不写盘,避免半成品),
|
||||
* 再一次 f32_write_root_file 落盘。 */
|
||||
static int lxc_file_pull(const char *linux_path, const char *fat32_name,
|
||||
u64 *out_size) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!linux_path || !fat32_name) return -1;
|
||||
int rc = lxc_f32_ensure();
|
||||
if (rc != 0) { if (out_size) *out_size = 0; return rc; }
|
||||
|
||||
u64 off = 0;
|
||||
u64 total = 0;
|
||||
for (;;) {
|
||||
u64 got = 0;
|
||||
u64 space = (u64)sizeof(g_pull_stage) - off;
|
||||
if (space == 0) {
|
||||
if (out_size) *out_size = 0;
|
||||
return -7; /* 超过 256KB 暂存上限 */
|
||||
}
|
||||
rc = lxc_file_read(linux_path, off, (char *)g_pull_stage + off,
|
||||
space, &got, &total);
|
||||
if (rc != 0) { if (out_size) *out_size = 0; return rc; }
|
||||
off += got;
|
||||
if (got == 0 || off >= total) break;
|
||||
}
|
||||
|
||||
rc = lxc_f32_write_file(fat32_name, g_pull_stage, (u32)off);
|
||||
if (rc != 0) { if (out_size) *out_size = 0; return rc; }
|
||||
if (out_size) *out_size = off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 跨内核推送:UTSM FAT32 根目录 → Linux 文件(整文件替换)。
|
||||
* f32_read_root_file 读出后经 lxc_file_write 按 pool 容量分块写入;
|
||||
* 首块 offset=0 → daemon 侧 O_TRUNC,实现整文件替换语义。 */
|
||||
static int lxc_file_push(const char *fat32_name, const char *linux_path,
|
||||
u64 *out_size) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!fat32_name || !linux_path) return -1;
|
||||
|
||||
u8 *data = 0;
|
||||
u32 size = 0;
|
||||
int rc = lxc_f32_read_file(fat32_name, &data, &size);
|
||||
if (rc != 0) { if (out_size) *out_size = 0; return rc; }
|
||||
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
u64 off = 0;
|
||||
for (;;) {
|
||||
u64 remain = (u64)size - off;
|
||||
u64 chunk = remain > pool_cap ? pool_cap : remain;
|
||||
u64 written = 0;
|
||||
rc = lxc_file_write(linux_path, off, (const char *)data + off,
|
||||
chunk, &written);
|
||||
if (rc != 0) { if (out_size) *out_size = off; return rc; }
|
||||
if (written != chunk) { /* 短写:按 I/O 错误处理 */
|
||||
if (out_size) *out_size = off + written;
|
||||
return -5;
|
||||
}
|
||||
off += written;
|
||||
if (off >= size) break;
|
||||
}
|
||||
if (out_size) *out_size = off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== 服务表 ===== */
|
||||
|
||||
static const linux_compat_service g_lxc_service = {
|
||||
LINUX_COMPAT_MAGIC,
|
||||
lxc_is_available,
|
||||
lxc_exec,
|
||||
lxc_file_list,
|
||||
lxc_file_read,
|
||||
lxc_status,
|
||||
lxc_file_write,
|
||||
lxc_file_pull,
|
||||
lxc_file_push,
|
||||
};
|
||||
|
||||
const linux_compat_service *linux_compat_get_service(void) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
vmm/linux_compat.o: vmm\linux_compat.c include\utsm\linux_compat.h \
|
||||
include\utsm\types.h include\utsm\linux_resume.h \
|
||||
include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h include\utsm\vmm.h \
|
||||
include\utsm\log.h include\utsm\block.h vmm\..\..\tools\fat32_io.h
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_resume.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\block.h:
|
||||
vmm\..\..\tools\fat32_io.h:
|
||||
@@ -309,6 +309,46 @@ static void *linux_find_initrd_module(u64 *size_out) {
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== rootfs module finder =====
|
||||
*
|
||||
* 在 Limine boot module 中查找 Arch rootfs 镜像(路径含 "rootfs")。
|
||||
* 返回模块数据指针,*size_out 为大小;未找到返回 NULL。
|
||||
* 该镜像通过 virtio-blk(memory-backed)暴露给 Linux guest 作为 /dev/vdb。 */
|
||||
void *linux_find_rootfs_module(u64 *size_out) {
|
||||
if (!g_module_request.response) return (void *)0;
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
/* 匹配 "rootfs"(如 linux-rootfs.img) */
|
||||
if ((s[0] == 'r' || s[0] == 'R') &&
|
||||
(s[1] == 'o' || s[1] == 'O') &&
|
||||
(s[2] == 'o' || s[2] == 'O') &&
|
||||
(s[3] == 't' || s[3] == 'T') &&
|
||||
(s[4] == 'f' || s[4] == 'F') &&
|
||||
(s[5] == 's' || s[5] == 'S')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[LINUX] found rootfs module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[LINUX] rootfs size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_warn("[LINUX] no rootfs module found");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== bzImage parser ===== */
|
||||
|
||||
int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
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:
|
||||
@@ -0,0 +1,8 @@
|
||||
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:
|
||||
@@ -252,3 +252,199 @@ void virtio_blk_backend_init(void) {
|
||||
|
||||
virtio_mmio_register(&g_blk_backend);
|
||||
}
|
||||
|
||||
/* ===== Memory-backed rootfs virtio-blk 后端 =====
|
||||
*
|
||||
* 第二个 virtio-blk 设备,后端为内存中的 Arch rootfs 镜像(Limine boot module)。
|
||||
* guest 看到 /dev/vdb,挂载为真实根文件系统(ext4),switch_root 进入 Arch。
|
||||
*
|
||||
* 读写直接操作内存(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 单位) */
|
||||
|
||||
static u32 rootfs_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_rootfs_capacity;
|
||||
cfg.size_max = 128;
|
||||
cfg.seg_max = 32;
|
||||
cfg.blk_size = 512;
|
||||
if (offset + 4 <= sizeof(cfg)) {
|
||||
u32 val = 0;
|
||||
rf_memcpy(&val, c + offset, 4);
|
||||
return val;
|
||||
}
|
||||
if (offset < sizeof(cfg)) {
|
||||
u32 val = 0;
|
||||
rf_memcpy(&val, c + offset, 4);
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case VIRTIO_BLK_T_FLUSH:
|
||||
result = VIRTIO_BLK_S_OK;
|
||||
break;
|
||||
default:
|
||||
result = VIRTIO_BLK_S_UNSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status) *status = result;
|
||||
return bytes_done;
|
||||
}
|
||||
|
||||
static u16 g_rootfs_last_avail;
|
||||
|
||||
static void rootfs_blk_queue_notify(u32 queue_idx) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_BLOCK, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
u16 cur = g_rootfs_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u32 len = rootfs_blk_handle_chain(desc, head, qnum);
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = len;
|
||||
used_idx++;
|
||||
cur++;
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_rootfs_last_avail = cur;
|
||||
}
|
||||
|
||||
static void rootfs_blk_reset(void) {
|
||||
g_rootfs_last_avail = 0;
|
||||
}
|
||||
|
||||
static struct virtio_backend g_rootfs_blk_backend = {
|
||||
.device_id = VIRTIO_ID_BLOCK,
|
||||
.gpa_base = VIRTIO_MMIO_ROOTFS_GPA,
|
||||
.irq = 7, /* guest ISA IRQ7(cmdline :7,vector 0x37) */
|
||||
.num_queues = 1,
|
||||
.queue_size = 128,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1) |
|
||||
(1ULL << VIRTIO_BLK_F_SIZE_MAX) |
|
||||
(1ULL << VIRTIO_BLK_F_SEG_MAX) |
|
||||
(1ULL << VIRTIO_BLK_F_BLK_SIZE) |
|
||||
(1ULL << VIRTIO_BLK_F_FLUSH),
|
||||
.config_len = sizeof(struct virtio_blk_config),
|
||||
.read_config = rootfs_blk_read_config,
|
||||
.queue_notify = rootfs_blk_queue_notify,
|
||||
.reset = rootfs_blk_reset,
|
||||
};
|
||||
|
||||
void virtio_rootfs_blk_init(void) {
|
||||
u64 rootfs_size = 0;
|
||||
void *rootfs = linux_find_rootfs_module(&rootfs_size);
|
||||
if (!rootfs || rootfs_size == 0) {
|
||||
log_warn("[VBLK] no rootfs module, rootfs-blk backend not registered");
|
||||
return;
|
||||
}
|
||||
|
||||
g_rootfs_data = (const u8 *)rootfs;
|
||||
g_rootfs_size = rootfs_size;
|
||||
g_rootfs_capacity = rootfs_size / 512; /* 扇区数 */
|
||||
if (rootfs_size % 512) g_rootfs_capacity++;
|
||||
|
||||
log_info("[VBLK] rootfs-blk backend bound to memory module");
|
||||
log_hex64("[VBLK] rootfs size=", rootfs_size);
|
||||
log_hex64("[VBLK] rootfs capacity(sectors)=", g_rootfs_capacity);
|
||||
|
||||
virtio_mmio_register(&g_rootfs_blk_backend);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
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:
|
||||
@@ -271,6 +271,18 @@ void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx) {
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
}
|
||||
|
||||
void virtio_mmio_raise_irq(u32 device_id) {
|
||||
for (int i = 0; i < g_vdev_count; i++) {
|
||||
struct virtio_dev_state *s = &g_vdevs[i];
|
||||
if (!s->be || s->be->device_id != device_id) continue;
|
||||
s->interrupt_status |= 1;
|
||||
if (s->be->irq != 0) {
|
||||
vmx_guest_queue_irq(0x30 + s->be->irq);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 框架初始化 =====
|
||||
*
|
||||
* 注册 block 与 net 后端。后端结构体由 virtio_blk.c / virtio_net.c 提供。 */
|
||||
@@ -281,6 +293,7 @@ extern struct virtio_backend g_virtio_net_backend;
|
||||
* 这里声明以便链接;具体注册在各自文件完成。 */
|
||||
void virtio_blk_backend_init(void);
|
||||
void virtio_net_backend_init(void);
|
||||
void virtio_rootfs_blk_init(void); /* memory-backed Arch rootfs (/dev/vdb) */
|
||||
|
||||
void virtio_mmio_init(void) {
|
||||
if (g_vio_ready) return;
|
||||
@@ -288,6 +301,10 @@ void virtio_mmio_init(void) {
|
||||
|
||||
virtio_blk_backend_init();
|
||||
virtio_net_backend_init();
|
||||
/* Register memory-backed rootfs backend (no-op if no rootfs module found).
|
||||
* Must come after blk/net so /dev/vda (SATA) and /dev/vdb (rootfs) order
|
||||
* is deterministic. */
|
||||
virtio_rootfs_blk_init();
|
||||
|
||||
g_vio_ready = 1;
|
||||
log_info("[VIO] virtio-mmio backend init ok");
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
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:
|
||||
+152
-45
@@ -5,12 +5,12 @@
|
||||
*
|
||||
* Virtio Spec 1.1 §5.1 (Network Device)。
|
||||
* 队列:0=RX(device→driver 收包),1=TX(driver→device 发包)。
|
||||
* 每帧前置 virtio_net_hdr(10/12 字节,无 GSO 时全零)。
|
||||
* 每帧前置 virtio_net_hdr(10 字节,无 GSO/MRG_RXBUF 协商时全零)。
|
||||
*
|
||||
* 最小可用实现:
|
||||
* - 无 GSO/TSO/checksum offload(feature 不声明,guest 走软件路径)
|
||||
* - RX 轮询:guest RX 队列提交空 buffer,queue_notify 时尝试 rx_poll
|
||||
* - TX:queue_notify 时取帧直接 net->tx 发出
|
||||
* - RX:guest 提交空 buffer,queue_notify / 周期 poll 时调 host rx_poll 填充
|
||||
* - TX:queue_notify 时拼接描述符链成帧,直接 net->tx 发出
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
@@ -32,7 +32,7 @@ struct virtio_net_hdr {
|
||||
|
||||
#define VIRTIO_NET_HDR_LEN 10
|
||||
|
||||
/* feature bits(我们都不声明,保持最简) */
|
||||
/* feature bits(除 MAC/STATUS 外都不声明,保持最简) */
|
||||
#define VIRTIO_NET_F_CSUM 0
|
||||
#define VIRTIO_NET_F_GUEST_CSUM 1
|
||||
#define VIRTIO_NET_F_MAC 5
|
||||
@@ -48,6 +48,25 @@ struct virtio_net_config {
|
||||
static u8 g_net_mac[6] = { 0x52, 0x54, 0x00, 0xDE, 0x5A, 0x01 };
|
||||
static int g_net_bound = -1;
|
||||
|
||||
/* ===== 数据路径统计(低频串口摘要,便于现场诊断 guest 网络问题) ===== */
|
||||
static u64 g_stat_rx_filled; /* 已填充给 guest 的 RX 帧数 */
|
||||
static u64 g_stat_rx_drop_nobuf; /* host 有包但 guest 无可用 RX buffer → 主动丢弃 */
|
||||
static u64 g_stat_rx_badbuf; /* guest RX 描述符异常(未映射/hdr 太小/无 data 段) */
|
||||
static u64 g_stat_tx_sent; /* 成功送交 host 网卡的 TX 帧数 */
|
||||
static u64 g_stat_tx_drop; /* TX 丢弃帧数(链异常/超 MTU/host 发送失败) */
|
||||
|
||||
/* 每 512 次数据路径事件打印一行摘要(避免刷串口)。 */
|
||||
static void net_stat_tick(void) {
|
||||
u64 sum = g_stat_rx_filled + g_stat_rx_drop_nobuf + g_stat_rx_badbuf +
|
||||
g_stat_tx_sent + g_stat_tx_drop;
|
||||
if ((sum & 0x1FF) != 0) return;
|
||||
log_hex64("[VNET] stat rx_filled=", g_stat_rx_filled);
|
||||
log_hex64("[VNET] stat rx_drop_nobuf=", g_stat_rx_drop_nobuf);
|
||||
log_hex64("[VNET] stat rx_badbuf=", g_stat_rx_badbuf);
|
||||
log_hex64("[VNET] stat tx_sent=", g_stat_tx_sent);
|
||||
log_hex64("[VNET] stat tx_drop=", g_stat_tx_drop);
|
||||
}
|
||||
|
||||
static u32 net_read_config(u32 offset, int width) {
|
||||
(void)width;
|
||||
struct virtio_net_config cfg;
|
||||
@@ -64,31 +83,31 @@ static u32 net_read_config(u32 offset, int width) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 从描述符链拼接一个连续帧(virtio-net 帧通常 2 个描述符:hdr + data)。
|
||||
* 返回帧长度,数据拷入 out_buf(out_cap 容量)。 */
|
||||
/* 从 TX 描述符链拼接一个连续帧到 out_buf(out_cap 容量),返回帧长度。
|
||||
* 第一个描述符前置 virtio_net_hdr:Linux 驱动把 hdr 作为【独立的 10 字节
|
||||
* 描述符】(len 恰好等于 VIRTIO_NET_HDR_LEN),必须无条件跳过首描述符的
|
||||
* 前 min(len,10) 字节,否则 hdr 会被拼进帧首导致 host 发出坏包。
|
||||
* 任一段放不下 out_cap(超 MTU 截断)或描述符 GPA 未映射时返回 0(整帧丢弃)。 */
|
||||
static u32 net_gather_frame(struct virtq_desc *desc, u16 head,
|
||||
u8 *out_buf, u32 out_cap, int skip_hdr) {
|
||||
u8 *out_buf, u32 out_cap) {
|
||||
u32 total = 0;
|
||||
u16 cur = head;
|
||||
int hops = 0;
|
||||
while (hops < 32) {
|
||||
u8 *src = (u8 *)virtio_gpa_to_host(desc[cur].addr);
|
||||
u32 len = desc[cur].len;
|
||||
if (!src) break;
|
||||
if (!src) return 0;
|
||||
|
||||
/* 跳过第一个描述符中的 virtio_net_hdr */
|
||||
if (hops == 0 && skip_hdr && len > VIRTIO_NET_HDR_LEN) {
|
||||
u32 dl = len - VIRTIO_NET_HDR_LEN;
|
||||
if (total + dl <= out_cap) {
|
||||
u8 *d = src + VIRTIO_NET_HDR_LEN;
|
||||
for (u32 i = 0; i < dl; i++) out_buf[total + i] = d[i];
|
||||
total += dl;
|
||||
}
|
||||
} else {
|
||||
if (total + len <= out_cap) {
|
||||
for (u32 i = 0; i < len; i++) out_buf[total + i] = src[i];
|
||||
total += len;
|
||||
}
|
||||
/* 跳过首描述符中的 virtio_net_hdr(hdr 与数据同段时也正确) */
|
||||
if (hops == 0) {
|
||||
u32 skip = len < VIRTIO_NET_HDR_LEN ? len : VIRTIO_NET_HDR_LEN;
|
||||
src += skip;
|
||||
len -= skip;
|
||||
}
|
||||
if (len > 0) {
|
||||
if (total + len > out_cap) return 0;
|
||||
for (u32 i = 0; i < len; i++) out_buf[total + i] = src[i];
|
||||
total += len;
|
||||
}
|
||||
|
||||
if (!(desc[cur].flags & VIRTQ_DESC_F_NEXT)) break;
|
||||
@@ -115,65 +134,143 @@ static void net_handle_tx(void) {
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u32 flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch), 1);
|
||||
if (flen > 0 && net && g_net_bound >= 0) {
|
||||
net->tx((u32)g_net_bound, g_tx_scratch, flen);
|
||||
u32 flen = 0;
|
||||
if (head < qnum) {
|
||||
flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch));
|
||||
}
|
||||
if (flen > 0 && net && g_net_bound >= 0) {
|
||||
if (net->tx((u32)g_net_bound, g_tx_scratch, flen) == 0) {
|
||||
g_stat_tx_sent++;
|
||||
} else {
|
||||
/* host ring 满/发送失败:丢帧(guest 不感知 TX 成败) */
|
||||
g_stat_tx_drop++;
|
||||
}
|
||||
} else {
|
||||
/* 链异常/超 MTU 截断保护/未绑定网卡:丢帧 */
|
||||
g_stat_tx_drop++;
|
||||
}
|
||||
/* TX buffer 必须回执(guest 回收 skb),len 字段对 TX 无意义 */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
cur++;
|
||||
net_stat_tick();
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_tx_last_avail = cur;
|
||||
}
|
||||
|
||||
/* RX queue(idx=0):guest 提交空 buffer,我们尝试 rx_poll 填充。
|
||||
* guest 每提交一批 buffer 就 notify 一次;有包则填充 + used,无包则
|
||||
* 保留 buffer 等下次 notify(简化:直接返回 0 长度 used,让 guest 重投)。 */
|
||||
/* RX queue(idx=0):guest 提交空 buffer,我们调 host rx_poll 填充。
|
||||
*
|
||||
* buffer 所有权规则(关键,避免泄漏):
|
||||
* - guest 一次性投满 RX 队列后只 notify 一次,之后等 used 事件。
|
||||
* - 有包:填充 hdr+data → used ring 回执 → guest 收包后重投 buffer。
|
||||
* - 无包:【必须停止且不消耗该 buffer】(g_rx_last_avail 保持指向它),
|
||||
* 由 virtio_net_poll() 在 preemption timer 路径周期性重试;
|
||||
* 否则 buffer 被跳过且永远不进 used ring → guest RX buffer 耗尽饿死。
|
||||
* - guest 无可用 buffer 时:主动把 host 收到的包收出来丢弃并统计,
|
||||
* 避免 e1000 8 深 RX ring 堵死后网卡级无差别丢包(virtio 语义上
|
||||
* device 在 RX 无 buffer 时丢包是标准行为)。
|
||||
* 填充 ≥1 个 buffer 后由调用方负责注入 guest IRQ。 */
|
||||
static u16 g_rx_last_avail;
|
||||
static u8 g_rx_drop_scratch[2048]; /* 无 guest buffer 时的丢弃暂存 */
|
||||
|
||||
static void net_handle_rx(void) {
|
||||
/* 解析 RX 描述符链的 data 接收区(跳过首描述符的 hdr 区)。
|
||||
* 支持两种布局:
|
||||
* A. hdr(10B, W) + data(N, W) 两个描述符(Linux add_recvbuf_small 布局)
|
||||
* B. hdr+data 同在一个描述符(len > 10,data 从偏移 10 起)
|
||||
* 成功返回 0 并填充 datap/datacap/hdrp 输出参数;失败返回 -1(链异常)。 */
|
||||
static int net_rx_data_region(struct virtq_desc *desc, u16 head,
|
||||
u8 **datap, u32 *datacap, u8 **hdrp) {
|
||||
struct virtq_desc *d0 = &desc[head];
|
||||
u8 *hdr = (u8 *)virtio_gpa_to_host(d0->addr);
|
||||
if (!hdr || d0->len < VIRTIO_NET_HDR_LEN) return -1;
|
||||
*hdrp = hdr;
|
||||
|
||||
if (d0->len > VIRTIO_NET_HDR_LEN) {
|
||||
/* 布局 B:hdr 与 data 同段 */
|
||||
if (!(d0->flags & VIRTQ_DESC_F_WRITE)) return -1;
|
||||
*datap = hdr + VIRTIO_NET_HDR_LEN;
|
||||
*datacap = d0->len - VIRTIO_NET_HDR_LEN;
|
||||
return 0;
|
||||
}
|
||||
/* 布局 A:hdr 独立描述符,data 在 NEXT 段 */
|
||||
if (!(d0->flags & VIRTQ_DESC_F_NEXT)) return -1;
|
||||
struct virtq_desc *d1 = &desc[d0->next];
|
||||
if (!(d1->flags & VIRTQ_DESC_F_WRITE)) return -1;
|
||||
u8 *data = (u8 *)virtio_gpa_to_host(d1->addr);
|
||||
if (!data || d1->len == 0) return -1;
|
||||
*datap = data;
|
||||
*datacap = d1->len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 处理 RX 队列。返回本次填充的 buffer 数(0 = 无包/无可填)。 */
|
||||
static int net_handle_rx(void) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 0, &desc, &avail, &used, &qnum) != 0)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
const dkm_net_api *net = net_get_api();
|
||||
if (!net || g_net_bound < 0) return;
|
||||
if (!net || g_net_bound < 0) return 0;
|
||||
|
||||
int filled = 0;
|
||||
u16 cur = g_rx_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
/* RX 描述符链:hdr(W) + data(W)。尝试收一包。 */
|
||||
u16 curd = head;
|
||||
u8 *hdrp = (u8 *)virtio_gpa_to_host(desc[curd].addr);
|
||||
u8 *datap = (u8 *)0;
|
||||
u32 datalen = 0;
|
||||
if (desc[curd].flags & VIRTQ_DESC_F_NEXT) {
|
||||
u16 di = desc[curd].next;
|
||||
datap = (u8 *)virtio_gpa_to_host(desc[di].addr);
|
||||
datalen = desc[di].len;
|
||||
int budget = 32; /* 单次调用处理上限,避免 vmexit/poll 路径停留过久 */
|
||||
|
||||
while (budget-- > 0) {
|
||||
if (cur == avail->idx) {
|
||||
/* guest 无可用 RX buffer:主动收包丢弃,防 host RX ring 堵死 */
|
||||
u32 got = 0;
|
||||
if (net->rx_poll((u32)g_net_bound, g_rx_drop_scratch,
|
||||
sizeof(g_rx_drop_scratch), &got) == 0 && got > 0) {
|
||||
g_stat_rx_drop_nobuf++;
|
||||
net_stat_tick();
|
||||
continue;
|
||||
}
|
||||
break; /* host 也无包 */
|
||||
}
|
||||
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u8 *hdrp = (u8 *)0;
|
||||
u8 *datap = (u8 *)0;
|
||||
u32 datacap = 0;
|
||||
if (head >= qnum ||
|
||||
net_rx_data_region(desc, head, &datap, &datacap, &hdrp) != 0) {
|
||||
/* 异常 buffer:len=0 回执让 guest 回收重投,避免卡死 */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
g_stat_rx_badbuf++;
|
||||
net_stat_tick();
|
||||
continue;
|
||||
}
|
||||
if (!hdrp || !datap || datalen == 0) { cur++; continue; }
|
||||
|
||||
u32 got = 0;
|
||||
if (net->rx_poll((u32)g_net_bound, datap, datalen, &got) == 0 && got > 0) {
|
||||
if (net->rx_poll((u32)g_net_bound, datap, datacap, &got) == 0 && got > 0) {
|
||||
/* 填 virtio_net_hdr 全零(无 GSO) */
|
||||
for (int i = 0; i < VIRTIO_NET_HDR_LEN; i++) hdrp[i] = 0;
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = VIRTIO_NET_HDR_LEN + got;
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
g_stat_rx_filled++;
|
||||
net_stat_tick();
|
||||
} else {
|
||||
/* 无包:不消耗该 buffer,留待下次(但为避免死循环,跳过) */
|
||||
/* 无包:停止,不消耗 cur 指向的 buffer,下次 poll 重试 */
|
||||
break;
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_rx_last_avail = cur;
|
||||
return filled;
|
||||
}
|
||||
|
||||
static void net_queue_notify(u32 queue_idx) {
|
||||
@@ -216,3 +313,13 @@ void virtio_net_backend_init(void) {
|
||||
}
|
||||
virtio_mmio_register(&g_net_backend);
|
||||
}
|
||||
|
||||
/* 周期轮询入口(vmexit preemption timer 路径调用,~1kHz)。
|
||||
* guest 投满 RX buffer 后不再 notify,host 侧包到达时靠此轮询填充
|
||||
* 并注入 IRQ,guest 才能及时收包(pacman/ping 等依赖)。 */
|
||||
void virtio_net_poll(void) {
|
||||
if (g_net_bound < 0) return;
|
||||
if (net_handle_rx() > 0) {
|
||||
virtio_mmio_raise_irq(VIRTIO_ID_NET);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
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:
|
||||
+126
-2
@@ -8,6 +8,7 @@
|
||||
#include <utsm/dma.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/instr.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
/* VM-Exit 处理器:从 VMCS 读取 exit reason 与 guest 状态,分发处理。
|
||||
@@ -36,6 +37,17 @@ extern void serial_putc(char c);
|
||||
|
||||
static u64 g_vmexit_count;
|
||||
|
||||
/* ---- 插桩: VM-Exit 统计计数器 (热路径,仅 STAT_INC) ---- */
|
||||
INSTR_STAT_DECL(vmexit_total);
|
||||
INSTR_STAT_DECL(ept_violation);
|
||||
INSTR_STAT_DECL(ext_irq);
|
||||
INSTR_STAT_DECL(cpuid_exit);
|
||||
INSTR_STAT_DECL(io_exit);
|
||||
INSTR_STAT_DECL(intr_window);
|
||||
INSTR_STAT_DECL(preempt_timer);
|
||||
INSTR_STAT_DECL(msr_exit);
|
||||
INSTR_STAT_DECL(pre_resume);
|
||||
|
||||
static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len) {
|
||||
log_hex64("[VMEXIT] reason=", reason);
|
||||
log_hex64("[VMEXIT] qual=", qualification);
|
||||
@@ -473,11 +485,14 @@ int vmx_preemption_timer_supported(void) {
|
||||
|
||||
/* 每次 vmresume 前调用(vmexit_dispatch resume=1 路径末尾)。
|
||||
* 重新 arm preemption timer(1ms 周期 exit),轮询 host 串口 RX,
|
||||
* 推进 PIT tick,注入 pending IRQ。 */
|
||||
* 推进 PIT tick,注入 pending IRQ。
|
||||
* 同时轮询 virtio-net RX:guest 投满 RX buffer 后不再 notify,
|
||||
* host 网卡收到的包靠这里周期性填充并注入 IRQ6。 */
|
||||
static void vmexit_before_resume(void) {
|
||||
if (!g_linux_guest_active) return;
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
poll_host_serial_to_guest();
|
||||
virtio_net_poll();
|
||||
pit_tick_update();
|
||||
maybe_inject_irq();
|
||||
}
|
||||
@@ -677,36 +692,70 @@ int vmexit_dispatch(void) {
|
||||
int resume = 0;
|
||||
|
||||
g_vmexit_count++;
|
||||
INSTR_STAT_INC(vmexit_total);
|
||||
|
||||
switch (reason) {
|
||||
case EXIT_HLT:
|
||||
handle_hlt(rip, instr_len, &resume);
|
||||
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;
|
||||
break;
|
||||
case EXIT_EXCEPTION_NMI:
|
||||
INSTR_PROBE(EXCP, qualification, rip, 0, 0);
|
||||
handle_exception(qualification, rip, &resume);
|
||||
break;
|
||||
case EXIT_TRIPLE_FAULT:
|
||||
INSTR_PROBE(EXCP, 0, rip, 0, 0);
|
||||
log_error("[VMEXIT] triple fault");
|
||||
resume = 0;
|
||||
break;
|
||||
case EXIT_EXTERNAL_INTERRUPT:
|
||||
INSTR_STAT_INC(ext_irq);
|
||||
/* 外部中断到达 host(真机比 QEMU 更频繁触发)。
|
||||
* 当前 UTSM 不处理 host 外部中断(依赖 PIC/IDT),
|
||||
* 直接 resume guest。真机上此 exit 由 PIN_EXT_INTERRUPT_EXITING
|
||||
* 触发,不应终止 guest。 */
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_INIT_SIGNAL:
|
||||
/* INIT 信号在 VMX root 下被阻断,但 non-root 会 exit。
|
||||
* 真机 BSP 向 AP 发 INIT-IPI-SIPI 时可能触发。
|
||||
* 吞掉:不执行 INIT,直接 resume。 */
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_INVD:
|
||||
/* INVD(cache flush):吞掉,直接 resume。 */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_MONITOR:
|
||||
case EXIT_MWAIT:
|
||||
/* MONITOR/MWAIT:在 noapic 模式下 Linux 不会主动使用,
|
||||
* 但某些驱动路径可能触发。直接 resume。 */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_CPUID:
|
||||
INSTR_STAT_INC(cpuid_exit);
|
||||
/* host passthrough + 屏蔽 VMX/hypervisor 位(handle_cpuid 内推进 RIP) */
|
||||
handle_cpuid(rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_IO_INSTRUCTION:
|
||||
INSTR_STAT_INC(io_exit);
|
||||
/* legacy 设备模拟:COM1 UART / PIC / PIT / 其余端口吞掉 */
|
||||
handle_io(qualification, rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_INTERRUPT_WINDOW:
|
||||
INSTR_STAT_INC(intr_window);
|
||||
/* guest 刚开中断(RFLAGS.IF=1):尝试注入 pending IRQ */
|
||||
{
|
||||
u64 cpu = vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL);
|
||||
@@ -717,16 +766,90 @@ 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 */
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_RDMSR:
|
||||
case EXIT_WRMSR:
|
||||
case EXIT_WRMSR: {
|
||||
INSTR_STAT_INC(msr_exit);
|
||||
/* 真机 MSR 访问比 QEMU 严格得多。Linux guest 会访问许多 MSR:
|
||||
* - IA32_MISC_ENABLE (0x1A0): QEMU 忽略,真机需要返回合理值
|
||||
* - IA32_APIC_BASE (0x1B): APIC base 地址
|
||||
* - IA32_TSC_AUX (0xC0000103): RDTSCP auxiliary
|
||||
* - IA32_FS_BASE/GS_BASE (0xC0000100/1): 已由 VMCS 管理
|
||||
* - IA32_KERNEL_GS_BASE (0xC0000102): swapgs 目标
|
||||
* - IA32_BNDCFGS (0xD90): MPX bound config(某些 Intel 有)
|
||||
* 当前策略:直接 passthrough host MSR 值(读),或忽略写。
|
||||
* 注意:WRMSR 写入某些 MSR 可能影响 host,需要白名单保护。
|
||||
*/
|
||||
u64 msr = g_guest_regs.rcx & 0xFFFFFFFFULL;
|
||||
if (reason == EXIT_RDMSR) {
|
||||
u64 val = 0;
|
||||
/* 白名单:允许读取的 MSR(passthrough host 值) */
|
||||
int allowed = 0;
|
||||
/* 通用允许列表 */
|
||||
if (msr == 0x1A0 || /* IA32_MISC_ENABLE */
|
||||
msr == 0x1B || /* IA32_APIC_BASE */
|
||||
msr == 0xC0000100 || /* IA32_FS_BASE */
|
||||
msr == 0xC0000101 || /* IA32_GS_BASE */
|
||||
msr == 0xC0000102 || /* IA32_KERNEL_GS_BASE */
|
||||
msr == 0xC0000103 || /* IA32_TSC_AUX */
|
||||
msr == 0xD90 || /* IA32_BNDCFGS */
|
||||
msr == 0x3A || /* IA32_FEATURE_CONTROL */
|
||||
msr == 0x8B || /* IA32_BIOS_SIGN (microcode) */
|
||||
msr == 0x17 || /* IA32_PLATFORM_ID */
|
||||
msr == 0x4B || /* IA32_UCODE_REV (some CPUs) */
|
||||
msr == 0xCE || /* IA32_PLATFORM_INFO */
|
||||
msr == 0x198 || /* IA32_PERF_STATUS */
|
||||
msr == 0x199 || /* IA32_PERF_CTL */
|
||||
msr == 0x1AD || /* IA32_THREAD_FEEDBACK_CHAR (Raptor Lake) */
|
||||
msr == 0x1AE || /* IA32_CORE_THREAD_COUNT */
|
||||
(msr >= 0x0 && msr <= 0x1F) || /* Basic CPUID MSRs */
|
||||
(msr >= 0x30 && msr <= 0x3F) || /* PMC MSRs */
|
||||
(msr >= 0x186 && msr <= 0x18F) || /* Perf MSRs */
|
||||
(msr >= 0xC0000000 && msr <= 0xC0000103) /* SYSENTER/EFER etc */
|
||||
) {
|
||||
allowed = 1;
|
||||
}
|
||||
if (allowed) {
|
||||
/* 安全读取:用 rdmsr 可能 #GP,用 try-catch 模式 */
|
||||
__asm__ volatile(
|
||||
"1: rdmsr\n"
|
||||
" jmp 2f\n"
|
||||
".section .text\n"
|
||||
"2:\n"
|
||||
: "=a"(((u32*)&val)[0]), "=d"(((u32*)&val)[1])
|
||||
: "c"(msr)
|
||||
);
|
||||
}
|
||||
g_guest_regs.rax = val & 0xFFFFFFFFULL;
|
||||
g_guest_regs.rdx = (val >> 32) & 0xFFFFFFFFULL;
|
||||
} 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 */
|
||||
msr == 0xC0000103 || /* IA32_TSC_AUX */
|
||||
msr == 0x1B || /* IA32_APIC_BASE — 忽略(guest noapic) */
|
||||
msr == 0xD90 || /* IA32_BNDCFGS — 忽略 */
|
||||
msr == 0x199 /* IA32_PERF_CTL — 忽略 */
|
||||
) {
|
||||
allowed = 1;
|
||||
}
|
||||
/* 不允许的写直接吞掉 */
|
||||
if (!allowed) {
|
||||
log_hex64("[VMEXIT] WRMSR blocked msr=", msr);
|
||||
}
|
||||
}
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
break;
|
||||
}
|
||||
case EXIT_VMCALL:
|
||||
INSTR_PROBE(HCALL, g_guest_regs.rax, g_guest_regs.rdi, g_guest_regs.rsi, rip);
|
||||
/* VMCALL hypercall: read guest GPRs from g_guest_regs (saved by
|
||||
* vmexit_asm.S), dispatch to the hypercall handler, and write
|
||||
* the return value into g_guest_regs.rax. The handler also
|
||||
@@ -750,6 +873,7 @@ int vmexit_dispatch(void) {
|
||||
/* resume 前例行工作:arm preemption timer + host 串口 RX 轮询 +
|
||||
* PIT tick 推进 + pending IRQ 注入(仅 Linux guest active 时) */
|
||||
if (resume) {
|
||||
INSTR_STAT_INC(pre_resume);
|
||||
vmexit_before_resume();
|
||||
}
|
||||
return resume;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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:
|
||||
@@ -402,6 +402,10 @@ int vmm_init(void) {
|
||||
|
||||
g_vmm_ready = 1;
|
||||
log_info("[VMM] init ok");
|
||||
|
||||
/* 真机支持检查:INVVPID/INVEPT */
|
||||
ept_check_vpid_support();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
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
@@ -15,7 +15,7 @@ u64 vmx_read_msr(u32 msr) {
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
static void vmx_write_msr(u32 msr, u64 value) {
|
||||
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
|
||||
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
||||
u32 hi = (u32)(value >> 32);
|
||||
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
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:
|
||||
Reference in New Issue
Block a user