diff --git a/CODE/UTSM/include/utsm/ipc_native.h b/CODE/UTSM/include/utsm/ipc_native.h new file mode 100644 index 0000000..a536843 --- /dev/null +++ b/CODE/UTSM/include/utsm/ipc_native.h @@ -0,0 +1,135 @@ +#ifndef UTSM_IPC_NATIVE_H +#define UTSM_IPC_NATIVE_H + +#include + +/* + * Experimental native Deshab capability IPC ABI. + * + * This is intentionally distinct from CODE/utsm-ipc/ipc_proto.h, which is a + * UTSM <-> Linux guest transport. No native queue implementation or syscall + * binding exists yet. All ABI fields use bounded fixed-width Deshab types; + * pointers and implementation-owned kernel addresses never appear on wire. + */ + +#define UTSM_IPC_NATIVE_MAGIC 0x44534950u /* "DSIP" */ +#define UTSM_IPC_NATIVE_ABI_MAJOR 1u +#define UTSM_IPC_NATIVE_ABI_MINOR 0u + +#define UTSM_IPC_NATIVE_MESSAGE_BYTES 256u +#define UTSM_IPC_NATIVE_PAYLOAD_BYTES 144u +#define UTSM_IPC_NATIVE_MAX_CAPS 4u +#define UTSM_IPC_NATIVE_MIN_DEPTH 2u +#define UTSM_IPC_NATIVE_MAX_DEPTH 4096u +#define UTSM_IPC_NATIVE_DEADLINE_NONE (~0ULL) + +/* + * The rights field requests or describes attenuation. Authority comes from + * the calling task's kernel capability table; copying or editing this value + * must never create authority. + */ +typedef struct utsm_ipc_native_cap { + u64 object_id; + u32 generation; + u32 rights; +} utsm_ipc_native_cap; + +typedef enum utsm_ipc_native_right { + UTSM_IPC_RIGHT_SEND = (1u << 0), + UTSM_IPC_RIGHT_RECEIVE = (1u << 1), + UTSM_IPC_RIGHT_CALL = (1u << 2), + UTSM_IPC_RIGHT_REPLY = (1u << 3), + UTSM_IPC_RIGHT_TRANSFER = (1u << 4), + UTSM_IPC_RIGHT_DUPLICATE = (1u << 5), + UTSM_IPC_RIGHT_MANAGE = (1u << 6), + UTSM_IPC_RIGHT_WAIT = (1u << 7), + UTSM_IPC_RIGHT_SIGNAL = (1u << 8) +} utsm_ipc_native_right; + +#define UTSM_IPC_RIGHT_ALL ((u32)((1u << 9) - 1u)) + +/* + * Queue operations are carried in a u16 ABI field. CALL creates a transaction + * ID; REPLY must name that transaction. WAIT/SIGNAL are queue-state events, + * not substitutes for SEND/RECEIVE. + */ +typedef enum utsm_ipc_native_queue_op { + UTSM_IPC_QUEUE_CREATE = 1, + UTSM_IPC_QUEUE_DESTROY = 2, + UTSM_IPC_QUEUE_SEND = 3, + UTSM_IPC_QUEUE_RECEIVE = 4, + UTSM_IPC_QUEUE_CALL = 5, + UTSM_IPC_QUEUE_REPLY = 6, + UTSM_IPC_QUEUE_WAIT = 7, + UTSM_IPC_QUEUE_SIGNAL = 8, + UTSM_IPC_QUEUE_QUERY = 9 +} utsm_ipc_native_queue_op; + +#define UTSM_IPC_MSG_F_NONBLOCK (1u << 0) +#define UTSM_IPC_MSG_F_MOVE_CAPS (1u << 1) +#define UTSM_IPC_MSG_F_EXPECT_REPLY (1u << 2) +#define UTSM_IPC_MSG_F_CANCEL (1u << 3) + +typedef enum utsm_ipc_native_result { + UTSM_IPC_NATIVE_OK = 0, + UTSM_IPC_NATIVE_ERR_INVALID = -1, + UTSM_IPC_NATIVE_ERR_ACCESS = -2, + UTSM_IPC_NATIVE_ERR_STALE_CAP = -3, + UTSM_IPC_NATIVE_ERR_FULL = -4, + UTSM_IPC_NATIVE_ERR_EMPTY = -5, + UTSM_IPC_NATIVE_ERR_CLOSED = -6, + UTSM_IPC_NATIVE_ERR_TIMEOUT = -7, + UTSM_IPC_NATIVE_ERR_CANCELLED = -8, + UTSM_IPC_NATIVE_ERR_UNSUPPORTED = -38 +} utsm_ipc_native_result; + +/* + * Fixed 256-byte message. message_size must equal sizeof(this structure), + * payload_length is bounded by PAYLOAD_BYTES, and capability_count is bounded + * by MAX_CAPS. Receivers must reject nonzero reserved fields. + */ +typedef struct utsm_ipc_native_message { + u32 magic; + u16 abi_major; + u16 abi_minor; + + u16 operation; /* utsm_ipc_native_queue_op */ + u16 flags; + u32 message_size; + u64 transaction_id; + + utsm_ipc_native_cap endpoint; + u32 payload_length; + u16 capability_count; + u16 reserved0; + + utsm_ipc_native_cap capabilities[UTSM_IPC_NATIVE_MAX_CAPS]; + u8 payload[UTSM_IPC_NATIVE_PAYLOAD_BYTES]; +} utsm_ipc_native_message; + +/* + * Fixed queue-control request for a future syscall dispatcher. depth is valid + * only for CREATE and must stay within MIN_DEPTH..MAX_DEPTH. deadline_ns is + * an absolute monotonic deadline or UTSM_IPC_NATIVE_DEADLINE_NONE. + */ +typedef struct utsm_ipc_native_queue_request { + u16 abi_major; + u16 abi_minor; + u16 operation; /* utsm_ipc_native_queue_op */ + u16 flags; + + u32 depth; + u32 message_size; + utsm_ipc_native_cap queue; + u64 deadline_ns; +} utsm_ipc_native_queue_request; + +_Static_assert(sizeof(utsm_ipc_native_cap) == 16u, + "native IPC capability ABI changed"); +_Static_assert(sizeof(utsm_ipc_native_message) == + UTSM_IPC_NATIVE_MESSAGE_BYTES, + "native IPC message ABI changed"); +_Static_assert(sizeof(utsm_ipc_native_queue_request) == 40u, + "native IPC queue request ABI changed"); + +#endif /* UTSM_IPC_NATIVE_H */ diff --git a/CODE/UTSM/include/utsm/sched.h b/CODE/UTSM/include/utsm/sched.h new file mode 100644 index 0000000..e8831f9 --- /dev/null +++ b/CODE/UTSM/include/utsm/sched.h @@ -0,0 +1,130 @@ +#ifndef UTSM_SCHED_H +#define UTSM_SCHED_H + +#include + +/* + * Experimental SAS-R0-PCQ scheduler contract. + * + * This header reserves the first ABI shape for a future per-CPU O(1) + * priority-bitmap scheduler. It is not connected to boot or context-switch + * code. Structures begin with version/size where they may cross component + * boundaries; existing fields must not be reordered and extensions append. + */ + +#define UTSM_SCHED_ABI_VERSION 1u +#define UTSM_SCHED_PRIORITY_COUNT 64u +#define UTSM_SCHED_INVALID_SLOT 0xffffffffu +#define UTSM_SCHED_PRIORITY_HIGHEST 0u +#define UTSM_SCHED_PRIORITY_LOWEST (UTSM_SCHED_PRIORITY_COUNT - 1u) + +/* Explicit API results. The isolated stub returns UNSUPPORTED for every call. */ +typedef enum utsm_sched_result { + UTSM_SCHED_OK = 0, + UTSM_SCHED_ERR_INVALID = -1, + UTSM_SCHED_ERR_STATE = -2, + UTSM_SCHED_ERR_NO_TASK = -3, + UTSM_SCHED_ERR_UNSUPPORTED = -38 +} utsm_sched_result; + +/* Values are stored in u32 fields so the TCB layout does not depend on enum size. */ +typedef enum utsm_sched_task_state { + UTSM_SCHED_TASK_UNUSED = 0, + UTSM_SCHED_TASK_NEW, + UTSM_SCHED_TASK_READY, + UTSM_SCHED_TASK_RUNNING, + UTSM_SCHED_TASK_BLOCKED, + UTSM_SCHED_TASK_SLEEPING, + UTSM_SCHED_TASK_RECOVERING, + UTSM_SCHED_TASK_FAULTED, + UTSM_SCHED_TASK_ZOMBIE +} utsm_sched_task_state; + +#define UTSM_SCHED_TASK_F_KERNEL (1u << 0) +#define UTSM_SCHED_TASK_F_IDLE (1u << 1) +#define UTSM_SCHED_TASK_F_NO_MIGRATE (1u << 2) +#define UTSM_SCHED_TASK_F_RECOVERY (1u << 3) + +/* + * Minimum x86_64 resume context. Volatile registers belong to the eventual + * interrupt/syscall frame and are deliberately not duplicated here. + */ +typedef struct utsm_sched_arch_context { + u64 rsp; + u64 rip; + u64 rflags; + u64 rbx; + u64 rbp; + u64 r12; + u64 r13; + u64 r14; + u64 r15; +} utsm_sched_arch_context; + +/* + * Task control block prefix. + * + * runq_prev/runq_next are task-table slots, not pointers, so queues remain + * relocatable. priority 0 is highest. crypto_context is loaded as a pointer + * during a future switch; switch code must not scan capabilities or perform + * cryptography/checkpoint work. + */ +typedef struct utsm_sched_tcb { + u32 abi_version; + u32 struct_size; + + u64 task_id; + u32 process_slot; + u32 flags; + + u32 state; /* utsm_sched_task_state */ + u32 priority; + u32 assigned_cpu; + u32 last_cpu; + u64 affinity_mask; + + u64 timeslice_ns; + u64 wake_deadline_ns; + void *crypto_context; + + u32 runq_prev; + u32 runq_next; + utsm_sched_arch_context arch; + + u64 reserved[4]; +} utsm_sched_tcb; + +/* + * One ready bitmap and one intrusive FIFO per priority form the planned O(1) + * run queue. A set bit means that head[priority] is valid. Synchronization is + * an implementation concern and is intentionally not implied by this layout. + */ +typedef struct utsm_sched_run_queue { + u32 abi_version; + u32 struct_size; + u32 cpu_id; + u32 flags; + + u64 ready_bitmap; + u32 head[UTSM_SCHED_PRIORITY_COUNT]; + u32 tail[UTSM_SCHED_PRIORITY_COUNT]; + + u32 current_task_slot; + u32 idle_task_slot; + u32 ready_count; + u32 reserved0; +} utsm_sched_run_queue; + +int utsm_sched_init(void); +int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue); +int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id, + u32 process_slot, u32 priority, u64 affinity_mask); +int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task); +int utsm_sched_dequeue(utsm_sched_tcb *task); +int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state); +int utsm_sched_wake(utsm_sched_tcb *task); +int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task); +int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns, + utsm_sched_tcb **out_preempted_task); + +#endif /* UTSM_SCHED_H */ diff --git a/CODE/UTSM/include/utsm/vmm.h b/CODE/UTSM/include/utsm/vmm.h index e052a5d..2a61c74 100644 --- a/CODE/UTSM/include/utsm/vmm.h +++ b/CODE/UTSM/include/utsm/vmm.h @@ -88,5 +88,7 @@ int vmm_self_test(void); /* VMM 状态查询 */ int vmm_is_ready(void); u64 vmm_get_eptp(void); +/* P8.5: VMCS 物理地址(linux_launch 前 VMCLEAR+VMPTRLD 用) */ +u64 vmm_get_vmcs_phys(void); #endif diff --git a/CODE/UTSM/include/utsm/vmx.h b/CODE/UTSM/include/utsm/vmx.h index c00115c..6e022f2 100644 --- a/CODE/UTSM/include/utsm/vmx.h +++ b/CODE/UTSM/include/utsm/vmx.h @@ -9,6 +9,7 @@ #define IA32_VMX_PROCBASED_CTLS 0x482 #define IA32_VMX_EXIT_CTLS 0x483 #define IA32_VMX_ENTRY_CTLS 0x484 +#define IA32_VMX_MISC 0x485 #define IA32_VMX_PROCBASED_CTLS2 0x48B #define IA32_VMX_EPT_VPID_CAP 0x48C #define IA32_VMX_TRUE_PINBASED_CTLS 0x48D @@ -37,15 +38,9 @@ /* Control fields */ #define VMCS_VPID 0x0000 -#define VMCS_GUEST_ES_ADDR 0x0006 -#define VMCS_GUEST_CS_ADDR 0x0008 -#define VMCS_GUEST_SS_ADDR 0x000A -#define VMCS_GUEST_DS_ADDR 0x000C -#define VMCS_GUEST_FS_ADDR 0x000E -#define VMCS_GUEST_GS_ADDR 0x0010 -#define VMCS_GUEST_LDTR_ADDR 0x0012 -#define VMCS_GUEST_TR_ADDR 0x0014 -#define VMCS_GUEST_INTR_STATUS 0x0009 +/* P8.5: guest interrupt status 真实编码为 0x0810(16-bit guest-state), + * 旧值 0x0009 是 posted-interrupt 描述符区域,属于错误编码。 */ +#define VMCS_GUEST_INTR_STATUS 0x0810 #define VMCS_IO_BITMAP_A 0x2000 #define VMCS_IO_BITMAP_B 0x2002 #define VMCS_MSR_BITMAP 0x2004 @@ -54,15 +49,22 @@ /* Read-only data fields */ #define VMCS_GUEST_PHYSICAL_ADDR 0x2400 +#define VMCS_EXIT_INTR_INFO 0x4404 +#define VMCS_EXIT_INTR_ERROR_CODE 0x4406 #define VMCS_EXIT_REASON 0x4402 #define VMCS_EXIT_QUALIFICATION 0x6400 -#define VMCS_IO_RCX 0x6404 -#define VMCS_IO_RSI 0x6406 -#define VMCS_IO_RDI 0x6408 -#define VMCS_IO_RIP 0x640A +/* P8.5: IO RCX/RSI/RDI/RIP 真实编码为 0x6402/0x6404/0x6406/0x6408, + * 旧值整体右移了一格。guest-linear-address = 0x640A 不变。 */ +#define VMCS_IO_RCX 0x6402 +#define VMCS_IO_RSI 0x6404 +#define VMCS_IO_RDI 0x6406 +#define VMCS_IO_RIP 0x6408 #define VMCS_GUEST_LINEAR_ADDR 0x640A #define VMCS_INSTRUCTION_LENGTH 0x440C -#define VMCS_VMX_INSTRUCTION_ERROR 0x4406 +/* P8.5 关键修复:VM-instruction error 真实编码是 0x4400。 + * 旧值 0x4406 是 "VM-exit interruption error code", + * 导致 vmlaunch 失败后错误码永远读出 0。 */ +#define VMCS_VMX_INSTRUCTION_ERROR 0x4400 /* Guest-state fields */ #define VMCS_GUEST_ES_SELECTOR 0x0800 @@ -82,8 +84,8 @@ #define VMCS_GUEST_LDTR_LIMIT 0x480C #define VMCS_GUEST_TR_LIMIT 0x480E #define VMCS_GUEST_CS_ACCESS 0x4816 -#define VMCS_GUEST_SS_ACCESS 0x481A -#define VMCS_GUEST_DS_ACCESS 0x4818 +#define VMCS_GUEST_SS_ACCESS 0x4818 +#define VMCS_GUEST_DS_ACCESS 0x481A #define VMCS_GUEST_ES_ACCESS 0x4814 #define VMCS_GUEST_FS_ACCESS 0x481C #define VMCS_GUEST_GS_ACCESS 0x481E @@ -165,11 +167,13 @@ #define VMCS_VM_EXIT_CONTROLS 0x400C #define VMCS_VM_ENTRY_CONTROLS 0x4012 #define VMCS_VM_ENTRY_INTERRUPT_INFO 0x4016 +#define VMCS_VM_ENTRY_EXCEPTION_ERROR 0x4018 #define VMCS_SECONDARY_VM_EXEC_CONTROL 0x401E /* ===== Pin-based controls ===== */ #define PIN_EXT_INTERRUPT_EXITING (1ULL << 0) #define PIN_NMI_EXITING (1ULL << 3) +#define PIN_VIRTUAL_NMIS (1ULL << 5) #define PIN_VMX_PREEMPTION_TIMER (1ULL << 6) /* ===== Primary processor-based controls ===== */ @@ -269,11 +273,23 @@ int vmx_vmcs_load(u64 phys); int vmx_vmcs_clear(u64 phys); /* P8.4: VMCLEAR before VMLAUNCH */ u64 vmx_vmcs_read(u64 field); void vmx_vmcs_write(u64 field, u64 value); +/* P8.5: 带状态返回的 VMREAD。 + * 返回 0 成功(CF=0 且 ZF=0);-1 = VMfailValid/VMfailInvalid + * (此时 *value 未更新,多为 current-VMCS 无效)。 */ +int vmx_vmcs_read_checked(u64 field, u64 *value); int vmx_vmlaunch(void); int vmx_vmresume(void); u64 vmx_read_msr(u32 msr); +/* Capability polarity used by KVM and verified under nested KVM: + * low bit=1 => must be 1; high bit=0 => must be 0. + * Formula: (desired | allowed0) & allowed1. */ +u64 vmx_adjust_control(u64 value, u32 msr); u64 vmx_get_host_cr3(void); +/* Convert a pointer inside the linked UTSM kernel image to its physical + * address using Limine's kernel-address response. Kernel-image mappings are + * distinct from the HHDM and must never be translated by subtracting HHDM. */ +u64 vmx_kernel_virt_to_phys(const void *address); /* ===== 虚拟中断注入(vmexit.c 实现) ===== */ /* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。 @@ -281,9 +297,9 @@ u64 vmx_get_host_cr3(void); * 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */ void vmx_guest_queue_irq(u32 vector); -/* VMX preemption timer:返回 1ms 对应的 timer 计数值(按 IA32_VMX_BASIC - * bits[55:48] 的 scale 换算 TSC 频率)。 */ -u64 vmx_preemption_quantum_1ms(void); +/* VMX preemption timer:返回 host polling 周期对应的 timer 计数值。 + * 当前周期为 10ms,避免 nested KVM 在 1ms VM-exit 下失去前进性。 */ +u64 vmx_preemption_quantum(void); /* 检查 CPU 是否支持 VMX preemption timer。 */ int vmx_preemption_timer_supported(void); diff --git a/CODE/UTSM/sched/sched_stub.c b/CODE/UTSM/sched/sched_stub.c new file mode 100644 index 0000000..a65f34a --- /dev/null +++ b/CODE/UTSM/sched/sched_stub.c @@ -0,0 +1,65 @@ +/* + * Isolated scheduler placeholder. + * + * Deliberately not linked by the UTSM build. These functions make accidental + * use fail explicitly until the per-CPU bitmap scheduler and switch path are + * implemented. + */ + +#include + +int utsm_sched_init(void) { + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue) { + UTSM_UNUSED(cpu_id); + UTSM_UNUSED(queue); + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id, + u32 process_slot, u32 priority, u64 affinity_mask) { + UTSM_UNUSED(task); + UTSM_UNUSED(task_id); + UTSM_UNUSED(process_slot); + UTSM_UNUSED(priority); + UTSM_UNUSED(affinity_mask); + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task) { + UTSM_UNUSED(cpu_id); + UTSM_UNUSED(task); + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_dequeue(utsm_sched_tcb *task) { + UTSM_UNUSED(task); + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state) { + UTSM_UNUSED(task); + UTSM_UNUSED(blocked_state); + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_wake(utsm_sched_tcb *task) { + UTSM_UNUSED(task); + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task) { + UTSM_UNUSED(cpu_id); + UTSM_UNUSED(out_task); + return UTSM_SCHED_ERR_UNSUPPORTED; +} + +int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns, + utsm_sched_tcb **out_preempted_task) { + UTSM_UNUSED(cpu_id); + UTSM_UNUSED(elapsed_ns); + UTSM_UNUSED(out_preempted_task); + return UTSM_SCHED_ERR_UNSUPPORTED; +} diff --git a/CODE/UTSM/vmm/ept.c b/CODE/UTSM/vmm/ept.c index 56db3fe..13cb6d4 100644 --- a/CODE/UTSM/vmm/ept.c +++ b/CODE/UTSM/vmm/ept.c @@ -76,8 +76,9 @@ static u64 *ept_walk(u64 gpa, int alloc_missing) { if (!alloc_missing) return (u64 *)0; u64 new_phys = ept_alloc_page(); if (new_phys == 0) return (u64 *)0; - /* 中间页表项:RWX + WB 内存类型 */ - table[indices[level]] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3); + /* 非叶 EPT 表项只含物理地址与 RWX。bits 5:3 在非叶项中 + * 保留,写入 leaf memory type 会触发 EPT misconfiguration。 */ + table[indices[level]] = new_phys | EPT_RWX; /* 切换到新分配的下一级页表 */ table = (u64 *)phys_to_virt(new_phys); } else { @@ -145,7 +146,7 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) { if (!(entry & EPT_READ)) { u64 new_phys = ept_alloc_page(); if (new_phys == 0) return -3; - table[pml4_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3); + table[pml4_idx] = new_phys | EPT_RWX; table = (u64 *)phys_to_virt(new_phys); } else { table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL); @@ -156,7 +157,7 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) { if (!(entry & EPT_READ)) { u64 new_phys = ept_alloc_page(); if (new_phys == 0) return -4; - table[pdpt_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3); + table[pdpt_idx] = new_phys | EPT_RWX; table = (u64 *)phys_to_virt(new_phys); } else { table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL); @@ -210,7 +211,7 @@ void ept_flush_ept(void) { int err; __asm__ volatile( "invept (%2), %1\n" - "jnc 1f\n" + "ja 1f\n" "mov $1, %0\n" "jmp 2f\n" "1:\n" @@ -234,7 +235,7 @@ void ept_flush_vpid(u16 vpid) { int err; __asm__ volatile( "invvpid (%2), %1\n" - "jnc 1f\n" + "ja 1f\n" "mov $1, %0\n" "jmp 2f\n" "1:\n" @@ -296,7 +297,8 @@ u64 ept_gpa_to_hpa(u64 gpa) { u64 ept_get_eptp(void) { if (!g_ept_ready) return 0; - /* EPTP: bits [5:0] = memtype, bits [7:6] = walk length-1 (3 for 4-level), - * bits [51:12] = PML4 HPA */ + /* EPTP: bits [2:0]=memory type WB(6), bits [5:3]=walk length-1 (3 => 4-level). + * Nested KVM accepted WB (eptp=...1e) on the passing self-test; forcing UC + * after a misread of EPT_VPID_CAP bit 8 regressed to error 7. */ return g_ept_pml4_phys | EPTP_WALK_LEN_4 | EPTP_MEMTYPE_WB; } diff --git a/CODE/UTSM/vmm/linux_boot.c b/CODE/UTSM/vmm/linux_boot.c index 5e97ce5..6a08ea8 100644 --- a/CODE/UTSM/vmm/linux_boot.c +++ b/CODE/UTSM/vmm/linux_boot.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include "../arch/x86_64/limine.h" @@ -50,14 +51,6 @@ static inline u64 read_cr4_local(void) { return v; } -static u64 vmx_adjust_control(u64 value, u32 msr) { - u32 allowed0 = (u32)vmx_read_msr(msr); - u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32); - u32 adjusted = (u32)value | allowed0; - adjusted &= allowed1; - return adjusted; -} - /* MSR bitmap (4KB, all zeros = don't intercept any MSR) */ static u8 g_linux_msr_bitmap[4096] __attribute__((aligned(4096))); @@ -125,11 +118,13 @@ static void linux_vmcs_setup_host_state(void) { /* ===== VMCS guest state for Linux ===== */ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) { - /* CR0/CR3/CR4: long mode with paging */ + /* CR0/CR3/CR4: long mode with paging. + * P8.5: CR4 必须含 VMXE(IA32_VMX_CR4_FIXED0=0x2000 强制, + * 旧代码漏掉会触发 VM-entry invalid guest state)。 */ vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP); /* CR3 points to guest PML4 (GPA) */ vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_gpa); - vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE); + vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE); /* Segment selectors from guest GDT */ vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08); @@ -165,14 +160,18 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) { vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28); vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF); - /* Access rights */ + /* Access rights. + * P8.5: selector=0 的段必须标记 unusable(access rights bit 16), + * 否则 VM-entry guest-state 检查失败: + * FS/GS selector=0 → 0x1C093(unusable) + * LDTR selector=0 → 0x10082(unusable,旧值 0x82 缺 bit 16) */ vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); /* 64-bit code, L=1 */ vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093); vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093); vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093); - vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093); - vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093); - vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82); /* unusable */ + vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0x1C093); /* unusable (selector=0) */ + vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0x1C093); /* unusable (selector=0) */ + vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x10082); /* unusable (selector=0) */ vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */ /* Guest RIP = Linux 64-bit entry (startup_64) */ @@ -204,10 +203,22 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) { /* ===== VMCS controls for Linux ===== */ static void linux_vmcs_setup_controls(u64 eptp) { + log_hex64("[LINUX] TRUE_PINBASED_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS)); + log_hex64("[LINUX] TRUE_PROCBASED_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_PROCBASED_CTLS)); + log_hex64("[LINUX] PROCBASED_CTLS2 raw=", + vmx_read_msr(IA32_VMX_PROCBASED_CTLS2)); + log_hex64("[LINUX] TRUE_EXIT_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_EXIT_CTLS)); + log_hex64("[LINUX] TRUE_ENTRY_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_ENTRY_CTLS)); + /* Pin-based:外部中断 exit + NMI exit + VMX preemption timer(若支持)。 * preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、 * 推进 guest PIT tick、注入 pending virtio/COM1 IRQ。 */ - u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING; + u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING | + PIN_VIRTUAL_NMIS; if (vmx_preemption_timer_supported()) { pin_want |= PIN_VMX_PREEMPTION_TIMER; } @@ -222,13 +233,12 @@ static void linux_vmcs_setup_controls(u64 eptp) { | CPU_BASED_INVLPG_EXITING; cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); - /* Secondary: EPT + VPID */ + /* Secondary: EPT + VPID(经 capability MSR 调整; + * EPT 不可用则打日志——Linux guest 的 GPA 布局依赖 EPT)。 */ u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID; - { - u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2); - u32 allowed1 = (u32)(msr >> 32); - cpu2 &= allowed1; - cpu2 |= SEC_EXEC_ENABLE_EPT; + cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2); + if (!(cpu2 & SEC_EXEC_ENABLE_EPT)) { + log_warn("[LINUX] EPT not available (shadow paging) - guest GPA map disabled"); } u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS @@ -249,20 +259,31 @@ static void linux_vmcs_setup_controls(u64 eptp) { /* MSR bitmap */ { - u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; - u64 msr_bitmap_phys = (u64)g_linux_msr_bitmap - hhdm; + u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_linux_msr_bitmap); + log_hex64("[LINUX] MSR bitmap phys=", msr_bitmap_phys); vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys); } - vmx_vmcs_write(VMCS_EPT_POINTER, eptp); - vmx_vmcs_write(VMCS_VPID, 2); /* VPID 1 = self-test, VPID 2 = Linux */ + if (cpu2 & SEC_EXEC_ENABLE_EPT) { + vmx_vmcs_write(VMCS_EPT_POINTER, eptp); + } else { + vmx_vmcs_write(VMCS_EPT_POINTER, 0); + } + if (cpu2 & SEC_EXEC_ENABLE_VPID) { + vmx_vmcs_write(VMCS_VPID, 2); /* VPID 1 = self-test, VPID 2 = Linux */ + } else { + vmx_vmcs_write(VMCS_VPID, 0); + } - /* Exception bitmap: catch #GP, #PF, #UD, #DF (triple fault handled separately) */ - vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6) | (1ULL << 8)); + /* Linux owns its IDT. Intercepting #PF here livelocks: the guest + * page-fault handler never runs, so early mm faults spin in VM-exit. */ + vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, 0); } /* ===== Global for passing RSI to launch asm ===== */ static volatile u64 g_linux_rsi; +static volatile int g_linux_launch_failed; +static volatile u64 g_linux_launch_flags; /* ===== Linux launch ===== * @@ -306,17 +327,44 @@ int linux_launch(void) { linux_vmcs_setup_controls(vmm_get_eptp()); linux_vmcs_setup_host_state(); - /* 初始 arm preemption timer(1ms 后首次周期 exit) */ - vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms()); + /* 仅在 pin-based control 真正启用 timer 时 arm。 */ + if (vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL) & + PIN_VMX_PREEMPTION_TIMER) { + vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, + vmx_preemption_quantum()); + } else { + vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, 0); + } /* Set RSI for Linux boot_params (passed via global, loaded in asm) */ g_linux_rsi = gi->bootparams_gpa; + /* P8.5: VMCLEAR + VMPTRLD —— VMLAUNCH 要求 VMCS 处于 "clear" 态。 + * 自检成功后 VMCS 状态为 "launched",不 VMCLEAR 会报 + * "VMLAUNCH with non-clear VMCS"(error 4)。 + * 内容不受影响:VMCLEAR 只回写内存并改状态,VMPTRLD 再挂载。 */ + if (vmx_vmcs_clear(vmm_get_vmcs_phys()) != 0) { + log_error("[LINUX] vmclear failed"); + return -4; + } + if (vmx_vmcs_load(vmm_get_vmcs_phys()) != 0) { + log_error("[LINUX] vmptrld after vmclear failed"); + return -5; + } + log_hex64("[LINUX] RIP=", gi->kernel_entry); log_hex64("[LINUX] RSP=", LINUX_GUEST_STACK_GPA + 0x1000); log_hex64("[LINUX] RSI=", gi->bootparams_gpa); log_hex64("[LINUX] CR3=", gi->pgt_gpa); log_hex64("[LINUX] GDTR=", gi->gdt_gpa); + log_hex64("[LINUX] pin=", vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL)); + log_hex64("[LINUX] cpu=", vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL)); + log_hex64("[LINUX] cpu2=", vmx_vmcs_read(VMCS_SECONDARY_VM_EXEC_CONTROL)); + log_hex64("[LINUX] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS)); + log_hex64("[LINUX] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS)); + log_hex64("[LINUX] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER)); + log_hex64("[LINUX] msr_bitmap=", vmx_vmcs_read(VMCS_MSR_BITMAP)); + log_hex64("[LINUX] vpid=", vmx_vmcs_read(VMCS_VPID)); log_info("[LINUX] vmlaunch"); /* vmlaunch with RSI = boot_params. @@ -324,38 +372,98 @@ int linux_launch(void) { * - On failure: failed=1, falls through * - On success: enters guest, VM-Exit → vmx_vm_exit_handler * - On terminate: handler restores RSP, jmps to post_guest label */ - int failed; + g_linux_launch_failed = 1; + g_linux_launch_flags = 0; __asm__ volatile( "movq %%rsp, g_saved_host_rsp(%%rip)\n\t" "leaq 1f(%%rip), %%rax\n\t" "movq %%rax, g_saved_return_rip(%%rip)\n\t" + /* Linux 64-bit boot protocol requires every GPR except RSI zero. */ + "xorl %%eax, %%eax\n\t" + "xorl %%ebx, %%ebx\n\t" + "xorl %%ecx, %%ecx\n\t" + "xorl %%edx, %%edx\n\t" + "xorl %%edi, %%edi\n\t" + "xorl %%ebp, %%ebp\n\t" + "xorl %%r8d, %%r8d\n\t" + "xorl %%r9d, %%r9d\n\t" + "xorl %%r10d, %%r10d\n\t" + "xorl %%r11d, %%r11d\n\t" + "xorl %%r12d, %%r12d\n\t" + "xorl %%r13d, %%r13d\n\t" + "xorl %%r14d, %%r14d\n\t" + "xorl %%r15d, %%r15d\n\t" "movq g_linux_rsi(%%rip), %%rsi\n\t" /* RSI = boot_params GPA */ "vmlaunch\n\t" /* ---- failure path ---- */ - "movl $1, %0\n\t" + "movl $1, g_linux_launch_failed(%%rip)\n\t" + "pushfq\n\t" + "popq g_linux_launch_flags(%%rip)\n\t" "jmp 2f\n\t" /* ---- post_guest: terminate path jmps here ---- */ "1:\n\t" - "movl $0, %0\n\t" + "movl $0, g_linux_launch_failed(%%rip)\n\t" "2:\n\t" - : "=r"(failed) - :: "rax", "rcx", "rdx", "rsi", "rdi", - "r8", "r9", "r10", "r11", "memory" + : + : + : "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + "cc", "memory" ); + int failed = g_linux_launch_failed; + u64 vm_flags = g_linux_launch_flags; if (failed) { - u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR); + /* CF=1 → VMfailInvalid;ZF=1 → VMfailValid(error field 有效)。 */ + if (vm_flags & 0x1) { + log_error("[LINUX] vmlaunch VMfailInvalid (invalid current-VMCS)"); + } else if (vm_flags & 0x40) { + log_error("[LINUX] vmlaunch VMfailValid"); + } + u64 error = 0; + if (vm_flags & 0x40) { + vmx_vmcs_read_checked(VMCS_VMX_INSTRUCTION_ERROR, &error); + } log_hex64("[LINUX] vmlaunch failed, error=", error); g_linux_guest_active = 0; return -3; } - /* Guest parked (HLT) — Linux daemon 已驻留,等待 linux_resume() 唤醒 */ - g_linux_guest_active = 0; /* 回到 host 上下文,清除 active 标志 */ - g_guest_parked = 1; /* 标记 guest 已 park,linux_resume() 可唤醒 */ - log_info("[LINUX] guest parked (daemon ready)"); + /* Only handle_hlt() may mark the Linux guest parked. A fatal exception, + * triple fault, or EPT failure also returns through post_guest, but must + * not make the compatibility layer report a ready daemon. */ + g_linux_guest_active = 0; log_hex64("[LINUX] last exit reason=", g_last_exit_reason); log_hex64("[LINUX] vmexit count=", vmexit_get_count()); + if (!g_guest_parked) { + log_error("[LINUX] guest terminated before daemon park"); + return -6; + } + + /* A kernel idle/fatal HLT is indistinguishable from the daemon's PARK at + * the VM-exit reason level. The daemon must first publish EXEC_READY + * through /dev/utsm; this proves the driver PING, SHM mapping, userspace + * daemon, and Linux→UTSM ring are all alive. */ + { + struct utsm_ipc_msg ready; + int got_ready = 0; + for (int i = 0; i < 8; i++) { + if (ipc_shm_recv(&ready) != 0) break; + if (ready.type == UTSM_MSG_EXEC_READY) { + got_ready = 1; + break; + } + log_hex64("[LINUX] pre-ready message type=", ready.type); + } + if (!got_ready) { + g_guest_parked = 0; + log_error("[LINUX] HLT without EXEC_READY handshake"); + return -7; + } + } + + log_info("[LINUX] EXEC_READY received from /dev/utsm"); + log_info("[LINUX] guest parked (daemon ready)"); return 0; } diff --git a/CODE/UTSM/vmm/vmm.c b/CODE/UTSM/vmm/vmm.c index 9b3a20c..c398774 100644 --- a/CODE/UTSM/vmm/vmm.c +++ b/CODE/UTSM/vmm/vmm.c @@ -8,26 +8,36 @@ extern volatile struct limine_hhdm_request g_hhdm_request; -/* ===== VMX 指令错误码 ===== */ +/* ===== VMX 指令错误码(Intel SDM Vol 3D, "VM Instruction Error Numbers") ===== */ static const char *vmx_error_str(u64 err) { switch (err) { - case 1: return "VMCALL/VMCLEAR/VMLAUNCH/VMRESUME with non-launched VMCS"; - case 2: return "VMRESUME after VMXOFF"; - case 3: return "VMRESUME with corrupted VMCS"; - case 4: return "vmlaunch with non-clear VMCS"; - case 5: return "VM operation invalid"; - case 6: return "VMCS revision mismatch"; - case 7: return "VMCS shadowing mismatch"; - case 8: return "invalid guest state"; - case 9: return "host state invalid"; - case 10: return "VM-execution control invalid"; - case 11: return "VM-exit control invalid"; - case 12: return "VM-entry control invalid"; - case 13: return "VM-exit control fields invalid"; - case 15: return "address out of width"; - case 16: return "MSR bitmap addr invalid"; - case 17: return "VMREAD/VMWRITE from unsupported field"; - case 18: return "VMCS addr invalid"; + case 1: return "VMCALL executed in VMX root operation"; + case 2: return "VMCLEAR with invalid physical address"; + case 3: return "VMCLEAR with VMXON pointer"; + case 4: return "VMLAUNCH with non-clear VMCS"; + case 5: return "VMRESUME with non-launched VMCS"; + case 6: return "VMRESUME after VMXOFF"; + case 7: return "VM entry with invalid VM-execution control fields"; + case 8: return "VM entry with invalid host-state fields"; + case 9: return "VMPTRLD with invalid physical address"; + case 10: return "VMPTRLD with VMXON pointer"; + case 11: return "VMPTRLD with incorrect VMCS revision identifier"; + case 12: return "VMREAD from unsupported VMCS component"; + case 13: return "VMWRITE to read-only VMCS component"; + case 15: return "VM entry with invalid guest-state fields"; + case 16: return "VM entry with invalid executive-VMCS pointer"; + case 17: return "VM entry with non-launched executive VMCS"; + case 18: return "VM entry with executive-VMCS inconsistency"; + case 19: return "VM entry with invalid VM-exit control fields"; + case 20: return "VM entry with invalid MSEG VMCS revision"; + case 22: return "VMCALL with non-clear VMCS"; + case 23: return "VMCALL with invalid VM-exit control fields"; + case 24: return "VMCALL with incorrect MSEG VMCS revision"; + case 25: return "VM switch with invalid VM-exit control fields"; + case 26: return "VM switch with invalid VM-exit MSEG VMCS revision"; + case 28: return "VMRETURN with non-launched VMCS"; + case 29: return "VMRETURN with invalid executive VMCS"; + case 30: return "VM entry with invalid VM-execution control fields in event injection"; default: return "unknown"; } } @@ -78,6 +88,17 @@ static u8 g_guest_code[4096] __attribute__((aligned(4096))) = { /* guest 栈页 */ static u8 g_guest_stack[4096] __attribute__((aligned(4096))); +/* P8.5: guest 自有页表(EPT 模式下 guest CR3 不能指向 host 页表—— + * host 页表页未在 EPT 映射,页表遍历本身就会 EPT violation)。 + * 布局(GPA): + * 0x6000 PML4 [0] -> 0x7000 | P|RW + * 0x7000 PDPT [0] -> 0x9000 | P|RW + * 0x9000 PD [0] -> 2MB 大页 | P|RW|PS (GPA 0..2MB 恒等映射) + * guest 代码/栈/GDT/TSS/IDT 页全部落在低 2MB 内。 */ +static u64 g_guest_pml4[512] __attribute__((aligned(4096))); +static u64 g_guest_pdpt[512] __attribute__((aligned(4096))); +static u64 g_guest_pd[512] __attribute__((aligned(4096))); + /* MSR bitmap(4KB 全 0 = 不拦截 MSR) */ static u8 g_msr_bitmap[4096] __attribute__((aligned(4096))); @@ -162,14 +183,6 @@ static inline u64 read_cr3_local(void) { /* ===== VMCS 配置 ===== */ -static u64 vmx_adjust_control(u64 value, u32 msr) { - u32 allowed0 = (u32)vmx_read_msr(msr); - u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32); - u32 adjusted = (u32)value | allowed0; - adjusted &= allowed1; - return adjusted; -} - static void vmcs_setup_host_state(void) { vmx_vmcs_write(VMCS_HOST_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG); vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local()); @@ -235,10 +248,11 @@ static void vmcs_setup_host_state(void) { } static void vmcs_setup_guest_state_phase1(void) { - /* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表。 - * P8.4: CR4 must include VMXE (CR4_FIXED0 requires it under KVM). */ + /* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE。 + * P8.5: CR3 指向 guest 自有页表(GPA 0x6000), + * 不再借用 host CR3(EPT 下 host 页表页未映射,遍历会失败)。 */ vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP); - vmx_vmcs_write(VMCS_GUEST_CR3, read_cr3_local()); + vmx_vmcs_write(VMCS_GUEST_CR3, 0x6000); vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE); /* 段选择子:flat 模型。 @@ -324,7 +338,19 @@ static void vmcs_setup_guest_state_phase1(void) { } static void vmcs_setup_controls(u64 eptp) { - u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING, + log_hex64("[VMM] TRUE_PINBASED_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS)); + log_hex64("[VMM] TRUE_PROCBASED_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_PROCBASED_CTLS)); + log_hex64("[VMM] PROCBASED_CTLS2 raw=", + vmx_read_msr(IA32_VMX_PROCBASED_CTLS2)); + log_hex64("[VMM] TRUE_EXIT_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_EXIT_CTLS)); + log_hex64("[VMM] TRUE_ENTRY_CTLS raw=", + vmx_read_msr(IA32_VMX_TRUE_ENTRY_CTLS)); + + u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING | + PIN_VIRTUAL_NMIS, IA32_VMX_TRUE_PINBASED_CTLS); u64 cpu = CPU_BASED_HLT_EXITING @@ -334,11 +360,12 @@ static void vmcs_setup_controls(u64 eptp) { | CPU_BASED_INVLPG_EXITING; cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); - /* P8.4d: Try without EPT first to isolate nested VMX issue under KVM. - * If KVM requires EPT, vmx_adjust_control will OR it back in. */ - u64 cpu2 = 0; /* no EPT, no VPID */ + /* P8.5: 请求 EPT(KVM 报告的 allowed-1 集合含 EPT 位)。 + * 自检 guest 用 GPA 0x1000..0x9000 + 自有页表,必须开 EPT + * 才能让 ept_map_range 的映射真正生效。 + * EPT 不可用时 cpu2 会失去 EPT 位并打日志,自检将失败。 */ + u64 cpu2 = SEC_EXEC_ENABLE_EPT; cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2); - log_hex64("[VMM] IA32_VMX_PROCBASED_CTLS2 raw=", vmx_read_msr(IA32_VMX_PROCBASED_CTLS2)); u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS | VM_EXIT_HOST_ADDR_SPACE_SIZE @@ -358,8 +385,8 @@ static void vmcs_setup_controls(u64 eptp) { /* MSR bitmap 物理地址 */ { - u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; - u64 msr_bitmap_phys = (u64)g_msr_bitmap - hhdm; + u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_msr_bitmap); + log_hex64("[VMM] MSR bitmap phys=", msr_bitmap_phys); vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys); } @@ -371,6 +398,8 @@ static void vmcs_setup_controls(u64 eptp) { } if (cpu2 & SEC_EXEC_ENABLE_VPID) { vmx_vmcs_write(VMCS_VPID, 1); + } else { + vmx_vmcs_write(VMCS_VPID, 0); } /* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */ @@ -430,6 +459,9 @@ void vmm_shutdown(void) { int vmm_is_ready(void) { return g_vmm_ready; } u64 vmm_get_eptp(void) { return ept_get_eptp(); } +/* P8.5: 暴露 VMCS 物理地址,供 linux_launch() 在 vmlaunch 前 + * 执行 VMCLEAR+VMPTRLD(VMCS 状态机:VMLAUNCH 要求 clear 态)。 */ +u64 vmm_get_vmcs_phys(void) { return g_vmcs_phys; } /* P8.4: Dump all guest-state VMCS fields for KVM nested VMX debugging. * Called before VMLAUNCH to identify field encoding/value errors. */ @@ -532,12 +564,12 @@ int vmm_self_test(void) { /* EPT 映射 guest 代码页(GPA 0x1000)、栈页(GPA 0x8000)、 * GDT 页(GPA 0x3000)、TSS 页(GPA 0x4000)。 - * g_guest_* 是 UTSM 虚拟地址,物理地址 = virt - HHDM */ - u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; - u64 guest_code_phys = (u64)g_guest_code - hhdm; - u64 guest_stack_phys = (u64)g_guest_stack - hhdm; - u64 guest_gdt_phys = (u64)g_guest_gdt - hhdm; - u64 guest_tss_phys = (u64)g_guest_tss - hhdm; + * g_guest_* 属于高半 UTSM 内核映像,必须使用 Limine 提供的 + * kernel virtual/physical base 转换;它们不是 HHDM 指针。 */ + u64 guest_code_phys = vmx_kernel_virt_to_phys(g_guest_code); + u64 guest_stack_phys = vmx_kernel_virt_to_phys(g_guest_stack); + u64 guest_gdt_phys = vmx_kernel_virt_to_phys(g_guest_gdt); + u64 guest_tss_phys = vmx_kernel_virt_to_phys(g_guest_tss); if (ept_map_range(0x1000, guest_code_phys, 4096, EPT_RWX) != 0) { log_error("[VMM] map guest code failed"); @@ -557,7 +589,7 @@ int vmm_self_test(void) { } /* P8.4: Map IDT page for KVM nested VMX (IDTR base = 0x5000) */ { - u64 guest_idt_phys = (u64)g_guest_idt - hhdm; + u64 guest_idt_phys = vmx_kernel_virt_to_phys(g_guest_idt); for (int i = 0; i < 4096; i++) g_guest_idt[i] = 0; if (ept_map_range(0x5000, guest_idt_phys, 4096, EPT_READ | EPT_WRITE) != 0) { log_error("[VMM] map guest IDT failed"); @@ -565,6 +597,29 @@ int vmm_self_test(void) { } } + /* P8.5: 构建 guest 自有页表(GPA 低 2MB 恒等,2MB 大页)并 EPT 映射。 + * 页表项:P(bit0)|RW(bit1),大页加 PS(bit7);NX=0(可执行)。 */ + { + for (int i = 0; i < 512; i++) { + g_guest_pml4[i] = 0; + g_guest_pdpt[i] = 0; + g_guest_pd[i] = 0; + } + g_guest_pml4[0] = 0x7000 | 0x3; /* P|RW -> PDPT @ GPA 0x7000 */ + g_guest_pdpt[0] = 0x9000 | 0x3; /* P|RW -> PD @ GPA 0x9000 */ + g_guest_pd[0] = 0x0000 | 0x83; /* P|RW|PS -> 2MB page @ GPA 0 */ + + u64 pml4_phys = vmx_kernel_virt_to_phys(g_guest_pml4); + u64 pdpt_phys = vmx_kernel_virt_to_phys(g_guest_pdpt); + u64 pd_phys = vmx_kernel_virt_to_phys(g_guest_pd); + if (ept_map_range(0x6000, pml4_phys, 4096, EPT_READ | EPT_WRITE) != 0 || + ept_map_range(0x7000, pdpt_phys, 4096, EPT_READ | EPT_WRITE) != 0 || + ept_map_range(0x9000, pd_phys, 4096, EPT_READ | EPT_WRITE) != 0) { + log_error("[VMM] map guest page tables failed"); + return -12; + } + } + /* 配置 VMCS */ vmcs_setup_guest_state_phase1(); vmcs_setup_controls(ept_get_eptp()); @@ -590,36 +645,56 @@ int vmm_self_test(void) { log_hex64("[VMM] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS)); log_hex64("[VMM] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS)); log_hex64("[VMM] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER)); + log_hex64("[VMM] msr_bitmap=", vmx_vmcs_read(VMCS_MSR_BITMAP)); + log_hex64("[VMM] vpid=", vmx_vmcs_read(VMCS_VPID)); log_hex64("[VMM] link=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER)); /* P8.4: Dump all guest-state fields for KVM debug */ vmm_dump_guest_state(); /* vmlaunch 控制流: - * - 失败:执行下一条指令,failed=1 + * - 失败:执行下一条指令,failed=1(同时捕获 RFLAGS 区分 + * VMfailValid CF=1 / VMfailInvalid ZF=1) * - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。 * - guest 终止时,handler 恢复 saved_rsp 并 jmp 到 post_guest 标签。 * * 我们在 vmlaunch 前保存 RSP 与"vmlaunch 后"的 RIP(= post_guest 标签地址)。 */ int failed; + u64 vm_flags = 0; __asm__ volatile( "movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */ "leaq 1f(%%rip), %%rax\n\t" /* 取 post_guest 标签地址 */ "movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */ "vmlaunch\n\t" /* 启动 guest */ - /* ---- 失败路径 ---- */ + /* ---- 失败路径(mov 不改 flags,先存 failed 再抓 RFLAGS) ---- */ "movl $1, %0\n\t" + "pushfq\n\t" + "popq %1\n\t" "jmp 2f\n\t" /* ---- post_guest 标签:终止路径 jmp 到这里 ---- */ "1:\n\t" "movl $0, %0\n\t" "2:\n\t" - : "=r"(failed) + : "=r"(failed), "=r"(vm_flags) :: "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory" ); if (failed) { - u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR); + /* CF(bit0)=1 → VMfailInvalid(current-VMCS 无效); + * ZF(bit6)=1 → VMfailValid(错误码在 VMCS 0x4400)。 */ + if (vm_flags & 0x1) { + log_error("[VMM] vmlaunch VMfailInvalid (invalid current-VMCS)"); + } else if (vm_flags & 0x40) { + log_error("[VMM] vmlaunch VMfailValid"); + } else { + log_error("[VMM] vmlaunch failed (unexpected flags)"); + } + u64 error = 0; + if (!(vm_flags & 0x40)) { + log_error("[VMM] VM-instruction error unavailable"); + } else if (vmx_vmcs_read_checked(VMCS_VMX_INSTRUCTION_ERROR, &error) != 0) { + log_error("[VMM] VMREAD error field failed (current-VMCS invalid)"); + } log_hex64("[VMM] vmlaunch failed, error=", error); serial_write("[VMM] "); serial_write(vmx_error_str(error)); diff --git a/CODE/UTSM/vmm/vmx.c b/CODE/UTSM/vmm/vmx.c index 039856e..1f0c6e3 100644 --- a/CODE/UTSM/vmm/vmx.c +++ b/CODE/UTSM/vmm/vmx.c @@ -6,6 +6,7 @@ #include "../arch/x86_64/limine.h" extern volatile struct limine_hhdm_request g_hhdm_request; +extern volatile struct limine_kernel_address_request g_kernel_address_request; /* ===== MSR / CR helpers ===== */ @@ -15,6 +16,41 @@ u64 vmx_read_msr(u32 msr) { return ((u64)hi << 32) | lo; } +u64 vmx_adjust_control(u64 value, u32 msr) { + u64 raw = vmx_read_msr(msr); + u32 allowed0 = (u32)raw; + u32 allowed1 = (u32)(raw >> 32); + /* KVM / nested-KVM polarity (verified 2026-09-06): + * low bit=1 => must be 1 + * high bit=0 => must be 0 + * Secondary CTLS2 reports allowed0=0; inverting low would force every + * allowed1 feature on and fail VM-entry check 7. */ + u32 adjusted = ((u32)value | allowed0) & allowed1; + log_hex64("[VMX] adjust msr=", msr); + log_hex64("[VMX] adjust desired=", value); + log_hex64("[VMX] adjust allowed0=", allowed0); + log_hex64("[VMX] adjust allowed1=", allowed1); + log_hex64("[VMX] adjust result=", adjusted); + return adjusted; +} + +u64 vmx_kernel_virt_to_phys(const void *address) { + const struct limine_kernel_address_response *response = + g_kernel_address_request.response; + u64 virt = (u64)address; + + if (!response) { + log_error("[VMX] Limine kernel-address response missing"); + return 0; + } + if (virt < response->virtual_base) { + log_error("[VMX] address is below kernel virtual base"); + return 0; + } + + return response->physical_base + (virt - response->virtual_base); +} + static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) { u32 lo = (u32)(value & 0xFFFFFFFFULL); u32 hi = (u32)(value >> 32); @@ -176,6 +212,8 @@ static int vmx_truly_available(void) { return 0; } +/* P8.5: VMX 指令成功条件是 CF=0 且 ZF=0(即 ja)。 + * 旧代码只查 CF(jnc):VMfailInvalid(ZF=1, CF=0)会被误判为成功。 */ int vmx_enable(void) { if (g_vmx_enabled) return 0; @@ -221,7 +259,7 @@ int vmx_enable(void) { int err; __asm__ volatile( "vmxon %1\n" - "jnc 1f\n" + "ja 1f\n" "jmp 2f\n" "1:\n" "mov $0, %0\n" @@ -276,7 +314,7 @@ int vmx_vmcs_load(u64 phys) { int err; __asm__ volatile( "vmptrld %1\n" - "jnc 1f\n" + "ja 1f\n" "jmp 2f\n" "1:\n" "mov $0, %0\n" @@ -300,7 +338,7 @@ int vmx_vmcs_clear(u64 phys) { int err; __asm__ volatile( "vmclear %1\n" - "jnc 1f\n" + "ja 1f\n" "jmp 2f\n" "1:\n" "mov $0, %0\n" @@ -330,6 +368,23 @@ u64 vmx_vmcs_read(u64 field) { return value; } +int vmx_vmcs_read_checked(u64 field, u64 *value) { + u64 v; + u8 ok; + __asm__ volatile( + "vmread %2, %1\n" + "seta %0\n" /* CF=0 且 ZF=0 才是 VMsucceed */ + : "=q"(ok), "=r"(v) + : "r"(field) + : "memory" + ); + if (ok) { + if (value) *value = v; + return 0; + } + return -1; +} + void vmx_vmcs_write(u64 field, u64 value) { __asm__ volatile( "vmwrite %1, %0\n" @@ -342,7 +397,7 @@ int vmx_vmlaunch(void) { int err; __asm__ volatile( "vmlaunch\n" - "jnc 1f\n" + "ja 1f\n" "jmp 2f\n" "1:\n" "mov $0, %0\n" @@ -360,7 +415,7 @@ int vmx_vmresume(void) { int err; __asm__ volatile( "vmresume\n" - "jnc 1f\n" + "ja 1f\n" "jmp 2f\n" "1:\n" "mov $0, %0\n" diff --git a/CODE/UTSM/vmm/xj380_boot.c b/CODE/UTSM/vmm/xj380_boot.c index 085fc92..ef61241 100644 --- a/CODE/UTSM/vmm/xj380_boot.c +++ b/CODE/UTSM/vmm/xj380_boot.c @@ -48,7 +48,7 @@ static inline u64 read_cr4_local(void) { return v; } -static u64 vmx_adjust_control(u64 value, u32 msr) { +static u64 xj380_adjust_control(u64 value, u32 msr) { u32 allowed0 = (u32)vmx_read_msr(msr); u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32); u32 adjusted = (u32)value | allowed0; @@ -186,14 +186,14 @@ static void xj380_vmcs_setup_controls(u64 eptp) { if (vmx_preemption_timer_supported()) { pin_want |= PIN_VMX_PREEMPTION_TIMER; } - u64 pin = vmx_adjust_control(pin_want, IA32_VMX_TRUE_PINBASED_CTLS); + u64 pin = xj380_adjust_control(pin_want, IA32_VMX_TRUE_PINBASED_CTLS); u64 cpu = CPU_BASED_HLT_EXITING | CPU_BASED_ACTIVATE_SECONDARY | CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_UNCOND_IO_EXITING | CPU_BASED_INVLPG_EXITING; - cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); + cpu = xj380_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID; { @@ -206,12 +206,12 @@ static void xj380_vmcs_setup_controls(u64 eptp) { u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS | VM_EXIT_HOST_ADDR_SPACE_SIZE | VM_EXIT_LOAD_HOST_EFER; - exit_ctrl = vmx_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS); + exit_ctrl = xj380_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS); u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS | VM_ENTRY_IA32E_MODE_GUEST | VM_ENTRY_LOAD_GUEST_EFER; - entry_ctrl = vmx_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS); + entry_ctrl = xj380_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS); vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin); vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu); @@ -270,7 +270,7 @@ int xj380_launch(void) { xj380_vmcs_setup_host_state(); /* 初始 arm preemption timer(1ms 后首次周期 exit) */ - vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms()); + vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum()); /* KernelMain 3 个 SysV 参数(HHDM 虚拟地址) */ g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET; diff --git a/CODE/linux/KDE.md b/CODE/linux/KDE.md new file mode 100644 index 0000000..6494648 --- /dev/null +++ b/CODE/linux/KDE.md @@ -0,0 +1,104 @@ +# KDE Plasma guest rootfs + +`build_extra_rootfs.sh` has two modes: + +- Default (`KDE_FULL=0`): build the existing 1 GiB persistence volume. +- Opt-in (`KDE_FULL=1`): use Arch `pacstrap` to build a complete KDE Plasma + environment inside `linux-extra-rootfs.img`. + +No KDE packages are downloaded unless `KDE_FULL=1` is explicitly set. + +## Build + +Run the full build from an Arch Linux environment with +`arch-install-scripts`, working pacman mirrors, and a valid keyring: + +```bash +sudo pacman -S --needed arch-install-scripts +sudo KDE_FULL=1 IMG_SIZE_MB=8192 \ + bash CODE/linux/build_extra_rootfs.sh +``` + +The default size for `KDE_FULL=1` is 8192 MiB. The complete +`kde-applications-meta` suite can grow over time, so a larger value such as +12288 MiB may be needed. Values below 8192 MiB are allowed but produce a +warning. + +Useful build settings: + +```text +IMG_SIZE_MB=12288 image capacity in MiB +EXTRA_ROOTFS_WORK=/native/fs native-Linux work directory +KDE_PACSTRAP=/usr/bin/pacstrap +KDE_PACMAN_CONF=/path/to/pacman.conf +KDE_USER=deshab +KDE_UID=1000 +KDE_EXTRA_PACKAGES="pkg1 pkg2" +``` + +`KDE_PACMAN_CONF` can point at a project-controlled mirror/snapshot +configuration when package-version reproducibility is required. Without it, +the build intentionally follows the Arch host's current repositories and +keyring. + +The package set includes `plasma-meta`, the full +`kde-applications-meta`, `plasma-workspace`, SDDM, Wayland, XWayland, +Mesa/LLVM llvmpipe, Vulkan lavapipe (`vulkan-swrast`), D-Bus, systemd user +units, input/seat support, portals, and Noto CJK/emoji fonts. Current Arch +ships the Wayland session in `plasma-workspace`; the obsolete +`plasma-wayland-session` package is intentionally not requested. + +## Storage and attachment + +The output remains: + +```text +SYSTEM/boot/linux-extra-rootfs.img +``` + +That path is the existing build staging convention, not a claim that the full +image fits in the boot partition. A full KDE image must **not** be copied into +the 2 GiB ESP or enabled as a large Limine boot module. It must later be backed +by external storage and attached to the Linux guest through the existing +virtio-blk extra-rootfs path (normally `/dev/vdc`). Transporting that large +backing image is separate from this preparation script. + +Allocate at least 4 GiB of guest RAM for Plasma; 6-8 GiB is preferable when +running the complete application suite. + +## Starting the session + +The current guest mounts the extra volume at `/mnt/persist` while retaining +its minimal read-only root. Start the prepared chroot from the guest console: + +```bash +sudo /mnt/persist/usr/local/bin/deshab-kde-session +``` + +The launcher: + +- bind-mounts `/dev`, `/proc`, `/sys`, and `/run` into the prepared root; +- creates the user's `XDG_RUNTIME_DIR` with safe ownership and permissions; +- starts a private D-Bus session using Plasma's current Arch wrapper; +- starts `startplasma-wayland` as the unprivileged `deshab` user; +- defaults to Mesa llvmpipe/lavapipe software rendering; and +- returns to the existing guest console on failure or logout. + +Logs are written to: + +```text +/mnt/persist/var/log/deshab-kde-session.log +``` + +Software rendering can be disabled for a working accelerated virtual GPU: + +```bash +sudo DESHAB_KDE_SOFTWARE_RENDERING=0 \ + /mnt/persist/usr/local/bin/deshab-kde-session +``` + +The image contains SDDM and systemd user-session support, but neither replaces +the current custom guest PID 1. The prepared user selects Plasma's script-based +startup fallback for that environment. SDDM is deliberately not enabled, so a +broken graphical session cannot remove the guest console or Deshab native +desktop fallback. diff --git a/CODE/linux/build_extra_rootfs.sh b/CODE/linux/build_extra_rootfs.sh index 56c5b30..0c85af2 100644 --- a/CODE/linux/build_extra_rootfs.sh +++ b/CODE/linux/build_extra_rootfs.sh @@ -1,80 +1,386 @@ #!/bin/bash # -# Build the persistent rw ext4 volume for the UTSM Linux guest (VSCode Phase 4). +# Build the persistent rw ext4 volume for the UTSM Linux guest. # -# Produces a sparse-friendly ext4 image exposed to the guest as a WRITABLE -# virtio-blk device (/dev/vdc, slot6 @ GPA 0xF4006000 IRQ11) by the UTSM -# extra_rootfs backend. The Arch /sbin/init mounts it at /mnt/persist and -# bind-mounts its subtrees over /opt, /home and /root, giving VSCode and -# user data a persistent home across the read-only rootfs. +# The default path creates the existing lightweight persistence volume. +# KDE_FULL=1 additionally pacstraps a self-contained Arch/KDE tree into the +# volume. The running guest keeps its current read-only root and can enter the +# KDE tree with /mnt/persist/usr/local/bin/deshab-kde-session. This keeps the +# native desktop and the guest console available as emergency fallbacks. # # Output: -# SYSTEM/boot/linux-extra-rootfs.img (ext4 image, default 1024MB) -# -# After generating, uncomment the two extra-rootfs module lines in -# SYSTEM/limine/limine.conf so Limine preloads it as a boot module. -# -# Prerequisites: -# - WSL or native Linux with root access -# - mkfs.ext4, losetup, mount (or debugfs for unprivileged population) +# SYSTEM/boot/linux-extra-rootfs.img # # Usage: -# sudo bash CODE/linux/build_extra_rootfs.sh # 1GB default +# sudo bash CODE/linux/build_extra_rootfs.sh # sudo IMG_SIZE_MB=2048 bash CODE/linux/build_extra_rootfs.sh +# sudo KDE_FULL=1 IMG_SIZE_MB=8192 bash CODE/linux/build_extra_rootfs.sh +# +# KDE_FULL=1 prerequisites: +# - An Arch Linux build environment with arch-install-scripts (pacstrap) +# - Working pacman mirrors and keyring +# - Enough free space for the full KDE Applications suite -set -e +set -Eeuo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot" OUTPUT_IMG="$OUTPUT_DIR/linux-extra-rootfs.img" -IMG_SIZE_MB=${IMG_SIZE_MB:-1024} + +KDE_FULL="${KDE_FULL:-0}" +case "$KDE_FULL" in + 0|1) ;; + *) + echo "[extra-rootfs] ERROR: KDE_FULL must be 0 or 1" >&2 + exit 2 + ;; +esac + +if [ -z "${IMG_SIZE_MB+x}" ]; then + if [ "$KDE_FULL" -eq 1 ]; then + IMG_SIZE_MB=8192 + else + IMG_SIZE_MB=1024 + fi +fi +if ! [[ "$IMG_SIZE_MB" =~ ^[1-9][0-9]*$ ]]; then + echo "[extra-rootfs] ERROR: IMG_SIZE_MB must be a positive integer" >&2 + exit 2 +fi +if [ "$KDE_FULL" -eq 1 ] && [ "$IMG_SIZE_MB" -lt 8192 ]; then + echo "[extra-rootfs] WARNING: full KDE is expected to need at least 8192MB" >&2 +fi WORK_DIR="${EXTRA_ROOTFS_WORK:-/root/extra_rootfs_work}" MNT_DIR="$WORK_DIR/mnt" +NATIVE_IMG="$WORK_DIR/extra-rootfs.img" +KDE_PACSTRAP="${KDE_PACSTRAP:-pacstrap}" +KDE_PACMAN_CONF="${KDE_PACMAN_CONF:-}" +KDE_USER="${KDE_USER:-deshab}" +KDE_UID="${KDE_UID:-1000}" + +if ! [[ "$KDE_USER" =~ ^[a-z_][a-z0-9_-]*$ ]]; then + echo "[extra-rootfs] ERROR: KDE_USER is not a valid user name" >&2 + exit 2 +fi +if ! [[ "$KDE_UID" =~ ^[1-9][0-9]*$ ]]; then + echo "[extra-rootfs] ERROR: KDE_UID must be a positive integer" >&2 + exit 2 +fi + +# Explicit groups make review and future package-name updates straightforward. +# plasma-workspace now owns the Wayland session and replaces the obsolete +# plasma-wayland-session package on current Arch Linux. +KDE_DESKTOP_PACKAGES=( + plasma-meta + kde-applications-meta + plasma-workspace + sddm +) +KDE_WAYLAND_PACKAGES=( + wayland + wayland-protocols + xorg-xwayland + xdg-desktop-portal + xdg-desktop-portal-kde + libinput + seatd +) +KDE_GRAPHICS_PACKAGES=( + mesa + mesa-utils + libdrm + libglvnd + llvm-libs + vulkan-icd-loader + vulkan-swrast +) +KDE_SESSION_PACKAGES=( + dbus + dbus-broker + systemd + systemd-libs + polkit + pam +) +KDE_FONT_PACKAGES=( + noto-fonts + noto-fonts-cjk + noto-fonts-emoji + ttf-dejavu +) +KDE_BASE_PACKAGES=( + base + bash + coreutils + shadow + util-linux +) +KDE_PACKAGES=( + "${KDE_BASE_PACKAGES[@]}" + "${KDE_DESKTOP_PACKAGES[@]}" + "${KDE_WAYLAND_PACKAGES[@]}" + "${KDE_GRAPHICS_PACKAGES[@]}" + "${KDE_SESSION_PACKAGES[@]}" + "${KDE_FONT_PACKAGES[@]}" +) +if [ -n "${KDE_EXTRA_PACKAGES:-}" ]; then + read -r -a KDE_EXTRA_PACKAGE_ARRAY <<< "$KDE_EXTRA_PACKAGES" + KDE_PACKAGES+=("${KDE_EXTRA_PACKAGE_ARRAY[@]}") +fi echo "[extra-rootfs] Building persistent rw volume" echo "[extra-rootfs] Work dir: $WORK_DIR (native FS)" echo "[extra-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)" - -mkdir -p "$WORK_DIR" "$MNT_DIR" "$OUTPUT_DIR" +echo "[extra-rootfs] KDE full: $KDE_FULL" if [ "$(id -u)" -ne 0 ]; then echo "[extra-rootfs] ERROR: must run as root (loop mount required)" >&2 exit 1 fi +if [ "$KDE_FULL" -eq 1 ] && ! command -v "$KDE_PACSTRAP" >/dev/null 2>&1; then + echo "[extra-rootfs] ERROR: KDE_FULL=1 requires pacstrap" >&2 + echo "[extra-rootfs] Install arch-install-scripts in an Arch build environment," >&2 + echo "[extra-rootfs] or set KDE_PACSTRAP to its absolute path." >&2 + exit 1 +fi +if [ -n "$KDE_PACMAN_CONF" ] && [ ! -r "$KDE_PACMAN_CONF" ]; then + echo "[extra-rootfs] ERROR: KDE_PACMAN_CONF is not readable: $KDE_PACMAN_CONF" >&2 + exit 1 +fi -# 1. Allocate the image file (seek-truncate: sparse on supporting filesystems) +mkdir -p "$WORK_DIR" "$MNT_DIR" "$OUTPUT_DIR" + +LOOP_DEV="" +MOUNTED=0 +cleanup() { + set +e + if [ "$MOUNTED" -eq 1 ]; then + umount -R "$MNT_DIR" 2>/dev/null || umount "$MNT_DIR" 2>/dev/null || true + fi + if [ -n "$LOOP_DEV" ]; then + losetup -d "$LOOP_DEV" 2>/dev/null || true + fi +} +trap cleanup EXIT INT TERM + +# 1. Allocate a sparse-friendly image and create its ext4 filesystem. echo "[extra-rootfs] Allocating ${IMG_SIZE_MB}MB image..." -dd if=/dev/zero of="$WORK_DIR/extra-rootfs.img" bs=1M count=0 seek="$IMG_SIZE_MB" status=none +rm -f "$NATIVE_IMG" +dd if=/dev/zero of="$NATIVE_IMG" bs=1M count=0 seek="$IMG_SIZE_MB" status=none -# 2. Create the ext4 filesystem. -# -N 1048576 caps inode count (1M inodes is plenty for VSCode + user data -# and keeps the inode table small); -O ^has_journal skips the journal — -# the volume is memory-backed (writes are volatile until Phase 7 writeback), -# so journal overhead buys nothing. echo "[extra-rootfs] mkfs.ext4..." -mkfs.ext4 -q -F -N 1048576 -O ^has_journal -L deshab-persist "$WORK_DIR/extra-rootfs.img" +mkfs.ext4 -q -F -N 1048576 -O ^has_journal -L deshab-persist "$NATIVE_IMG" -# 3. Populate the base directory skeleton via loop mount. +# 2. Mount and populate the lightweight persistence skeleton. echo "[extra-rootfs] Populating directory skeleton..." -LOOP_DEV="$(losetup --find --show "$WORK_DIR/extra-rootfs.img")" -trap 'losetup -d "$LOOP_DEV" 2>/dev/null || true' EXIT - +LOOP_DEV="$(losetup --find --show "$NATIVE_IMG")" mount -t ext4 "$LOOP_DEV" "$MNT_DIR" +MOUNTED=1 mkdir -p "$MNT_DIR/opt" "$MNT_DIR/home" "$MNT_DIR/root" "$MNT_DIR/vscode" -# /mnt/persist/vscode will hold the VSCode tarball extraction target marker; -# /opt bind-mount covers /opt/vscode at runtime. touch "$MNT_DIR/vscode/.keep" + +if [ "$KDE_FULL" -eq 1 ]; then + echo "[extra-rootfs] Installing full Arch/KDE suite with pacstrap..." + PACSTRAP_ARGS=(-K -c) + if [ -n "$KDE_PACMAN_CONF" ]; then + PACSTRAP_ARGS+=(-C "$KDE_PACMAN_CONF") + fi + "$KDE_PACSTRAP" "${PACSTRAP_ARGS[@]}" "$MNT_DIR" "${KDE_PACKAGES[@]}" + + # The image is a complete chroot kept separate from the active minimal + # guest root. A marker lets the launcher distinguish wrapper and chroot + # execution without relying on package presence in the outer root. + mkdir -p "$MNT_DIR/etc" "$MNT_DIR/usr/local/bin" "$MNT_DIR/var/log" + cat > "$MNT_DIR/etc/deshab-kde-root" <<'EOF' +Deshab Arch/KDE guest root +EOF + : > "$MNT_DIR/etc/machine-id" + + if ! chroot "$MNT_DIR" /usr/bin/id "$KDE_USER" >/dev/null 2>&1; then + chroot "$MNT_DIR" /usr/sbin/useradd \ + --create-home --uid "$KDE_UID" --shell /bin/bash "$KDE_USER" + fi + for group in audio input video wheel; do + if chroot "$MNT_DIR" /usr/bin/getent group "$group" >/dev/null 2>&1; then + chroot "$MNT_DIR" /usr/sbin/usermod --append --groups "$group" "$KDE_USER" + fi + done + chroot "$MNT_DIR" /usr/bin/passwd --lock "$KDE_USER" >/dev/null + + # The current guest has a custom PID 1, so Plasma's supported legacy boot + # path is selected. systemd and its user units remain installed for a + # future systemd/logind boot path. + mkdir -p "$MNT_DIR/home/$KDE_USER/.config" + cat > "$MNT_DIR/home/$KDE_USER/.config/startkderc" <<'EOF' +[General] +systemdBoot=false +EOF + chroot "$MNT_DIR" /usr/bin/chown -R "$KDE_USER:$KDE_USER" "/home/$KDE_USER" + + cat > "$MNT_DIR/usr/local/bin/deshab-kde-session" <<'SESSION_SCRIPT' +#!/bin/bash +set -Eeuo pipefail + +KDE_ROOT="${DESHAB_KDE_ROOT:-/mnt/persist}" +SESSION_USER="${DESHAB_KDE_USER:-deshab}" +SOFTWARE_RENDERING="${DESHAB_KDE_SOFTWARE_RENDERING:-1}" +BOUND_MOUNTS=() +SEATD_PID="" + +log() { + printf '[deshab-kde] %s\n' "$*" | tee -a "$LOG_FILE" >&2 +} + +cleanup_session() { + local i + set +e + if [ -n "$SEATD_PID" ]; then + kill "$SEATD_PID" 2>/dev/null || true + wait "$SEATD_PID" 2>/dev/null || true + fi + for ((i=${#BOUND_MOUNTS[@]} - 1; i >= 0; i--)); do + umount -R "${BOUND_MOUNTS[$i]}" 2>/dev/null || \ + umount "${BOUND_MOUNTS[$i]}" 2>/dev/null || true + done +} +trap cleanup_session EXIT INT TERM + +if [ ! -e /etc/deshab-kde-root ]; then + LOG_FILE="$KDE_ROOT/var/log/deshab-kde-session.log" + if [ "$(id -u)" -ne 0 ]; then + echo "[deshab-kde] ERROR: entering the KDE chroot requires root" >&2 + exit 1 + fi + if [ ! -x "$KDE_ROOT/usr/bin/startplasma-wayland" ]; then + echo "[deshab-kde] ERROR: no prepared KDE root at $KDE_ROOT" >&2 + exit 1 + fi + + mkdir -p "$KDE_ROOT"/{dev,proc,sys,run,var/log} + touch "$LOG_FILE" + log "entering KDE root at $KDE_ROOT" + for fs in dev proc sys run; do + target="$KDE_ROOT/$fs" + if mountpoint -q "$target"; then + continue + fi + if ! mount --rbind "/$fs" "$target"; then + log "ERROR: could not bind /$fs into the KDE root" + exit 1 + fi + mount --make-rslave "$target" 2>/dev/null || true + BOUND_MOUNTS+=("$target") + done + + set +e + chroot "$KDE_ROOT" /usr/bin/env \ + DESHAB_KDE_USER="$SESSION_USER" \ + DESHAB_KDE_SOFTWARE_RENDERING="$SOFTWARE_RENDERING" \ + /usr/local/bin/deshab-kde-session --inside + status=$? + set -e + log "chroot session exited with status $status" + exit "$status" +fi + +LOG_FILE="/var/log/deshab-kde-session.log" +touch "$LOG_FILE" +if [ "$(id -u)" -ne 0 ]; then + log "ERROR: session setup must start as root" + exit 1 +fi +if ! id "$SESSION_USER" >/dev/null 2>&1; then + log "ERROR: session user does not exist: $SESSION_USER" + exit 1 +fi +if [ ! -e /dev/dri/card0 ]; then + log "ERROR: /dev/dri/card0 is unavailable; leaving the fallback console intact" + exit 1 +fi + +dbus-uuidgen --ensure=/etc/machine-id +session_uid="$(id -u "$SESSION_USER")" +session_gid="$(id -g "$SESSION_USER")" +runtime_dir="/run/user/$session_uid" +install -d -m 0700 -o "$session_uid" -g "$session_gid" "$runtime_dir" + +if command -v seatd >/dev/null 2>&1 && [ ! -S /run/seatd.sock ]; then + log "starting seatd for the non-systemd guest session" + seatd -g video >>"$LOG_FILE" 2>&1 & + SEATD_PID=$! +fi + +if [ -x /usr/lib/plasma-dbus-run-session-if-needed ]; then + SESSION_COMMAND=( + /usr/lib/plasma-dbus-run-session-if-needed + /usr/bin/startplasma-wayland + ) +elif command -v dbus-run-session >/dev/null 2>&1; then + SESSION_COMMAND=(/usr/bin/dbus-run-session -- /usr/bin/startplasma-wayland) +else + log "ERROR: no Plasma D-Bus session launcher is installed" + exit 1 +fi + +SOFTWARE_ENV=() +if [ "$SOFTWARE_RENDERING" -eq 1 ]; then + SOFTWARE_ENV=( + LIBGL_ALWAYS_SOFTWARE=1 + GALLIUM_DRIVER=llvmpipe + MESA_LOADER_DRIVER_OVERRIDE=llvmpipe + QT_QUICK_BACKEND=software + ) + log "software rendering fallback enabled (llvmpipe/lavapipe)" +fi + +log "starting Plasma Wayland as $SESSION_USER" +set +e +runuser --user "$SESSION_USER" -- /usr/bin/env \ + -u DBUS_SESSION_BUS_ADDRESS \ + HOME="/home/$SESSION_USER" \ + USER="$SESSION_USER" \ + LOGNAME="$SESSION_USER" \ + XDG_RUNTIME_DIR="$runtime_dir" \ + XDG_SESSION_TYPE=wayland \ + XDG_SESSION_CLASS=user \ + XDG_CURRENT_DESKTOP=KDE \ + DESKTOP_SESSION=plasma \ + KDE_FULL_SESSION=true \ + QT_QPA_PLATFORM=wayland \ + LIBSEAT_BACKEND=seatd \ + "${SOFTWARE_ENV[@]}" \ + "${SESSION_COMMAND[@]}" >>"$LOG_FILE" 2>&1 +status=$? +set -e +if [ "$status" -ne 0 ]; then + log "Plasma failed with status $status; see $LOG_FILE" +else + log "Plasma session ended normally" +fi +exit "$status" +SESSION_SCRIPT + chmod 0755 "$MNT_DIR/usr/local/bin/deshab-kde-session" + + # pacstrap -c uses the host cache; remove any target cache left by hooks. + rm -rf "$MNT_DIR/var/cache/pacman/pkg/"* +fi + sync umount "$MNT_DIR" +MOUNTED=0 losetup -d "$LOOP_DEV" -trap - EXIT +LOOP_DEV="" +trap - EXIT INT TERM -# 4. Copy the final image to the Windows side (SYSTEM/boot). +# 3. Copy only the final image back to the project tree. echo "[extra-rootfs] Copying to $OUTPUT_IMG ..." -cp "$WORK_DIR/extra-rootfs.img" "$OUTPUT_IMG" +cp --sparse=always "$NATIVE_IMG" "$OUTPUT_IMG" echo "[extra-rootfs] Done: $OUTPUT_IMG ($(du -h "$OUTPUT_IMG" | cut -f1) on disk)" -echo "[extra-rootfs] Next: uncomment the extra-rootfs module lines in" -echo "[extra-rootfs] SYSTEM/limine/limine.conf, then rebuild the ESP image." +if [ "$KDE_FULL" -eq 1 ]; then + echo "[extra-rootfs] Guest launcher:" + echo "[extra-rootfs] /mnt/persist/usr/local/bin/deshab-kde-session" +fi +echo "[extra-rootfs] Attach this image to the Linux guest through virtio-blk." diff --git a/CODE/sdk/docs/PYTHON.md b/CODE/sdk/docs/PYTHON.md new file mode 100644 index 0000000..df0e081 --- /dev/null +++ b/CODE/sdk/docs/PYTHON.md @@ -0,0 +1,148 @@ +# CPython 应用(Linux guest) + +## 当前架构 + +Deshab 的 CPython 路径只运行在 Arch Linux guest 中。原生 DSK 应用是 +freestanding Ring 0 ELF,没有可供 CPython 使用的完整 libc、动态链接器、 +解释器和标准库,因此不能把 CPython 当作原生 DSK 运行时。 + +`SYSTEM/lib` 中的 `libpython` 和扩展模块只是兼容层库集合的一部分,不构成 +完整 Python 安装,也不应作为此路径的主运行时。解释器、标准库和扩展模块 +必须来自同一套 Arch 软件包,使 `/usr/bin/python3` 与其 distro 库版本保持 +一致。 + +运行前需要: + +- Linux guest 内核、initrd 和 Arch `linux-rootfs.img` 已构建并在 Limine + 配置中启用; +- guest 已启动并且 `linux_compat` daemon 处于 ready/parked 状态; +- Arch rootfs 中已安装 distro Python; +- 示例源码已复制到 guest 的固定路径 + `/usr/local/share/deshab/python/hello.py`。 + +## 在 Arch rootfs 安装 Python + +Arch 的安装命令是: + +```sh +pacman -S python +``` + +当前 guest 把基础 rootfs 以只读方式挂载,所以不要在已启动 guest 中直接向 +`/usr` 安装软件包。应在 WSL/Linux 的可写 rootfs staging tree 中安装,然后 +重新打包。使用 `build_arch_rootfs.sh` 的默认工作目录时,可以执行: + +```sh +cd /mnt/d/Code/DEAICUP/Deshab + +# 首次运行会创建 /root/arch_rootfs_work/rootfs staging tree。 +sudo bash CODE/linux/build_arch_rootfs.sh + +ROOTFS=/root/arch_rootfs_work/rootfs +sudo mkdir -p "$ROOTFS/tmp/pacman/lib/sync" "$ROOTFS/tmp/pacman/cache/pkg" +printf 'nameserver 1.1.1.1\n' | sudo tee "$ROOTFS/tmp/resolv.conf" >/dev/null +sudo chroot "$ROOTFS" /usr/bin/pacman -Sy --noconfirm +sudo chroot "$ROOTFS" /usr/bin/pacman -S --noconfirm python + +sudo install -D -m 0644 \ + SYSTEM/user/python/hello.py \ + "$ROOTFS/usr/local/share/deshab/python/hello.py" + +# staging tree 已存在,第二次运行会保留以上内容并重新生成 rootfs image。 +sudo bash CODE/linux/build_arch_rootfs.sh +``` + +若设置了 `ARCH_ROOTFS_WORK`,相应调整 `ROOTFS`。更新 Arch Python 时应通过 +pacman 一起更新解释器和标准库;不要从 `SYSTEM/lib` 拼装或覆盖 distro +`libpython`。 + +## 脚本如何进入 guest + +`SYSTEM/user/python/hello.py` 是系统盘中的示例源码。当前构建不会把 +`SYSTEM/user/python/` 任意目录自动同步到 Linux rootfs;`linux_compat` +启动时的自动同步只覆盖专用内容(例如兼容库和 VSCode 模块)。因此,上节的 +`install` 命令是当前确定性的发布步骤:在打包前把脚本写入 Arch staging +tree。 + +开发时也可以使用 Deshab shell 的 `dsl push`,但它的来源只支持 FAT32 +根目录中的 8.3 文件名。先把脚本临时放到系统盘根目录并重建镜像: + +```powershell +Copy-Item .\SYSTEM\user\python\hello.py .\SYSTEM\HELLO.PY +.\build.bat -Variant dev +``` + +启动后,在 Deshab shell 中复制到 guest 的可写目录: + +```text +dsl /bin/mkdir -p /root/deshab/python +dsl push HELLO.PY /root/deshab/python/hello.py +dsl /usr/bin/env -u LD_LIBRARY_PATH /usr/bin/python3 /root/deshab/python/hello.py +``` + +此开发路径不是任意目录自动推送,且 `/root` 需要可写的 extra-rootfs。正式 +注册项仍使用打包进基础 rootfs 的 `/usr/local/share/...` 路径。 + +## 桌面注册格式 + +当前 `SYSTEM/LINUXAPP.CNF` 由 `desktop.elf` 按以下格式解析: + +```text +NAME|DISPLAY|/guest/executable|arg1 arg2 +``` + +- `NAME` 是内部名,最多 11 个字符;只有精确的 `VSCODE` 有共享 IDE 特殊语义。 +- `DISPLAY` 是图标名,最多 15 个字符。 +- executable 必须是 guest 绝对路径;path 和 args 各最多 95 个字符。 +- args 只按 ASCII 空格拆分,最多形成 14 个附加参数;不支持引号、转义或含 + 空格的单个参数。 +- `#` 必须位于行首才会被识别为注释。 +- 当前 desktop 最多注册 4 个 Linux 应用。 + +Python 示例注册项为: + +```text +PYTHON|Python|/usr/bin/env|-u LD_LIBRARY_PATH /usr/bin/python3 /usr/local/share/deshab/python/hello.py +``` + +desktop 的 `exec_async` 会注入 +`LD_LIBRARY_PATH=/usr/lib/deshab`。注册项先运行 distro +`/usr/bin/env` 删除该变量,再 exec `/usr/bin/python3`,避免同步到 +`SYSTEM/lib` 的局部 Python/glibc 文件优先于 Arch distro 库。`DISPLAY=:0` +仍会保留。 + +## 验证与限制 + +桌面图标走面向 GUI 的异步启动路径:它会准备 X server、切换到 IDE attach +页,并把子进程 stdout/stderr 重定向到 `/dev/null`。因此点击 Python 图标 +不会显示 `hello.py` 的文本输出,也不代表脚本最终成功退出。 + +请用同步 `dsl` 路径验证,这会捕获 stdout/stderr 和退出码: + +```text +dsl /usr/bin/env -u LD_LIBRARY_PATH /usr/bin/python3 /usr/local/share/deshab/python/hello.py +``` + +成功输出至少包含: + +```text +DESHAB_PYTHON_READY +version=... +platform=... +``` + +其他当前限制: + +- 只有 guest CPython;没有原生 DSK CPython ABI。 +- `LINUXAPP.CNF` 没有 shell quoting,复杂命令应放进 guest 侧脚本。 +- desktop 异步启动不返回应用输出和最终退出状态。 +- 尚无 Python 专用 Deshab GUI、窗口、输入、通知或文件选择 API。 +- CPython 版本由 Arch 仓库决定,应用不应假设 `SYSTEM/lib` 中的版本。 + +## 未来 IPC GUI 方向 + +后续可在 guest 提供版本化的 `deshab` Python 包,通过现有共享内存/ring +IPC 与 host desktop 服务通信。建议逐步提供窗口/Surface 生命周期、批量绘制 +缓冲、键鼠事件队列、剪贴板、文件选择和通知 API。CPython 继续由 distro +维护;Python 包只封装稳定 IPC 消息,不直接链接 DSK,也不依赖 +`SYSTEM/lib` 的局部 `libpython`。 diff --git a/CODE/sdk/include/deshab/syscall.h b/CODE/sdk/include/deshab/syscall.h new file mode 100644 index 0000000..3e95d12 --- /dev/null +++ b/CODE/sdk/include/deshab/syscall.h @@ -0,0 +1,111 @@ +/* deshab/syscall.h - experimental native syscall ABI + * + * This header only reserves numbers and emits the x86_64 SYSCALL instruction. + * Deshab does not currently install a native syscall entry point or dispatcher. + * Calling these wrappers on a current image is unsupported and may fault. + */ +#ifndef DESHAB_SYSCALL_H +#define DESHAB_SYSCALL_H + +#include "types.h" + +#define DESHAB_SYSCALL_ABI_MAJOR 1u +#define DESHAB_SYSCALL_ABI_MINOR 0u + +/* + * Reserved v1 number ranges: + * 0x0000-0x001f core + * 0x0100-0x011f native capability IPC + * 0x0120-0x013f task/scheduler + * 0x0140-0x015f memory + * + * Values are explicit and must never be renumbered after implementation ships. + */ +typedef enum deshab_syscall_number { + DESHAB_SYS_ABI_QUERY = 0x0000, + DESHAB_SYS_EXIT = 0x0001, + DESHAB_SYS_YIELD = 0x0002, + DESHAB_SYS_CLOCK_MONOTONIC = 0x0003, + DESHAB_SYS_DEBUG_WRITE = 0x0004, + + DESHAB_SYS_IPC_QUEUE_CREATE = 0x0100, + DESHAB_SYS_IPC_QUEUE_DESTROY = 0x0101, + DESHAB_SYS_IPC_SEND = 0x0102, + DESHAB_SYS_IPC_RECEIVE = 0x0103, + DESHAB_SYS_IPC_CALL = 0x0104, + DESHAB_SYS_IPC_REPLY = 0x0105, + DESHAB_SYS_IPC_WAIT = 0x0106, + DESHAB_SYS_IPC_SIGNAL = 0x0107, + + DESHAB_SYS_TASK_CREATE = 0x0120, + DESHAB_SYS_TASK_EXIT = 0x0121, + DESHAB_SYS_TASK_SET_PRIORITY = 0x0122, + DESHAB_SYS_TASK_SET_AFFINITY = 0x0123, + + DESHAB_SYS_MEM_MAP = 0x0140, + DESHAB_SYS_MEM_UNMAP = 0x0141, + DESHAB_SYS_MEM_PROTECT = 0x0142 +} deshab_syscall_number; + +/* + * Proposed x86_64 register ABI: + * RAX = number + * RDI, RSI, RDX, R10, R8, R9 = arguments 0..5 + * RAX = signed result (negative values are errors) + * RCX and R11 are destroyed by SYSCALL + */ +#if (defined(__x86_64__) || defined(_M_X64)) && \ + (defined(__clang__) || defined(__GNUC__)) + +#define DESHAB_SYSCALL_INLINE_AVAILABLE 1 + +static inline i64 deshab_syscall6(u64 number, u64 arg0, u64 arg1, u64 arg2, + u64 arg3, u64 arg4, u64 arg5) { + register u64 syscall_arg3 __asm__("r10") = arg3; + register u64 syscall_arg4 __asm__("r8") = arg4; + register u64 syscall_arg5 __asm__("r9") = arg5; + u64 result; + + __asm__ volatile( + "syscall" + : "=a"(result) + : "a"(number), "D"(arg0), "S"(arg1), "d"(arg2), + "r"(syscall_arg3), "r"(syscall_arg4), "r"(syscall_arg5) + : "rcx", "r11", "memory", "cc"); + + return (i64)result; +} + +static inline i64 deshab_syscall0(u64 number) { + return deshab_syscall6(number, 0, 0, 0, 0, 0, 0); +} + +static inline i64 deshab_syscall1(u64 number, u64 arg0) { + return deshab_syscall6(number, arg0, 0, 0, 0, 0, 0); +} + +static inline i64 deshab_syscall2(u64 number, u64 arg0, u64 arg1) { + return deshab_syscall6(number, arg0, arg1, 0, 0, 0, 0); +} + +static inline i64 deshab_syscall3(u64 number, u64 arg0, u64 arg1, u64 arg2) { + return deshab_syscall6(number, arg0, arg1, arg2, 0, 0, 0); +} + +static inline i64 deshab_syscall4(u64 number, u64 arg0, u64 arg1, u64 arg2, + u64 arg3) { + return deshab_syscall6(number, arg0, arg1, arg2, arg3, 0, 0); +} + +static inline i64 deshab_syscall5(u64 number, u64 arg0, u64 arg1, u64 arg2, + u64 arg3, u64 arg4) { + return deshab_syscall6(number, arg0, arg1, arg2, arg3, arg4, 0); +} + +#else + +#define DESHAB_SYSCALL_INLINE_AVAILABLE 0 + +#endif + +#endif /* DESHAB_SYSCALL_H */ diff --git a/SYSTEM/LINUXAPP.CNF b/SYSTEM/LINUXAPP.CNF index db5eb1c..0509d36 100644 --- a/SYSTEM/LINUXAPP.CNF +++ b/SYSTEM/LINUXAPP.CNF @@ -11,3 +11,4 @@ # 其它实例只经桌面图标启动,IDE tab 标签显示最近启动实例的 DISPLAY)。 VSCODE|VSCode|/opt/vscode/bin/code|--no-sandbox --disable-gpu --user-data-dir /root/.vscode-data VSCODE2|VSCode2|/opt/vscode/bin/code|--no-sandbox --disable-gpu --user-data-dir /root/.vscode-data2 /root/workspace2 +PYTHON|Python|/usr/bin/env|-u LD_LIBRARY_PATH /usr/bin/python3 /usr/local/share/deshab/python/hello.py diff --git a/SYSTEM/user/python/hello.py b/SYSTEM/user/python/hello.py new file mode 100644 index 0000000..3d7e647 --- /dev/null +++ b/SYSTEM/user/python/hello.py @@ -0,0 +1,17 @@ +"""Minimal CPython readiness check for the Deshab Linux guest.""" + +from __future__ import annotations + +import platform +import sys + + +def main() -> None: + version = " ".join(sys.version.split()) + print("DESHAB_PYTHON_READY") + print(f"version={version}") + print(f"platform={platform.platform()}") + + +if __name__ == "__main__": + main() diff --git a/docs/XNU_PARITY.md b/docs/XNU_PARITY.md new file mode 100644 index 0000000..f2f356f --- /dev/null +++ b/docs/XNU_PARITY.md @@ -0,0 +1,177 @@ +# XNU parity and “surpass” gates + +This document is a gap analysis, not a claim that Deshab currently has XNU +parity. XNU is a production kernel with decades of hardware support, security +hardening, compatibility work, and performance tuning. Deshab is an +experimental x86_64 SAS-R0 system. “Surpass” below means passing a named, +reproducible gate on a narrow axis; it does not mean overall superiority. + +## Evidence boundary + +### Implemented strengths + +- Limine boots `utsm.elf`, which loads staged ELF64 DKM modules and transfers + control to the DSK kernel. +- DKM has a versioned descriptor/API, manifest-driven boot stages, an ELF64 + ET_REL loader, and working platform, storage, filesystem, network, console, + and input modules. The current loader reaches 14 external drivers in its + tested boot configuration. +- UTSM has segment descriptors, generation/epoch checks, capabilities, + per-CPU key-cache structures, encrypted-buffer read/write paths, dirty-page + accounting, and self-tests. +- DRR has an in-memory emergency pool, recovery log, CRC64 metadata, and A/B + checkpoint metadata primitives. This is not yet durable payload rollback, + an independent watchdog, or complete device recovery. +- The storage path includes AHCI block I/O, a small block-provider registry, + bootfs/devfs, read-only DKM FAT32, and a limited DSK FAT32 write path. +- The e1000 path and small network stack provide a boot-time/DSK data path for + Ethernet, ARP, DHCP, UDP, and DNS. They are not a socket stack. +- Linux compatibility code implements a VMX/EPT guest design, virtio-mmio + backends, and a guest-specific shared-memory/hypercall protocol. The full + nested-VMX test matrix remains pending in the repository. +- PE32+ loading and a PE32 interpreter exist with a limited Win32 shim. + Repository test matrices still list unverified cases and known failure + paths, so this is not general Windows binary compatibility. + +### Aspirational designs + +- SAS-R0-PCQ per-CPU bitmap scheduling is a design. `sched.h` and + `sched_stub.c` define an isolated contract only; there is no scheduler boot + hookup or context switch. +- `ipc_native.h` defines a possible native capability/message ABI. It has no + queue implementation, syscall transport, policy, or app integration. +- `deshab/syscall.h` reserves an experimental x86_64 native syscall surface. + There is no syscall entry point, dispatcher, user mode, or `libSystem` + equivalent. +- UTSM’s current xorshift-derived XOR stream and XOR capability tag are not + cryptographic confidentiality or integrity. The DRR root key is compiled + into the image. Claims of sealed-memory security must wait for real key + management, reviewed primitives, anti-replay checks, and adversarial tests. +- DKM unload, hotplug, driver fault domains, and DRR-driven device + quiesce/reset/reinit are designs, not demonstrated runtime properties. + +## XNU comparison + +| XNU area | What XNU provides | Current Deshab state | Parity requirement | +| --- | --- | --- | --- | +| Mach IPC | Kernel-managed ports, port rights, messages, notifications, port sets, RPC patterns, and mature scheduling/accounting behavior | Linux-guest SPSC rings and hypercalls exist. They are transport-specific and are not native task IPC. The new native ABI is declarations only. | Implement lifecycle-safe capability queues, transfer/attenuation, blocking and deadlines, cancellation, notifications, accounting, and stress/fuzz tests. | +| BSD/POSIX | Processes, credentials, signals, file descriptors, sockets, users/groups, resource controls, and a broad syscall contract | Custom Ring0 programs and services; no complete native process, descriptor, signal, credential, or POSIX syscall model | Publish a supported native contract and either implement a measured POSIX subset or explicitly route unsupported Linux behavior through the guest. | +| IOKit/DriverKit | Object/service registry, device matching, lifecycle and power management, user-space driver options, entitlements, and broad hardware support | DKM can stage and relocate native Ring0 modules through a kernel API. Runtime matching, power management, hotplug, and fault isolation are absent. | Add device/service registry, deterministic bind/unbind, hotplug, power transitions, resource revocation, and measured failure recovery. | +| Mach VM/pmap | Per-task VM maps, objects/pagers, copy-on-write, protections, wired memory, shared mappings, architecture pmap, TLB coherency, and mature accounting | HHDM/MMIO mappings, DMA allocation, UTSM segments, and capability checks exist. SAS-R0 intentionally does not provide XNU-style task isolation. There is no mature VM object/pager/COW layer. | Define SAS-R0 protection semantics, complete page lifecycle and SMP TLB rules, support mapped objects/COW where promised, and prove bounds/fault behavior. | +| VFS | Uniform vnode/name cache/mount model, permissions, locking, many filesystems, journaling integration, and scalable concurrent I/O | Small bootfs/devfs/VFS paths and limited FAT32 support; path, mount, locking, cache, and durability semantics are incomplete | Specify vnode-like object and mount contracts, concurrent lookup/I/O, cache invalidation, permissions policy, recovery, and filesystem conformance tests. | +| Networking | Sockets, TCP/IP and IPv6, routing, interfaces, packet filters, QoS, observability, and many drivers | e1000 plus minimal Ethernet/ARP/DHCP/UDP/DNS functionality; virtio-net is incomplete and there is no general socket/TCP/IPv6 API | Deliver sockets, TCP, IPv6, routing, DNS behavior, packet filtering, interrupt-driven drivers, loss/reorder tests, and sustained-load benchmarks. | +| kext | Historically loadable kernel extensions with dependency, code-signing, policy, lifecycle, and diagnostics; modern macOS moves many drivers out of the kernel | DKM’s small versioned ABI and staged ET_REL loader are a useful narrow base. Signing, policy, unload/hotplug, ABI evolution tests, and containment are absent. | Add authenticated modules, compatibility negotiation, dependency/lifecycle tests, unload safety, and recoverable driver fault domains. | +| dyld/libSystem | Stable process startup, dynamic loader, shared cache, libc/POSIX runtime, Objective-C/Swift runtime integration, and tooling | PIE loading exists in selected internal paths, but native apps have no general dynamic loader, libc, stable syscall dispatcher, or ABI toolchain contract | Provide crt startup, ELF dynamic linking/TLS, libc/runtime services, versioning, diagnostics, and an SDK conformance suite. | + +Architectural difference matters: XNU uses protected user tasks and multiple +security boundaries, while Deshab prioritizes a single-address-space Ring0 +model. Lower transition overhead in SAS-R0 cannot be counted as a win unless +fault propagation, revocation, recovery, and whole-system tail latency are +measured too. + +## Measurable “surpass” targets + +Every result must record source revision, compiler flags, firmware, CPU model, +core count, memory, timer source, warm-up, sample count, and raw data. XNU +comparisons must use the same physical machine where possible and equivalent +release builds. Passing one gate permits only the corresponding narrow claim. + +### S1 — DRR recovery and rollback + +- Run at least 10,000 deterministic fault-injection trials per class: dirty + page corruption, torn inactive metadata slot, torn active-slot switch, + exhausted normal allocator, task fault, and recoverable driver fault. +- Resume a protected service with correct pre-fault state at p99.9 within + 10 ms for page rollback, 50 ms for segment rollback, and 2 s for system-slot + rollback; report maximum as well as percentiles. +- Produce zero undetected corruptions, zero selection of a bad checkpoint, and + zero allocations from the normal heap on the recovery path. +- On the same workload, beat XNU’s closest service restart or OS recovery path + in p99.9 unavailable time by at least 2x. If the semantics are not + equivalent, publish only the absolute Deshab result. + +### S2 — UTSM confidentiality and integrity + +- Remove the xorshift/XOR stream, XOR `auth_tag`, and compiled root key. + Adopt reviewed KDF/encryption/authentication constructions with unique + nonces or tweaks, explicit key erasure, boot-derived root-key handling, and + replay-protected epochs. +- Pass published known-answer tests plus at least 10 million randomized + read/write/rekey/rollback operations under ASan/UBSan in a host harness. +- Detect 100% of injected ciphertext, metadata, tag, and stale-epoch changes + before plaintext release; demonstrate no plaintext in UTSM-managed backing + pages outside documented access windows. DMA and framebuffer exceptions + must be enumerated. +- Keep p99 4 KiB read and write overhead below 15% versus an unsealed Deshab + buffer on hardware with the selected acceleration enabled. Report a scalar + fallback separately; never trade away verification to meet this number. + +### S3 — O(1) per-CPU scheduler + +- Demonstrate by instrumentation that enqueue, dequeue, wake, and pick-next + touch a bounded number of bitmap/list words independent of runnable-task + count; no capability scan, MAC, checkpoint, allocation, or re-encryption may + occur on the switch path. +- Across 1, 64, 1,024, and 65,536 synthetic runnable tasks, keep p99 + pick-next below 250 cycles and variation between the smallest and largest + set below 5% on the reference CPU. +- Keep p99 same-CPU context switch below 1 microsecond and publish fairness, + starvation, affinity, timer, and 24-hour stress results. + +### S4 — Native capability IPC + +- Validate malformed sizes, stale generations, forged object IDs, rights + escalation, queue destruction races, cancellation, and capability transfer + with fuzzing and at least 100 million stress operations. +- For 64-byte inline request/reply on one core, achieve median below 1 + microsecond, p99 below 3 microseconds, and at least 2 million round trips per + second per core without dropping messages. +- Against `mach_msg` with equivalent payload, queue depth, copy semantics, and + synchronization on the same machine, achieve at least 1.5x throughput and + at most 0.67x p99 latency. Publish copy, transfer, and cross-core cases + separately. + +### S5 — DKM hotplug and fault containment + +- Complete 1,000 attach/use/detach cycles for every hotpluggable test device + with zero leaked mappings, vectors, DMA pages, capabilities, or callbacks. +- Inject faults at every driver entry point and DMA completion boundary. + Restore service at p99 within 100 ms without reboot and without modifying + memory outside the driver’s declared UTSM segments. +- Because all native code currently runs Ring0, document the mechanism that + makes the containment claim true (for example checked entry gates plus + recoverable memory domains, or a stronger execution boundary). A caught + exception alone is not containment. + +### S6 — Linux and PE compatibility breadth + +- Linux: on a published Arch package corpus of at least 100 CLI applications, + install and pass upstream smoke tests for at least 99%, including filesystem, + process, signal, networking, and terminal workloads. Complete 1,000 + park/resume cycles and a 24-hour mixed workload with no Deshab crash. +- PE: on a published corpus of at least 100 PE32/PE32+ applications, load at + least 95% and pass functional tests for at least 90%; include malformed + binary rejection, TLS, exception, GUI/input, file, and network cases. +- A Linux guest success is reported as guest compatibility, not a native + Linux syscall implementation. A PE image that only reaches its entry point + is reported as “loads,” not “works.” +- This gate can support a broader-binary-coverage claim than stock XNU only + for the exact tested corpus; it does not establish general Linux or Windows + compatibility. + +## First isolated slice + +This slice intentionally changes no boot order or build integration: + +- `CODE/UTSM/include/utsm/sched.h` reserves a scheduler contract and TCB/run + queue state. +- `CODE/UTSM/sched/sched_stub.c` makes every scheduler operation fail with an + explicit unsupported result. +- `CODE/UTSM/include/utsm/ipc_native.h` defines a bounded native IPC wire ABI, + distinct from `CODE/utsm-ipc/ipc_proto.h`. +- `CODE/sdk/include/deshab/syscall.h` reserves experimental native syscall + numbers and an x86_64 instruction wrapper. + +None of these files demonstrates a scheduler, native IPC, syscall entry, XNU +parity, or a “surpass” result. Those claims begin only after the gates above +are implemented and reproduced.