fix: let nested-KVM vmlaunch succeed with Virtual NMIs
Self-test and Linux guest were failing VM-instruction error 7. Share a KVM-style control adjust, request PIN_VIRTUAL_NMIS, and stop intercepting guest #PF. Land isolated XNU/Python/KDE scaffolding off the boot path. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
|||||||
|
#ifndef UTSM_IPC_NATIVE_H
|
||||||
|
#define UTSM_IPC_NATIVE_H
|
||||||
|
|
||||||
|
#include <utsm/types.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Experimental native Deshab capability IPC ABI.
|
||||||
|
*
|
||||||
|
* This is intentionally distinct from CODE/utsm-ipc/ipc_proto.h, which is a
|
||||||
|
* UTSM <-> Linux guest transport. No native queue implementation or syscall
|
||||||
|
* binding exists yet. All ABI fields use bounded fixed-width Deshab types;
|
||||||
|
* pointers and implementation-owned kernel addresses never appear on wire.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define UTSM_IPC_NATIVE_MAGIC 0x44534950u /* "DSIP" */
|
||||||
|
#define UTSM_IPC_NATIVE_ABI_MAJOR 1u
|
||||||
|
#define UTSM_IPC_NATIVE_ABI_MINOR 0u
|
||||||
|
|
||||||
|
#define UTSM_IPC_NATIVE_MESSAGE_BYTES 256u
|
||||||
|
#define UTSM_IPC_NATIVE_PAYLOAD_BYTES 144u
|
||||||
|
#define UTSM_IPC_NATIVE_MAX_CAPS 4u
|
||||||
|
#define UTSM_IPC_NATIVE_MIN_DEPTH 2u
|
||||||
|
#define UTSM_IPC_NATIVE_MAX_DEPTH 4096u
|
||||||
|
#define UTSM_IPC_NATIVE_DEADLINE_NONE (~0ULL)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The rights field requests or describes attenuation. Authority comes from
|
||||||
|
* the calling task's kernel capability table; copying or editing this value
|
||||||
|
* must never create authority.
|
||||||
|
*/
|
||||||
|
typedef struct utsm_ipc_native_cap {
|
||||||
|
u64 object_id;
|
||||||
|
u32 generation;
|
||||||
|
u32 rights;
|
||||||
|
} utsm_ipc_native_cap;
|
||||||
|
|
||||||
|
typedef enum utsm_ipc_native_right {
|
||||||
|
UTSM_IPC_RIGHT_SEND = (1u << 0),
|
||||||
|
UTSM_IPC_RIGHT_RECEIVE = (1u << 1),
|
||||||
|
UTSM_IPC_RIGHT_CALL = (1u << 2),
|
||||||
|
UTSM_IPC_RIGHT_REPLY = (1u << 3),
|
||||||
|
UTSM_IPC_RIGHT_TRANSFER = (1u << 4),
|
||||||
|
UTSM_IPC_RIGHT_DUPLICATE = (1u << 5),
|
||||||
|
UTSM_IPC_RIGHT_MANAGE = (1u << 6),
|
||||||
|
UTSM_IPC_RIGHT_WAIT = (1u << 7),
|
||||||
|
UTSM_IPC_RIGHT_SIGNAL = (1u << 8)
|
||||||
|
} utsm_ipc_native_right;
|
||||||
|
|
||||||
|
#define UTSM_IPC_RIGHT_ALL ((u32)((1u << 9) - 1u))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Queue operations are carried in a u16 ABI field. CALL creates a transaction
|
||||||
|
* ID; REPLY must name that transaction. WAIT/SIGNAL are queue-state events,
|
||||||
|
* not substitutes for SEND/RECEIVE.
|
||||||
|
*/
|
||||||
|
typedef enum utsm_ipc_native_queue_op {
|
||||||
|
UTSM_IPC_QUEUE_CREATE = 1,
|
||||||
|
UTSM_IPC_QUEUE_DESTROY = 2,
|
||||||
|
UTSM_IPC_QUEUE_SEND = 3,
|
||||||
|
UTSM_IPC_QUEUE_RECEIVE = 4,
|
||||||
|
UTSM_IPC_QUEUE_CALL = 5,
|
||||||
|
UTSM_IPC_QUEUE_REPLY = 6,
|
||||||
|
UTSM_IPC_QUEUE_WAIT = 7,
|
||||||
|
UTSM_IPC_QUEUE_SIGNAL = 8,
|
||||||
|
UTSM_IPC_QUEUE_QUERY = 9
|
||||||
|
} utsm_ipc_native_queue_op;
|
||||||
|
|
||||||
|
#define UTSM_IPC_MSG_F_NONBLOCK (1u << 0)
|
||||||
|
#define UTSM_IPC_MSG_F_MOVE_CAPS (1u << 1)
|
||||||
|
#define UTSM_IPC_MSG_F_EXPECT_REPLY (1u << 2)
|
||||||
|
#define UTSM_IPC_MSG_F_CANCEL (1u << 3)
|
||||||
|
|
||||||
|
typedef enum utsm_ipc_native_result {
|
||||||
|
UTSM_IPC_NATIVE_OK = 0,
|
||||||
|
UTSM_IPC_NATIVE_ERR_INVALID = -1,
|
||||||
|
UTSM_IPC_NATIVE_ERR_ACCESS = -2,
|
||||||
|
UTSM_IPC_NATIVE_ERR_STALE_CAP = -3,
|
||||||
|
UTSM_IPC_NATIVE_ERR_FULL = -4,
|
||||||
|
UTSM_IPC_NATIVE_ERR_EMPTY = -5,
|
||||||
|
UTSM_IPC_NATIVE_ERR_CLOSED = -6,
|
||||||
|
UTSM_IPC_NATIVE_ERR_TIMEOUT = -7,
|
||||||
|
UTSM_IPC_NATIVE_ERR_CANCELLED = -8,
|
||||||
|
UTSM_IPC_NATIVE_ERR_UNSUPPORTED = -38
|
||||||
|
} utsm_ipc_native_result;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fixed 256-byte message. message_size must equal sizeof(this structure),
|
||||||
|
* payload_length is bounded by PAYLOAD_BYTES, and capability_count is bounded
|
||||||
|
* by MAX_CAPS. Receivers must reject nonzero reserved fields.
|
||||||
|
*/
|
||||||
|
typedef struct utsm_ipc_native_message {
|
||||||
|
u32 magic;
|
||||||
|
u16 abi_major;
|
||||||
|
u16 abi_minor;
|
||||||
|
|
||||||
|
u16 operation; /* utsm_ipc_native_queue_op */
|
||||||
|
u16 flags;
|
||||||
|
u32 message_size;
|
||||||
|
u64 transaction_id;
|
||||||
|
|
||||||
|
utsm_ipc_native_cap endpoint;
|
||||||
|
u32 payload_length;
|
||||||
|
u16 capability_count;
|
||||||
|
u16 reserved0;
|
||||||
|
|
||||||
|
utsm_ipc_native_cap capabilities[UTSM_IPC_NATIVE_MAX_CAPS];
|
||||||
|
u8 payload[UTSM_IPC_NATIVE_PAYLOAD_BYTES];
|
||||||
|
} utsm_ipc_native_message;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fixed queue-control request for a future syscall dispatcher. depth is valid
|
||||||
|
* only for CREATE and must stay within MIN_DEPTH..MAX_DEPTH. deadline_ns is
|
||||||
|
* an absolute monotonic deadline or UTSM_IPC_NATIVE_DEADLINE_NONE.
|
||||||
|
*/
|
||||||
|
typedef struct utsm_ipc_native_queue_request {
|
||||||
|
u16 abi_major;
|
||||||
|
u16 abi_minor;
|
||||||
|
u16 operation; /* utsm_ipc_native_queue_op */
|
||||||
|
u16 flags;
|
||||||
|
|
||||||
|
u32 depth;
|
||||||
|
u32 message_size;
|
||||||
|
utsm_ipc_native_cap queue;
|
||||||
|
u64 deadline_ns;
|
||||||
|
} utsm_ipc_native_queue_request;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(utsm_ipc_native_cap) == 16u,
|
||||||
|
"native IPC capability ABI changed");
|
||||||
|
_Static_assert(sizeof(utsm_ipc_native_message) ==
|
||||||
|
UTSM_IPC_NATIVE_MESSAGE_BYTES,
|
||||||
|
"native IPC message ABI changed");
|
||||||
|
_Static_assert(sizeof(utsm_ipc_native_queue_request) == 40u,
|
||||||
|
"native IPC queue request ABI changed");
|
||||||
|
|
||||||
|
#endif /* UTSM_IPC_NATIVE_H */
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#ifndef UTSM_SCHED_H
|
||||||
|
#define UTSM_SCHED_H
|
||||||
|
|
||||||
|
#include <utsm/types.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Experimental SAS-R0-PCQ scheduler contract.
|
||||||
|
*
|
||||||
|
* This header reserves the first ABI shape for a future per-CPU O(1)
|
||||||
|
* priority-bitmap scheduler. It is not connected to boot or context-switch
|
||||||
|
* code. Structures begin with version/size where they may cross component
|
||||||
|
* boundaries; existing fields must not be reordered and extensions append.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define UTSM_SCHED_ABI_VERSION 1u
|
||||||
|
#define UTSM_SCHED_PRIORITY_COUNT 64u
|
||||||
|
#define UTSM_SCHED_INVALID_SLOT 0xffffffffu
|
||||||
|
#define UTSM_SCHED_PRIORITY_HIGHEST 0u
|
||||||
|
#define UTSM_SCHED_PRIORITY_LOWEST (UTSM_SCHED_PRIORITY_COUNT - 1u)
|
||||||
|
|
||||||
|
/* Explicit API results. The isolated stub returns UNSUPPORTED for every call. */
|
||||||
|
typedef enum utsm_sched_result {
|
||||||
|
UTSM_SCHED_OK = 0,
|
||||||
|
UTSM_SCHED_ERR_INVALID = -1,
|
||||||
|
UTSM_SCHED_ERR_STATE = -2,
|
||||||
|
UTSM_SCHED_ERR_NO_TASK = -3,
|
||||||
|
UTSM_SCHED_ERR_UNSUPPORTED = -38
|
||||||
|
} utsm_sched_result;
|
||||||
|
|
||||||
|
/* Values are stored in u32 fields so the TCB layout does not depend on enum size. */
|
||||||
|
typedef enum utsm_sched_task_state {
|
||||||
|
UTSM_SCHED_TASK_UNUSED = 0,
|
||||||
|
UTSM_SCHED_TASK_NEW,
|
||||||
|
UTSM_SCHED_TASK_READY,
|
||||||
|
UTSM_SCHED_TASK_RUNNING,
|
||||||
|
UTSM_SCHED_TASK_BLOCKED,
|
||||||
|
UTSM_SCHED_TASK_SLEEPING,
|
||||||
|
UTSM_SCHED_TASK_RECOVERING,
|
||||||
|
UTSM_SCHED_TASK_FAULTED,
|
||||||
|
UTSM_SCHED_TASK_ZOMBIE
|
||||||
|
} utsm_sched_task_state;
|
||||||
|
|
||||||
|
#define UTSM_SCHED_TASK_F_KERNEL (1u << 0)
|
||||||
|
#define UTSM_SCHED_TASK_F_IDLE (1u << 1)
|
||||||
|
#define UTSM_SCHED_TASK_F_NO_MIGRATE (1u << 2)
|
||||||
|
#define UTSM_SCHED_TASK_F_RECOVERY (1u << 3)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimum x86_64 resume context. Volatile registers belong to the eventual
|
||||||
|
* interrupt/syscall frame and are deliberately not duplicated here.
|
||||||
|
*/
|
||||||
|
typedef struct utsm_sched_arch_context {
|
||||||
|
u64 rsp;
|
||||||
|
u64 rip;
|
||||||
|
u64 rflags;
|
||||||
|
u64 rbx;
|
||||||
|
u64 rbp;
|
||||||
|
u64 r12;
|
||||||
|
u64 r13;
|
||||||
|
u64 r14;
|
||||||
|
u64 r15;
|
||||||
|
} utsm_sched_arch_context;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Task control block prefix.
|
||||||
|
*
|
||||||
|
* runq_prev/runq_next are task-table slots, not pointers, so queues remain
|
||||||
|
* relocatable. priority 0 is highest. crypto_context is loaded as a pointer
|
||||||
|
* during a future switch; switch code must not scan capabilities or perform
|
||||||
|
* cryptography/checkpoint work.
|
||||||
|
*/
|
||||||
|
typedef struct utsm_sched_tcb {
|
||||||
|
u32 abi_version;
|
||||||
|
u32 struct_size;
|
||||||
|
|
||||||
|
u64 task_id;
|
||||||
|
u32 process_slot;
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
u32 state; /* utsm_sched_task_state */
|
||||||
|
u32 priority;
|
||||||
|
u32 assigned_cpu;
|
||||||
|
u32 last_cpu;
|
||||||
|
u64 affinity_mask;
|
||||||
|
|
||||||
|
u64 timeslice_ns;
|
||||||
|
u64 wake_deadline_ns;
|
||||||
|
void *crypto_context;
|
||||||
|
|
||||||
|
u32 runq_prev;
|
||||||
|
u32 runq_next;
|
||||||
|
utsm_sched_arch_context arch;
|
||||||
|
|
||||||
|
u64 reserved[4];
|
||||||
|
} utsm_sched_tcb;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One ready bitmap and one intrusive FIFO per priority form the planned O(1)
|
||||||
|
* run queue. A set bit means that head[priority] is valid. Synchronization is
|
||||||
|
* an implementation concern and is intentionally not implied by this layout.
|
||||||
|
*/
|
||||||
|
typedef struct utsm_sched_run_queue {
|
||||||
|
u32 abi_version;
|
||||||
|
u32 struct_size;
|
||||||
|
u32 cpu_id;
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
u64 ready_bitmap;
|
||||||
|
u32 head[UTSM_SCHED_PRIORITY_COUNT];
|
||||||
|
u32 tail[UTSM_SCHED_PRIORITY_COUNT];
|
||||||
|
|
||||||
|
u32 current_task_slot;
|
||||||
|
u32 idle_task_slot;
|
||||||
|
u32 ready_count;
|
||||||
|
u32 reserved0;
|
||||||
|
} utsm_sched_run_queue;
|
||||||
|
|
||||||
|
int utsm_sched_init(void);
|
||||||
|
int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue);
|
||||||
|
int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id,
|
||||||
|
u32 process_slot, u32 priority, u64 affinity_mask);
|
||||||
|
int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task);
|
||||||
|
int utsm_sched_dequeue(utsm_sched_tcb *task);
|
||||||
|
int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state);
|
||||||
|
int utsm_sched_wake(utsm_sched_tcb *task);
|
||||||
|
int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task);
|
||||||
|
int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns,
|
||||||
|
utsm_sched_tcb **out_preempted_task);
|
||||||
|
|
||||||
|
#endif /* UTSM_SCHED_H */
|
||||||
@@ -88,5 +88,7 @@ int vmm_self_test(void);
|
|||||||
/* VMM 状态查询 */
|
/* VMM 状态查询 */
|
||||||
int vmm_is_ready(void);
|
int vmm_is_ready(void);
|
||||||
u64 vmm_get_eptp(void);
|
u64 vmm_get_eptp(void);
|
||||||
|
/* P8.5: VMCS 物理地址(linux_launch 前 VMCLEAR+VMPTRLD 用) */
|
||||||
|
u64 vmm_get_vmcs_phys(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#define IA32_VMX_PROCBASED_CTLS 0x482
|
#define IA32_VMX_PROCBASED_CTLS 0x482
|
||||||
#define IA32_VMX_EXIT_CTLS 0x483
|
#define IA32_VMX_EXIT_CTLS 0x483
|
||||||
#define IA32_VMX_ENTRY_CTLS 0x484
|
#define IA32_VMX_ENTRY_CTLS 0x484
|
||||||
|
#define IA32_VMX_MISC 0x485
|
||||||
#define IA32_VMX_PROCBASED_CTLS2 0x48B
|
#define IA32_VMX_PROCBASED_CTLS2 0x48B
|
||||||
#define IA32_VMX_EPT_VPID_CAP 0x48C
|
#define IA32_VMX_EPT_VPID_CAP 0x48C
|
||||||
#define IA32_VMX_TRUE_PINBASED_CTLS 0x48D
|
#define IA32_VMX_TRUE_PINBASED_CTLS 0x48D
|
||||||
@@ -37,15 +38,9 @@
|
|||||||
|
|
||||||
/* Control fields */
|
/* Control fields */
|
||||||
#define VMCS_VPID 0x0000
|
#define VMCS_VPID 0x0000
|
||||||
#define VMCS_GUEST_ES_ADDR 0x0006
|
/* P8.5: guest interrupt status 真实编码为 0x0810(16-bit guest-state),
|
||||||
#define VMCS_GUEST_CS_ADDR 0x0008
|
* 旧值 0x0009 是 posted-interrupt 描述符区域,属于错误编码。 */
|
||||||
#define VMCS_GUEST_SS_ADDR 0x000A
|
#define VMCS_GUEST_INTR_STATUS 0x0810
|
||||||
#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
|
|
||||||
#define VMCS_IO_BITMAP_A 0x2000
|
#define VMCS_IO_BITMAP_A 0x2000
|
||||||
#define VMCS_IO_BITMAP_B 0x2002
|
#define VMCS_IO_BITMAP_B 0x2002
|
||||||
#define VMCS_MSR_BITMAP 0x2004
|
#define VMCS_MSR_BITMAP 0x2004
|
||||||
@@ -54,15 +49,22 @@
|
|||||||
|
|
||||||
/* Read-only data fields */
|
/* Read-only data fields */
|
||||||
#define VMCS_GUEST_PHYSICAL_ADDR 0x2400
|
#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_REASON 0x4402
|
||||||
#define VMCS_EXIT_QUALIFICATION 0x6400
|
#define VMCS_EXIT_QUALIFICATION 0x6400
|
||||||
#define VMCS_IO_RCX 0x6404
|
/* P8.5: IO RCX/RSI/RDI/RIP 真实编码为 0x6402/0x6404/0x6406/0x6408,
|
||||||
#define VMCS_IO_RSI 0x6406
|
* 旧值整体右移了一格。guest-linear-address = 0x640A 不变。 */
|
||||||
#define VMCS_IO_RDI 0x6408
|
#define VMCS_IO_RCX 0x6402
|
||||||
#define VMCS_IO_RIP 0x640A
|
#define VMCS_IO_RSI 0x6404
|
||||||
|
#define VMCS_IO_RDI 0x6406
|
||||||
|
#define VMCS_IO_RIP 0x6408
|
||||||
#define VMCS_GUEST_LINEAR_ADDR 0x640A
|
#define VMCS_GUEST_LINEAR_ADDR 0x640A
|
||||||
#define VMCS_INSTRUCTION_LENGTH 0x440C
|
#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 */
|
/* Guest-state fields */
|
||||||
#define VMCS_GUEST_ES_SELECTOR 0x0800
|
#define VMCS_GUEST_ES_SELECTOR 0x0800
|
||||||
@@ -82,8 +84,8 @@
|
|||||||
#define VMCS_GUEST_LDTR_LIMIT 0x480C
|
#define VMCS_GUEST_LDTR_LIMIT 0x480C
|
||||||
#define VMCS_GUEST_TR_LIMIT 0x480E
|
#define VMCS_GUEST_TR_LIMIT 0x480E
|
||||||
#define VMCS_GUEST_CS_ACCESS 0x4816
|
#define VMCS_GUEST_CS_ACCESS 0x4816
|
||||||
#define VMCS_GUEST_SS_ACCESS 0x481A
|
#define VMCS_GUEST_SS_ACCESS 0x4818
|
||||||
#define VMCS_GUEST_DS_ACCESS 0x4818
|
#define VMCS_GUEST_DS_ACCESS 0x481A
|
||||||
#define VMCS_GUEST_ES_ACCESS 0x4814
|
#define VMCS_GUEST_ES_ACCESS 0x4814
|
||||||
#define VMCS_GUEST_FS_ACCESS 0x481C
|
#define VMCS_GUEST_FS_ACCESS 0x481C
|
||||||
#define VMCS_GUEST_GS_ACCESS 0x481E
|
#define VMCS_GUEST_GS_ACCESS 0x481E
|
||||||
@@ -165,11 +167,13 @@
|
|||||||
#define VMCS_VM_EXIT_CONTROLS 0x400C
|
#define VMCS_VM_EXIT_CONTROLS 0x400C
|
||||||
#define VMCS_VM_ENTRY_CONTROLS 0x4012
|
#define VMCS_VM_ENTRY_CONTROLS 0x4012
|
||||||
#define VMCS_VM_ENTRY_INTERRUPT_INFO 0x4016
|
#define VMCS_VM_ENTRY_INTERRUPT_INFO 0x4016
|
||||||
|
#define VMCS_VM_ENTRY_EXCEPTION_ERROR 0x4018
|
||||||
#define VMCS_SECONDARY_VM_EXEC_CONTROL 0x401E
|
#define VMCS_SECONDARY_VM_EXEC_CONTROL 0x401E
|
||||||
|
|
||||||
/* ===== Pin-based controls ===== */
|
/* ===== Pin-based controls ===== */
|
||||||
#define PIN_EXT_INTERRUPT_EXITING (1ULL << 0)
|
#define PIN_EXT_INTERRUPT_EXITING (1ULL << 0)
|
||||||
#define PIN_NMI_EXITING (1ULL << 3)
|
#define PIN_NMI_EXITING (1ULL << 3)
|
||||||
|
#define PIN_VIRTUAL_NMIS (1ULL << 5)
|
||||||
#define PIN_VMX_PREEMPTION_TIMER (1ULL << 6)
|
#define PIN_VMX_PREEMPTION_TIMER (1ULL << 6)
|
||||||
|
|
||||||
/* ===== Primary processor-based controls ===== */
|
/* ===== 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 */
|
int vmx_vmcs_clear(u64 phys); /* P8.4: VMCLEAR before VMLAUNCH */
|
||||||
u64 vmx_vmcs_read(u64 field);
|
u64 vmx_vmcs_read(u64 field);
|
||||||
void vmx_vmcs_write(u64 field, u64 value);
|
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_vmlaunch(void);
|
||||||
int vmx_vmresume(void);
|
int vmx_vmresume(void);
|
||||||
|
|
||||||
u64 vmx_read_msr(u32 msr);
|
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);
|
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 实现) ===== */
|
/* ===== 虚拟中断注入(vmexit.c 实现) ===== */
|
||||||
/* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。
|
/* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。
|
||||||
@@ -281,9 +297,9 @@ u64 vmx_get_host_cr3(void);
|
|||||||
* 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */
|
* 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */
|
||||||
void vmx_guest_queue_irq(u32 vector);
|
void vmx_guest_queue_irq(u32 vector);
|
||||||
|
|
||||||
/* VMX preemption timer:返回 1ms 对应的 timer 计数值(按 IA32_VMX_BASIC
|
/* VMX preemption timer:返回 host polling 周期对应的 timer 计数值。
|
||||||
* bits[55:48] 的 scale 换算 TSC 频率)。 */
|
* 当前周期为 10ms,避免 nested KVM 在 1ms VM-exit 下失去前进性。 */
|
||||||
u64 vmx_preemption_quantum_1ms(void);
|
u64 vmx_preemption_quantum(void);
|
||||||
/* 检查 CPU 是否支持 VMX preemption timer。 */
|
/* 检查 CPU 是否支持 VMX preemption timer。 */
|
||||||
int vmx_preemption_timer_supported(void);
|
int vmx_preemption_timer_supported(void);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Isolated scheduler placeholder.
|
||||||
|
*
|
||||||
|
* Deliberately not linked by the UTSM build. These functions make accidental
|
||||||
|
* use fail explicitly until the per-CPU bitmap scheduler and switch path are
|
||||||
|
* implemented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <utsm/sched.h>
|
||||||
|
|
||||||
|
int utsm_sched_init(void) {
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue) {
|
||||||
|
UTSM_UNUSED(cpu_id);
|
||||||
|
UTSM_UNUSED(queue);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id,
|
||||||
|
u32 process_slot, u32 priority, u64 affinity_mask) {
|
||||||
|
UTSM_UNUSED(task);
|
||||||
|
UTSM_UNUSED(task_id);
|
||||||
|
UTSM_UNUSED(process_slot);
|
||||||
|
UTSM_UNUSED(priority);
|
||||||
|
UTSM_UNUSED(affinity_mask);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task) {
|
||||||
|
UTSM_UNUSED(cpu_id);
|
||||||
|
UTSM_UNUSED(task);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_dequeue(utsm_sched_tcb *task) {
|
||||||
|
UTSM_UNUSED(task);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state) {
|
||||||
|
UTSM_UNUSED(task);
|
||||||
|
UTSM_UNUSED(blocked_state);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_wake(utsm_sched_tcb *task) {
|
||||||
|
UTSM_UNUSED(task);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task) {
|
||||||
|
UTSM_UNUSED(cpu_id);
|
||||||
|
UTSM_UNUSED(out_task);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns,
|
||||||
|
utsm_sched_tcb **out_preempted_task) {
|
||||||
|
UTSM_UNUSED(cpu_id);
|
||||||
|
UTSM_UNUSED(elapsed_ns);
|
||||||
|
UTSM_UNUSED(out_preempted_task);
|
||||||
|
return UTSM_SCHED_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
+10
-8
@@ -76,8 +76,9 @@ static u64 *ept_walk(u64 gpa, int alloc_missing) {
|
|||||||
if (!alloc_missing) return (u64 *)0;
|
if (!alloc_missing) return (u64 *)0;
|
||||||
u64 new_phys = ept_alloc_page();
|
u64 new_phys = ept_alloc_page();
|
||||||
if (new_phys == 0) return (u64 *)0;
|
if (new_phys == 0) return (u64 *)0;
|
||||||
/* 中间页表项:RWX + WB 内存类型 */
|
/* 非叶 EPT 表项只含物理地址与 RWX。bits 5:3 在非叶项中
|
||||||
table[indices[level]] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3);
|
* 保留,写入 leaf memory type 会触发 EPT misconfiguration。 */
|
||||||
|
table[indices[level]] = new_phys | EPT_RWX;
|
||||||
/* 切换到新分配的下一级页表 */
|
/* 切换到新分配的下一级页表 */
|
||||||
table = (u64 *)phys_to_virt(new_phys);
|
table = (u64 *)phys_to_virt(new_phys);
|
||||||
} else {
|
} else {
|
||||||
@@ -145,7 +146,7 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) {
|
|||||||
if (!(entry & EPT_READ)) {
|
if (!(entry & EPT_READ)) {
|
||||||
u64 new_phys = ept_alloc_page();
|
u64 new_phys = ept_alloc_page();
|
||||||
if (new_phys == 0) return -3;
|
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);
|
table = (u64 *)phys_to_virt(new_phys);
|
||||||
} else {
|
} else {
|
||||||
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
|
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)) {
|
if (!(entry & EPT_READ)) {
|
||||||
u64 new_phys = ept_alloc_page();
|
u64 new_phys = ept_alloc_page();
|
||||||
if (new_phys == 0) return -4;
|
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);
|
table = (u64 *)phys_to_virt(new_phys);
|
||||||
} else {
|
} else {
|
||||||
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
|
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
|
||||||
@@ -210,7 +211,7 @@ void ept_flush_ept(void) {
|
|||||||
int err;
|
int err;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"invept (%2), %1\n"
|
"invept (%2), %1\n"
|
||||||
"jnc 1f\n"
|
"ja 1f\n"
|
||||||
"mov $1, %0\n"
|
"mov $1, %0\n"
|
||||||
"jmp 2f\n"
|
"jmp 2f\n"
|
||||||
"1:\n"
|
"1:\n"
|
||||||
@@ -234,7 +235,7 @@ void ept_flush_vpid(u16 vpid) {
|
|||||||
int err;
|
int err;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"invvpid (%2), %1\n"
|
"invvpid (%2), %1\n"
|
||||||
"jnc 1f\n"
|
"ja 1f\n"
|
||||||
"mov $1, %0\n"
|
"mov $1, %0\n"
|
||||||
"jmp 2f\n"
|
"jmp 2f\n"
|
||||||
"1:\n"
|
"1:\n"
|
||||||
@@ -296,7 +297,8 @@ u64 ept_gpa_to_hpa(u64 gpa) {
|
|||||||
|
|
||||||
u64 ept_get_eptp(void) {
|
u64 ept_get_eptp(void) {
|
||||||
if (!g_ept_ready) return 0;
|
if (!g_ept_ready) return 0;
|
||||||
/* EPTP: bits [5:0] = memtype, bits [7:6] = walk length-1 (3 for 4-level),
|
/* EPTP: bits [2:0]=memory type WB(6), bits [5:3]=walk length-1 (3 => 4-level).
|
||||||
* bits [51:12] = PML4 HPA */
|
* 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;
|
return g_ept_pml4_phys | EPTP_WALK_LEN_4 | EPTP_MEMTYPE_WB;
|
||||||
}
|
}
|
||||||
|
|||||||
+146
-38
@@ -17,6 +17,7 @@
|
|||||||
#include <utsm/ept.h>
|
#include <utsm/ept.h>
|
||||||
#include <utsm/vmm.h>
|
#include <utsm/vmm.h>
|
||||||
#include <utsm/virtio_mmio.h>
|
#include <utsm/virtio_mmio.h>
|
||||||
|
#include <utsm/ipc_shm.h>
|
||||||
#include <utsm/log.h>
|
#include <utsm/log.h>
|
||||||
#include <utsm/types.h>
|
#include <utsm/types.h>
|
||||||
#include "../arch/x86_64/limine.h"
|
#include "../arch/x86_64/limine.h"
|
||||||
@@ -50,14 +51,6 @@ static inline u64 read_cr4_local(void) {
|
|||||||
return v;
|
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) */
|
/* MSR bitmap (4KB, all zeros = don't intercept any MSR) */
|
||||||
static u8 g_linux_msr_bitmap[4096] __attribute__((aligned(4096)));
|
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 ===== */
|
/* ===== VMCS guest state for Linux ===== */
|
||||||
|
|
||||||
static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
|
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);
|
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
|
||||||
/* CR3 points to guest PML4 (GPA) */
|
/* CR3 points to guest PML4 (GPA) */
|
||||||
vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_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 */
|
/* Segment selectors from guest GDT */
|
||||||
vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08);
|
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_GDTR_LIMIT, 0x28);
|
||||||
vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF);
|
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_CS_ACCESS, 0xA09B); /* 64-bit code, L=1 */
|
||||||
vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093);
|
vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093);
|
||||||
vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093);
|
vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093);
|
||||||
vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093);
|
vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093);
|
||||||
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093);
|
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0x1C093); /* unusable (selector=0) */
|
||||||
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093);
|
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0x1C093); /* unusable (selector=0) */
|
||||||
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82); /* unusable */
|
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x10082); /* unusable (selector=0) */
|
||||||
vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */
|
vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */
|
||||||
|
|
||||||
/* Guest RIP = Linux 64-bit entry (startup_64) */
|
/* 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 ===== */
|
/* ===== VMCS controls for Linux ===== */
|
||||||
|
|
||||||
static void linux_vmcs_setup_controls(u64 eptp) {
|
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(若支持)。
|
/* Pin-based:外部中断 exit + NMI exit + VMX preemption timer(若支持)。
|
||||||
* preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、
|
* preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、
|
||||||
* 推进 guest PIT tick、注入 pending virtio/COM1 IRQ。 */
|
* 推进 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()) {
|
if (vmx_preemption_timer_supported()) {
|
||||||
pin_want |= PIN_VMX_PREEMPTION_TIMER;
|
pin_want |= PIN_VMX_PREEMPTION_TIMER;
|
||||||
}
|
}
|
||||||
@@ -222,13 +233,12 @@ static void linux_vmcs_setup_controls(u64 eptp) {
|
|||||||
| CPU_BASED_INVLPG_EXITING;
|
| CPU_BASED_INVLPG_EXITING;
|
||||||
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
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 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
|
||||||
{
|
cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2);
|
||||||
u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2);
|
if (!(cpu2 & SEC_EXEC_ENABLE_EPT)) {
|
||||||
u32 allowed1 = (u32)(msr >> 32);
|
log_warn("[LINUX] EPT not available (shadow paging) - guest GPA map disabled");
|
||||||
cpu2 &= allowed1;
|
|
||||||
cpu2 |= SEC_EXEC_ENABLE_EPT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||||
@@ -249,20 +259,31 @@ static void linux_vmcs_setup_controls(u64 eptp) {
|
|||||||
|
|
||||||
/* MSR bitmap */
|
/* MSR bitmap */
|
||||||
{
|
{
|
||||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_linux_msr_bitmap);
|
||||||
u64 msr_bitmap_phys = (u64)g_linux_msr_bitmap - hhdm;
|
log_hex64("[LINUX] MSR bitmap phys=", msr_bitmap_phys);
|
||||||
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
|
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cpu2 & SEC_EXEC_ENABLE_EPT) {
|
||||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
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 */
|
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) */
|
/* Linux owns its IDT. Intercepting #PF here livelocks: the guest
|
||||||
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6) | (1ULL << 8));
|
* 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 ===== */
|
/* ===== Global for passing RSI to launch asm ===== */
|
||||||
static volatile u64 g_linux_rsi;
|
static volatile u64 g_linux_rsi;
|
||||||
|
static volatile int g_linux_launch_failed;
|
||||||
|
static volatile u64 g_linux_launch_flags;
|
||||||
|
|
||||||
/* ===== Linux launch =====
|
/* ===== Linux launch =====
|
||||||
*
|
*
|
||||||
@@ -306,17 +327,44 @@ int linux_launch(void) {
|
|||||||
linux_vmcs_setup_controls(vmm_get_eptp());
|
linux_vmcs_setup_controls(vmm_get_eptp());
|
||||||
linux_vmcs_setup_host_state();
|
linux_vmcs_setup_host_state();
|
||||||
|
|
||||||
/* 初始 arm preemption timer(1ms 后首次周期 exit) */
|
/* 仅在 pin-based control 真正启用 timer 时 arm。 */
|
||||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
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) */
|
/* Set RSI for Linux boot_params (passed via global, loaded in asm) */
|
||||||
g_linux_rsi = gi->bootparams_gpa;
|
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] RIP=", gi->kernel_entry);
|
||||||
log_hex64("[LINUX] RSP=", LINUX_GUEST_STACK_GPA + 0x1000);
|
log_hex64("[LINUX] RSP=", LINUX_GUEST_STACK_GPA + 0x1000);
|
||||||
log_hex64("[LINUX] RSI=", gi->bootparams_gpa);
|
log_hex64("[LINUX] RSI=", gi->bootparams_gpa);
|
||||||
log_hex64("[LINUX] CR3=", gi->pgt_gpa);
|
log_hex64("[LINUX] CR3=", gi->pgt_gpa);
|
||||||
log_hex64("[LINUX] GDTR=", gi->gdt_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");
|
log_info("[LINUX] vmlaunch");
|
||||||
|
|
||||||
/* vmlaunch with RSI = boot_params.
|
/* vmlaunch with RSI = boot_params.
|
||||||
@@ -324,38 +372,98 @@ int linux_launch(void) {
|
|||||||
* - On failure: failed=1, falls through
|
* - On failure: failed=1, falls through
|
||||||
* - On success: enters guest, VM-Exit → vmx_vm_exit_handler
|
* - On success: enters guest, VM-Exit → vmx_vm_exit_handler
|
||||||
* - On terminate: handler restores RSP, jmps to post_guest label */
|
* - On terminate: handler restores RSP, jmps to post_guest label */
|
||||||
int failed;
|
g_linux_launch_failed = 1;
|
||||||
|
g_linux_launch_flags = 0;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t"
|
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t"
|
||||||
"leaq 1f(%%rip), %%rax\n\t"
|
"leaq 1f(%%rip), %%rax\n\t"
|
||||||
"movq %%rax, g_saved_return_rip(%%rip)\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 */
|
"movq g_linux_rsi(%%rip), %%rsi\n\t" /* RSI = boot_params GPA */
|
||||||
"vmlaunch\n\t"
|
"vmlaunch\n\t"
|
||||||
/* ---- failure path ---- */
|
/* ---- 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"
|
"jmp 2f\n\t"
|
||||||
/* ---- post_guest: terminate path jmps here ---- */
|
/* ---- post_guest: terminate path jmps here ---- */
|
||||||
"1:\n\t"
|
"1:\n\t"
|
||||||
"movl $0, %0\n\t"
|
"movl $0, g_linux_launch_failed(%%rip)\n\t"
|
||||||
"2:\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) {
|
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);
|
log_hex64("[LINUX] vmlaunch failed, error=", error);
|
||||||
g_linux_guest_active = 0;
|
g_linux_guest_active = 0;
|
||||||
return -3;
|
return -3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Guest parked (HLT) — Linux daemon 已驻留,等待 linux_resume() 唤醒 */
|
/* Only handle_hlt() may mark the Linux guest parked. A fatal exception,
|
||||||
g_linux_guest_active = 0; /* 回到 host 上下文,清除 active 标志 */
|
* triple fault, or EPT failure also returns through post_guest, but must
|
||||||
g_guest_parked = 1; /* 标记 guest 已 park,linux_resume() 可唤醒 */
|
* not make the compatibility layer report a ready daemon. */
|
||||||
log_info("[LINUX] guest parked (daemon ready)");
|
g_linux_guest_active = 0;
|
||||||
log_hex64("[LINUX] last exit reason=", g_last_exit_reason);
|
log_hex64("[LINUX] last exit reason=", g_last_exit_reason);
|
||||||
log_hex64("[LINUX] vmexit count=", vmexit_get_count());
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+122
-47
@@ -8,26 +8,36 @@
|
|||||||
|
|
||||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
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) {
|
static const char *vmx_error_str(u64 err) {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case 1: return "VMCALL/VMCLEAR/VMLAUNCH/VMRESUME with non-launched VMCS";
|
case 1: return "VMCALL executed in VMX root operation";
|
||||||
case 2: return "VMRESUME after VMXOFF";
|
case 2: return "VMCLEAR with invalid physical address";
|
||||||
case 3: return "VMRESUME with corrupted VMCS";
|
case 3: return "VMCLEAR with VMXON pointer";
|
||||||
case 4: return "vmlaunch with non-clear VMCS";
|
case 4: return "VMLAUNCH with non-clear VMCS";
|
||||||
case 5: return "VM operation invalid";
|
case 5: return "VMRESUME with non-launched VMCS";
|
||||||
case 6: return "VMCS revision mismatch";
|
case 6: return "VMRESUME after VMXOFF";
|
||||||
case 7: return "VMCS shadowing mismatch";
|
case 7: return "VM entry with invalid VM-execution control fields";
|
||||||
case 8: return "invalid guest state";
|
case 8: return "VM entry with invalid host-state fields";
|
||||||
case 9: return "host state invalid";
|
case 9: return "VMPTRLD with invalid physical address";
|
||||||
case 10: return "VM-execution control invalid";
|
case 10: return "VMPTRLD with VMXON pointer";
|
||||||
case 11: return "VM-exit control invalid";
|
case 11: return "VMPTRLD with incorrect VMCS revision identifier";
|
||||||
case 12: return "VM-entry control invalid";
|
case 12: return "VMREAD from unsupported VMCS component";
|
||||||
case 13: return "VM-exit control fields invalid";
|
case 13: return "VMWRITE to read-only VMCS component";
|
||||||
case 15: return "address out of width";
|
case 15: return "VM entry with invalid guest-state fields";
|
||||||
case 16: return "MSR bitmap addr invalid";
|
case 16: return "VM entry with invalid executive-VMCS pointer";
|
||||||
case 17: return "VMREAD/VMWRITE from unsupported field";
|
case 17: return "VM entry with non-launched executive VMCS";
|
||||||
case 18: return "VMCS addr invalid";
|
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";
|
default: return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,6 +88,17 @@ static u8 g_guest_code[4096] __attribute__((aligned(4096))) = {
|
|||||||
/* guest 栈页 */
|
/* guest 栈页 */
|
||||||
static u8 g_guest_stack[4096] __attribute__((aligned(4096)));
|
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) */
|
/* MSR bitmap(4KB 全 0 = 不拦截 MSR) */
|
||||||
static u8 g_msr_bitmap[4096] __attribute__((aligned(4096)));
|
static u8 g_msr_bitmap[4096] __attribute__((aligned(4096)));
|
||||||
|
|
||||||
@@ -162,14 +183,6 @@ static inline u64 read_cr3_local(void) {
|
|||||||
|
|
||||||
/* ===== VMCS 配置 ===== */
|
/* ===== 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) {
|
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_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG);
|
||||||
vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local());
|
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) {
|
static void vmcs_setup_guest_state_phase1(void) {
|
||||||
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表。
|
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE。
|
||||||
* P8.4: CR4 must include VMXE (CR4_FIXED0 requires it under KVM). */
|
* 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_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);
|
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
|
||||||
|
|
||||||
/* 段选择子:flat 模型。
|
/* 段选择子:flat 模型。
|
||||||
@@ -324,7 +338,19 @@ static void vmcs_setup_guest_state_phase1(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void vmcs_setup_controls(u64 eptp) {
|
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);
|
IA32_VMX_TRUE_PINBASED_CTLS);
|
||||||
|
|
||||||
u64 cpu = CPU_BASED_HLT_EXITING
|
u64 cpu = CPU_BASED_HLT_EXITING
|
||||||
@@ -334,11 +360,12 @@ static void vmcs_setup_controls(u64 eptp) {
|
|||||||
| CPU_BASED_INVLPG_EXITING;
|
| CPU_BASED_INVLPG_EXITING;
|
||||||
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
||||||
|
|
||||||
/* P8.4d: Try without EPT first to isolate nested VMX issue under KVM.
|
/* P8.5: 请求 EPT(KVM 报告的 allowed-1 集合含 EPT 位)。
|
||||||
* If KVM requires EPT, vmx_adjust_control will OR it back in. */
|
* 自检 guest 用 GPA 0x1000..0x9000 + 自有页表,必须开 EPT
|
||||||
u64 cpu2 = 0; /* no EPT, no VPID */
|
* 才能让 ept_map_range 的映射真正生效。
|
||||||
|
* EPT 不可用时 cpu2 会失去 EPT 位并打日志,自检将失败。 */
|
||||||
|
u64 cpu2 = SEC_EXEC_ENABLE_EPT;
|
||||||
cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2);
|
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
|
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||||
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
||||||
@@ -358,8 +385,8 @@ static void vmcs_setup_controls(u64 eptp) {
|
|||||||
|
|
||||||
/* MSR bitmap 物理地址 */
|
/* MSR bitmap 物理地址 */
|
||||||
{
|
{
|
||||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_msr_bitmap);
|
||||||
u64 msr_bitmap_phys = (u64)g_msr_bitmap - hhdm;
|
log_hex64("[VMM] MSR bitmap phys=", msr_bitmap_phys);
|
||||||
vmx_vmcs_write(VMCS_MSR_BITMAP, 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) {
|
if (cpu2 & SEC_EXEC_ENABLE_VPID) {
|
||||||
vmx_vmcs_write(VMCS_VPID, 1);
|
vmx_vmcs_write(VMCS_VPID, 1);
|
||||||
|
} else {
|
||||||
|
vmx_vmcs_write(VMCS_VPID, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */
|
/* 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; }
|
int vmm_is_ready(void) { return g_vmm_ready; }
|
||||||
u64 vmm_get_eptp(void) { return ept_get_eptp(); }
|
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.
|
/* P8.4: Dump all guest-state VMCS fields for KVM nested VMX debugging.
|
||||||
* Called before VMLAUNCH to identify field encoding/value errors. */
|
* Called before VMLAUNCH to identify field encoding/value errors. */
|
||||||
@@ -532,12 +564,12 @@ int vmm_self_test(void) {
|
|||||||
|
|
||||||
/* EPT 映射 guest 代码页(GPA 0x1000)、栈页(GPA 0x8000)、
|
/* EPT 映射 guest 代码页(GPA 0x1000)、栈页(GPA 0x8000)、
|
||||||
* GDT 页(GPA 0x3000)、TSS 页(GPA 0x4000)。
|
* GDT 页(GPA 0x3000)、TSS 页(GPA 0x4000)。
|
||||||
* g_guest_* 是 UTSM 虚拟地址,物理地址 = virt - HHDM */
|
* g_guest_* 属于高半 UTSM 内核映像,必须使用 Limine 提供的
|
||||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
* kernel virtual/physical base 转换;它们不是 HHDM 指针。 */
|
||||||
u64 guest_code_phys = (u64)g_guest_code - hhdm;
|
u64 guest_code_phys = vmx_kernel_virt_to_phys(g_guest_code);
|
||||||
u64 guest_stack_phys = (u64)g_guest_stack - hhdm;
|
u64 guest_stack_phys = vmx_kernel_virt_to_phys(g_guest_stack);
|
||||||
u64 guest_gdt_phys = (u64)g_guest_gdt - hhdm;
|
u64 guest_gdt_phys = vmx_kernel_virt_to_phys(g_guest_gdt);
|
||||||
u64 guest_tss_phys = (u64)g_guest_tss - hhdm;
|
u64 guest_tss_phys = vmx_kernel_virt_to_phys(g_guest_tss);
|
||||||
|
|
||||||
if (ept_map_range(0x1000, guest_code_phys, 4096, EPT_RWX) != 0) {
|
if (ept_map_range(0x1000, guest_code_phys, 4096, EPT_RWX) != 0) {
|
||||||
log_error("[VMM] map guest code failed");
|
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) */
|
/* 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;
|
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) {
|
if (ept_map_range(0x5000, guest_idt_phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||||
log_error("[VMM] map guest IDT failed");
|
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 */
|
||||||
vmcs_setup_guest_state_phase1();
|
vmcs_setup_guest_state_phase1();
|
||||||
vmcs_setup_controls(ept_get_eptp());
|
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] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS));
|
||||||
log_hex64("[VMM] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_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] 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));
|
log_hex64("[VMM] link=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER));
|
||||||
|
|
||||||
/* P8.4: Dump all guest-state fields for KVM debug */
|
/* P8.4: Dump all guest-state fields for KVM debug */
|
||||||
vmm_dump_guest_state();
|
vmm_dump_guest_state();
|
||||||
|
|
||||||
/* vmlaunch 控制流:
|
/* vmlaunch 控制流:
|
||||||
* - 失败:执行下一条指令,failed=1
|
* - 失败:执行下一条指令,failed=1(同时捕获 RFLAGS 区分
|
||||||
|
* VMfailValid CF=1 / VMfailInvalid ZF=1)
|
||||||
* - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。
|
* - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。
|
||||||
* - guest 终止时,handler 恢复 saved_rsp 并 jmp 到 post_guest 标签。
|
* - guest 终止时,handler 恢复 saved_rsp 并 jmp 到 post_guest 标签。
|
||||||
*
|
*
|
||||||
* 我们在 vmlaunch 前保存 RSP 与"vmlaunch 后"的 RIP(= post_guest 标签地址)。 */
|
* 我们在 vmlaunch 前保存 RSP 与"vmlaunch 后"的 RIP(= post_guest 标签地址)。 */
|
||||||
int failed;
|
int failed;
|
||||||
|
u64 vm_flags = 0;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */
|
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */
|
||||||
"leaq 1f(%%rip), %%rax\n\t" /* 取 post_guest 标签地址 */
|
"leaq 1f(%%rip), %%rax\n\t" /* 取 post_guest 标签地址 */
|
||||||
"movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */
|
"movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */
|
||||||
"vmlaunch\n\t" /* 启动 guest */
|
"vmlaunch\n\t" /* 启动 guest */
|
||||||
/* ---- 失败路径 ---- */
|
/* ---- 失败路径(mov 不改 flags,先存 failed 再抓 RFLAGS) ---- */
|
||||||
"movl $1, %0\n\t"
|
"movl $1, %0\n\t"
|
||||||
|
"pushfq\n\t"
|
||||||
|
"popq %1\n\t"
|
||||||
"jmp 2f\n\t"
|
"jmp 2f\n\t"
|
||||||
/* ---- post_guest 标签:终止路径 jmp 到这里 ---- */
|
/* ---- post_guest 标签:终止路径 jmp 到这里 ---- */
|
||||||
"1:\n\t"
|
"1:\n\t"
|
||||||
"movl $0, %0\n\t"
|
"movl $0, %0\n\t"
|
||||||
"2:\n\t"
|
"2:\n\t"
|
||||||
: "=r"(failed)
|
: "=r"(failed), "=r"(vm_flags)
|
||||||
:: "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory"
|
:: "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (failed) {
|
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);
|
log_hex64("[VMM] vmlaunch failed, error=", error);
|
||||||
serial_write("[VMM] ");
|
serial_write("[VMM] ");
|
||||||
serial_write(vmx_error_str(error));
|
serial_write(vmx_error_str(error));
|
||||||
|
|||||||
+60
-5
@@ -6,6 +6,7 @@
|
|||||||
#include "../arch/x86_64/limine.h"
|
#include "../arch/x86_64/limine.h"
|
||||||
|
|
||||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||||
|
extern volatile struct limine_kernel_address_request g_kernel_address_request;
|
||||||
|
|
||||||
/* ===== MSR / CR helpers ===== */
|
/* ===== MSR / CR helpers ===== */
|
||||||
|
|
||||||
@@ -15,6 +16,41 @@ u64 vmx_read_msr(u32 msr) {
|
|||||||
return ((u64)hi << 32) | lo;
|
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) {
|
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
|
||||||
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
||||||
u32 hi = (u32)(value >> 32);
|
u32 hi = (u32)(value >> 32);
|
||||||
@@ -176,6 +212,8 @@ static int vmx_truly_available(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* P8.5: VMX 指令成功条件是 CF=0 且 ZF=0(即 ja)。
|
||||||
|
* 旧代码只查 CF(jnc):VMfailInvalid(ZF=1, CF=0)会被误判为成功。 */
|
||||||
int vmx_enable(void) {
|
int vmx_enable(void) {
|
||||||
if (g_vmx_enabled) return 0;
|
if (g_vmx_enabled) return 0;
|
||||||
|
|
||||||
@@ -221,7 +259,7 @@ int vmx_enable(void) {
|
|||||||
int err;
|
int err;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"vmxon %1\n"
|
"vmxon %1\n"
|
||||||
"jnc 1f\n"
|
"ja 1f\n"
|
||||||
"jmp 2f\n"
|
"jmp 2f\n"
|
||||||
"1:\n"
|
"1:\n"
|
||||||
"mov $0, %0\n"
|
"mov $0, %0\n"
|
||||||
@@ -276,7 +314,7 @@ int vmx_vmcs_load(u64 phys) {
|
|||||||
int err;
|
int err;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"vmptrld %1\n"
|
"vmptrld %1\n"
|
||||||
"jnc 1f\n"
|
"ja 1f\n"
|
||||||
"jmp 2f\n"
|
"jmp 2f\n"
|
||||||
"1:\n"
|
"1:\n"
|
||||||
"mov $0, %0\n"
|
"mov $0, %0\n"
|
||||||
@@ -300,7 +338,7 @@ int vmx_vmcs_clear(u64 phys) {
|
|||||||
int err;
|
int err;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"vmclear %1\n"
|
"vmclear %1\n"
|
||||||
"jnc 1f\n"
|
"ja 1f\n"
|
||||||
"jmp 2f\n"
|
"jmp 2f\n"
|
||||||
"1:\n"
|
"1:\n"
|
||||||
"mov $0, %0\n"
|
"mov $0, %0\n"
|
||||||
@@ -330,6 +368,23 @@ u64 vmx_vmcs_read(u64 field) {
|
|||||||
return value;
|
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) {
|
void vmx_vmcs_write(u64 field, u64 value) {
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"vmwrite %1, %0\n"
|
"vmwrite %1, %0\n"
|
||||||
@@ -342,7 +397,7 @@ int vmx_vmlaunch(void) {
|
|||||||
int err;
|
int err;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"vmlaunch\n"
|
"vmlaunch\n"
|
||||||
"jnc 1f\n"
|
"ja 1f\n"
|
||||||
"jmp 2f\n"
|
"jmp 2f\n"
|
||||||
"1:\n"
|
"1:\n"
|
||||||
"mov $0, %0\n"
|
"mov $0, %0\n"
|
||||||
@@ -360,7 +415,7 @@ int vmx_vmresume(void) {
|
|||||||
int err;
|
int err;
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
"vmresume\n"
|
"vmresume\n"
|
||||||
"jnc 1f\n"
|
"ja 1f\n"
|
||||||
"jmp 2f\n"
|
"jmp 2f\n"
|
||||||
"1:\n"
|
"1:\n"
|
||||||
"mov $0, %0\n"
|
"mov $0, %0\n"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ static inline u64 read_cr4_local(void) {
|
|||||||
return v;
|
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 allowed0 = (u32)vmx_read_msr(msr);
|
||||||
u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32);
|
u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32);
|
||||||
u32 adjusted = (u32)value | allowed0;
|
u32 adjusted = (u32)value | allowed0;
|
||||||
@@ -186,14 +186,14 @@ static void xj380_vmcs_setup_controls(u64 eptp) {
|
|||||||
if (vmx_preemption_timer_supported()) {
|
if (vmx_preemption_timer_supported()) {
|
||||||
pin_want |= PIN_VMX_PREEMPTION_TIMER;
|
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
|
u64 cpu = CPU_BASED_HLT_EXITING
|
||||||
| CPU_BASED_ACTIVATE_SECONDARY
|
| CPU_BASED_ACTIVATE_SECONDARY
|
||||||
| CPU_BASED_USE_MSR_BITMAPS
|
| CPU_BASED_USE_MSR_BITMAPS
|
||||||
| CPU_BASED_UNCOND_IO_EXITING
|
| CPU_BASED_UNCOND_IO_EXITING
|
||||||
| CPU_BASED_INVLPG_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;
|
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
|
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||||
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
||||||
| VM_EXIT_LOAD_HOST_EFER;
|
| 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
|
u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS
|
||||||
| VM_ENTRY_IA32E_MODE_GUEST
|
| VM_ENTRY_IA32E_MODE_GUEST
|
||||||
| VM_ENTRY_LOAD_GUEST_EFER;
|
| 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_PIN_BASED_VM_EXEC_CONTROL, pin);
|
||||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu);
|
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu);
|
||||||
@@ -270,7 +270,7 @@ int xj380_launch(void) {
|
|||||||
xj380_vmcs_setup_host_state();
|
xj380_vmcs_setup_host_state();
|
||||||
|
|
||||||
/* 初始 arm preemption timer(1ms 后首次周期 exit) */
|
/* 初始 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 虚拟地址) */
|
/* KernelMain 3 个 SysV 参数(HHDM 虚拟地址) */
|
||||||
g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET;
|
g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET;
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -1,80 +1,386 @@
|
|||||||
#!/bin/bash
|
#!/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
|
# The default path creates the existing lightweight persistence volume.
|
||||||
# virtio-blk device (/dev/vdc, slot6 @ GPA 0xF4006000 IRQ11) by the UTSM
|
# KDE_FULL=1 additionally pacstraps a self-contained Arch/KDE tree into the
|
||||||
# extra_rootfs backend. The Arch /sbin/init mounts it at /mnt/persist and
|
# volume. The running guest keeps its current read-only root and can enter the
|
||||||
# bind-mounts its subtrees over /opt, /home and /root, giving VSCode and
|
# KDE tree with /mnt/persist/usr/local/bin/deshab-kde-session. This keeps the
|
||||||
# user data a persistent home across the read-only rootfs.
|
# native desktop and the guest console available as emergency fallbacks.
|
||||||
#
|
#
|
||||||
# Output:
|
# Output:
|
||||||
# SYSTEM/boot/linux-extra-rootfs.img (ext4 image, default 1024MB)
|
# SYSTEM/boot/linux-extra-rootfs.img
|
||||||
#
|
|
||||||
# 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)
|
|
||||||
#
|
#
|
||||||
# Usage:
|
# 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 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)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot"
|
OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot"
|
||||||
OUTPUT_IMG="$OUTPUT_DIR/linux-extra-rootfs.img"
|
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}"
|
WORK_DIR="${EXTRA_ROOTFS_WORK:-/root/extra_rootfs_work}"
|
||||||
MNT_DIR="$WORK_DIR/mnt"
|
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] Building persistent rw volume"
|
||||||
echo "[extra-rootfs] Work dir: $WORK_DIR (native FS)"
|
echo "[extra-rootfs] Work dir: $WORK_DIR (native FS)"
|
||||||
echo "[extra-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)"
|
echo "[extra-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)"
|
||||||
|
echo "[extra-rootfs] KDE full: $KDE_FULL"
|
||||||
mkdir -p "$WORK_DIR" "$MNT_DIR" "$OUTPUT_DIR"
|
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "[extra-rootfs] ERROR: must run as root (loop mount required)" >&2
|
echo "[extra-rootfs] ERROR: must run as root (loop mount required)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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..."
|
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..."
|
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..."
|
echo "[extra-rootfs] Populating directory skeleton..."
|
||||||
LOOP_DEV="$(losetup --find --show "$WORK_DIR/extra-rootfs.img")"
|
LOOP_DEV="$(losetup --find --show "$NATIVE_IMG")"
|
||||||
trap 'losetup -d "$LOOP_DEV" 2>/dev/null || true' EXIT
|
|
||||||
|
|
||||||
mount -t ext4 "$LOOP_DEV" "$MNT_DIR"
|
mount -t ext4 "$LOOP_DEV" "$MNT_DIR"
|
||||||
|
MOUNTED=1
|
||||||
mkdir -p "$MNT_DIR/opt" "$MNT_DIR/home" "$MNT_DIR/root" "$MNT_DIR/vscode"
|
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"
|
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
|
sync
|
||||||
umount "$MNT_DIR"
|
umount "$MNT_DIR"
|
||||||
|
MOUNTED=0
|
||||||
losetup -d "$LOOP_DEV"
|
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 ..."
|
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] Done: $OUTPUT_IMG ($(du -h "$OUTPUT_IMG" | cut -f1) on disk)"
|
||||||
echo "[extra-rootfs] Next: uncomment the extra-rootfs module lines in"
|
if [ "$KDE_FULL" -eq 1 ]; then
|
||||||
echo "[extra-rootfs] SYSTEM/limine/limine.conf, then rebuild the ESP image."
|
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."
|
||||||
|
|||||||
@@ -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`。
|
||||||
@@ -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 */
|
||||||
@@ -11,3 +11,4 @@
|
|||||||
# 其它实例只经桌面图标启动,IDE tab 标签显示最近启动实例的 DISPLAY)。
|
# 其它实例只经桌面图标启动,IDE tab 标签显示最近启动实例的 DISPLAY)。
|
||||||
VSCODE|VSCode|/opt/vscode/bin/code|--no-sandbox --disable-gpu --user-data-dir /root/.vscode-data
|
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
|
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
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user