/* linux_loader.c — Linux 6.6 bzImage loader for UTSM VMX non-root guest. * * Responsibilities: * 1. Find the bzImage among Limine boot modules * 2. Parse the bzImage setup_header (Linux boot protocol) * 3. Allocate host physical memory for guest regions * 4. Load the protected-mode kernel into guest memory * 5. Set up boot_params (e820, cmdline, initrd pointers) * 6. Build guest page tables (identity-mapped 4GB, 2MB pages) * 7. Build guest GDT (64-bit code/data/TSS) * 8. EPT-map all guest regions (GPA → HPA) * * The actual VMCS configuration and vmlaunch happen in linux_boot.c. */ #include #include #include #include #include #include #include "../arch/x86_64/limine.h" extern volatile struct limine_module_request g_module_request; extern volatile struct limine_hhdm_request g_hhdm_request; extern volatile struct limine_memmap_request g_memmap_request; /* ===== Runtime helpers ===== */ static void mem_copy(void *dst, const void *src, u64 n) { u8 *d = (u8 *)dst; const u8 *s = (const u8 *)src; for (u64 i = 0; i < n; i++) d[i] = s[i]; } static void mem_zero(void *dst, u64 n) { u8 *d = (u8 *)dst; for (u64 i = 0; i < n; i++) d[i] = 0; } static u64 str_len(const char *s) { u64 n = 0; while (s[n]) n++; return n; } /* ===== Guest state ===== */ static struct linux_guest_info g_guest; static const char *g_default_cmdline = LINUX_DEFAULT_CMDLINE; /* VSCode Phase 3: host virtual address of the graphics surface pool. * The virtio-gpu 2D backend composites the scanout into this buffer; the * desktop reads it to blit guest graphics onto the IDE host area. 0 = unused. */ static void *g_surface_vaddr = 0; /* ===== Guest page table helpers ===== * * Build identity-mapped 4-level page tables for the guest. * Maps the first 4GB of GPA space using 2MB large pages. * * Layout (all at known GPAs for guest CR3): * PML4 at g_guest.pgt_gpa + 0x0000 (1 page) * PDPT at g_guest.pgt_gpa + 0x1000 (1 page) * PD[0] at g_guest.pgt_gpa + 0x2000 (1 page, covers 0-1GB) * PD[1] at g_guest.pgt_gpa + 0x3000 (1 page, covers 1-2GB) * PD[2] at g_guest.pgt_gpa + 0x4000 (1 page, covers 2-3GB) * PD[3] at g_guest.pgt_gpa + 0x5000 (1 page, covers 3-4GB) */ #define PGT_PML4_OFF 0x0000 #define PGT_PDPT_OFF 0x1000 #define PGT_PD_BASE 0x2000 #define PGT_PD_COUNT 4 /* 4 PD pages = 4GB */ #define PGT_TOTAL_PAGES (2 + PGT_PD_COUNT) #define PGT_TOTAL_SIZE (PGT_TOTAL_PAGES * 4096) /* x86-64 page table entry bits */ #define PTE_P (1ULL << 0) /* Present */ #define PTE_RW (1ULL << 1) /* Read/Write */ #define PTE_US (1ULL << 2) /* User/Supervisor */ #define PTE_PS (1ULL << 7) /* Page Size (large page) */ #define PTE_G (1ULL << 8) /* Global */ #define PTE_NX (1ULL << 63) /* No Execute */ static int build_guest_page_tables(u64 pgt_hpa) { u64 *pml4 = (u64 *)(pgt_hpa + (g_hhdm_request.response ? g_hhdm_request.response->offset : 0)); u64 *pdpt = (u64 *)((u8 *)pml4 + 4096); /* PML4[0] → PDPT */ u64 pdpt_gpa = g_guest.pgt_gpa + PGT_PDPT_OFF; pml4[0] = pdpt_gpa | PTE_P | PTE_RW; /* PDPT[0..3] → PD pages (each covers 1GB) */ for (int i = 0; i < PGT_PD_COUNT; i++) { u64 pd_gpa = g_guest.pgt_gpa + PGT_PD_BASE + (u64)i * 4096; pdpt[i] = pd_gpa | PTE_P | PTE_RW; } /* Each PD has 512 entries, each mapping a 2MB page (PS=1). * PD[i] entry j maps GPA = i*1GB + j*2MB. */ for (int pd_idx = 0; pd_idx < PGT_PD_COUNT; pd_idx++) { u64 *pd = (u64 *)((u8 *)pml4 + PGT_PD_BASE + (u64)pd_idx * 4096); for (int j = 0; j < 512; j++) { u64 page_gpa = (u64)pd_idx * (1ULL << 30) + (u64)j * (2ULL << 20); pd[j] = page_gpa | PTE_P | PTE_RW | PTE_PS; } } return 0; } /* ===== Guest GDT builder ===== * * GDT layout (at g_guest.gdt_gpa): * [0x00] null * [0x08] 64-bit code: base=0, limit=0xFFFFF, L=1, G=1, present, DPL0, type=0x9B * [0x10] 64-bit data: base=0, limit=0xFFFFF, G=1, present, DPL0, type=0x93 * [0x18] 64-bit TSS descriptor (16 bytes) * * TSS itself is placed in the same 4KB page after the GDT. */ typedef struct __attribute__((packed)) { u16 limit_low; u16 base_low; u8 base_mid; u8 access; u8 flags_limit_high; u8 base_high; } gdt_entry64; typedef struct __attribute__((packed)) { u32 limit_low_base_low; u8 base_mid; u8 access; u8 flags_limit_high; u8 base_high; u32 base_upper; u32 reserved; } tss_desc64; static int build_guest_gdt(u64 gdt_hpa) { u8 *gdt_page = (u8 *)(gdt_hpa + (g_hhdm_request.response ? g_hhdm_request.response->offset : 0)); mem_zero(gdt_page, 4096); gdt_entry64 *gdt = (gdt_entry64 *)gdt_page; /* [0x08] 64-bit code segment */ gdt[1].limit_low = 0xFFFF; gdt[1].base_low = 0; gdt[1].base_mid = 0; gdt[1].access = 0x9B; /* present, DPL0, code, exec/read */ gdt[1].flags_limit_high = 0xAF; /* G=1, L=1 (64-bit), limit[19:16]=0xF */ gdt[1].base_high = 0; /* [0x10] 64-bit data segment */ gdt[2].limit_low = 0xFFFF; gdt[2].base_low = 0; gdt[2].base_mid = 0; gdt[2].access = 0x93; /* present, DPL0, data, read/write */ gdt[2].flags_limit_high = 0xCF; /* G=1, DB=1, limit[19:16]=0xF */ gdt[2].base_high = 0; /* [0x18] 64-bit TSS descriptor (16 bytes). * TSS structure lives at offset 0x100 in the same page (GPA = gdt_gpa + 0x100). */ tss_desc64 *tss_d = (tss_desc64 *)&gdt_page[0x18]; u64 tss_base = g_guest.gdt_gpa + 0x100; u32 tss_limit = 0x67; /* 104-byte minimum 64-bit TSS */ tss_d->limit_low_base_low = (tss_limit & 0xFFFF) | ((u32)(tss_base & 0xFFFFFF) << 16); tss_d->base_mid = (u8)((tss_base >> 24) & 0xFF); tss_d->access = 0x89; /* present, DPL0, 64-bit TSS (available) */ tss_d->flags_limit_high = (u8)((tss_limit >> 16) & 0x0F); tss_d->base_high = 0; tss_d->base_upper = 0; tss_d->reserved = 0; return 0; } /* ===== e820 setup ===== */ struct __attribute__((packed)) e820_entry { u64 addr; u64 size; u32 type; }; static int setup_e820(void *bootparams_virt) { u8 *bp = (u8 *)bootparams_virt; /* e820_entries count at offset 0x1e8 */ u8 *e820_count = &bp[E820_ENTRIES_OFFSET]; struct e820_entry *table = (struct e820_entry *)&bp[E820_TABLE_OFFSET]; int n = 0; /* Entry 0: 0x00000000-0x0009FFFF: usable RAM (640KB) */ table[n].addr = 0x00000000; table[n].size = 0x000A0000; table[n].type = E820_TYPE_RAM; n++; /* Entry 1: 0x000A0000-0x000FFFFF: reserved (VGA + BIOS) */ table[n].addr = 0x000A0000; table[n].size = 0x00060000; table[n].type = E820_TYPE_RESERVED; n++; /* Entry 2: 0x00100000-0x003FFFFF: reserved (UTSM area) */ table[n].addr = 0x00100000; table[n].size = 0x00300000; table[n].type = E820_TYPE_RESERVED; n++; /* Entry 3: 0x00400000-0x03FFFFFF: usable RAM (kernel + initrd + low RAM) */ table[n].addr = 0x00400000; table[n].size = 0x03C00000; /* 60MB */ table[n].type = E820_TYPE_RAM; n++; /* Entry 4: 0x04000000-0x040FFFFF: reserved (IPC shared memory, 1MB) */ table[n].addr = 0x04000000; table[n].size = 0x00100000; /* 1MB */ table[n].type = E820_TYPE_RESERVED; n++; /* Entry 5: 0x04100000-0x04FFFFFF: reserved (Graphics surface pool, 16MB) * VSCode integration Phase 1: separate DMA region EPT-mapped into the * guest; Linux user-space mmaps it via /dev/utsm as the scanout buffer. */ table[n].addr = LINUX_GUEST_SURFACE_GPA; table[n].size = LINUX_GUEST_SURFACE_SIZE; /* 16MB */ table[n].type = E820_TYPE_RESERVED; n++; /* Entry 6: 0x05000000-0x5FFFFFFF: usable RAM (general RAM, ~1.4GB) * VSCode integration Phase 1: expanded from 191MB to 1.4GB to fit * VSCode + Electron + GUI stack (Xorg/modesetting + GTK + mesa). */ table[n].addr = LINUX_GUEST_RAM_GPA; table[n].size = LINUX_GUEST_RAM_SIZE; /* 1.4GB */ table[n].type = E820_TYPE_RAM; n++; *e820_count = (u8)n; return n; } /* ===== bzImage module finder ===== */ void *linux_find_bzimage_module(u64 *size_out) { if (!g_module_request.response) { log_error("[LINUX] no Limine module response"); return (void *)0; } struct limine_module_response *resp = g_module_request.response; for (u64 i = 0; i < resp->module_count; i++) { struct limine_file *file = resp->modules[i]; if (!file || !file->path) continue; /* Look for paths containing "linux" or "bzImage" */ const char *p = file->path; int match = 0; /* Simple case-insensitive substring search for "linux" */ for (const char *s = p; *s; s++) { if ((s[0] == 'l' || s[0] == 'L') && (s[1] == 'i' || s[1] == 'I') && (s[2] == 'n' || s[2] == 'N') && (s[3] == 'u' || s[3] == 'U') && (s[4] == 'x' || s[4] == 'X')) { match = 1; break; } } if (!match) continue; if (size_out) *size_out = file->size; log_info("[LINUX] found bzImage module:"); log_info(file->path); log_hex64("[LINUX] module size=", file->size); return file->address; } log_warn("[LINUX] no bzImage module found"); return (void *)0; } /* ===== initrd module finder ===== * * 在 Limine boot module 中查找 initramfs(路径含 "initrd" 或 "initramfs")。 * 返回模块数据指针,*size_out 为大小;未找到返回 NULL。 */ static void *linux_find_initrd_module(u64 *size_out) { if (!g_module_request.response) return (void *)0; struct limine_module_response *resp = g_module_request.response; for (u64 i = 0; i < resp->module_count; i++) { struct limine_file *file = resp->modules[i]; if (!file || !file->path) continue; const char *p = file->path; int match = 0; for (const char *s = p; *s; s++) { /* 匹配 "initrd"(如 linux-initrd.img) */ if ((s[0] == 'i' || s[0] == 'I') && (s[1] == 'n' || s[1] == 'N') && (s[2] == 'i' || s[2] == 'I') && (s[3] == 't' || s[3] == 'T') && (s[4] == 'r' || s[4] == 'R') && (s[5] == 'd' || s[5] == 'D')) { match = 1; break; } } if (!match) continue; if (size_out) *size_out = file->size; log_info("[LINUX] found initrd module:"); log_info(file->path); log_hex64("[LINUX] initrd size=", file->size); return file->address; } log_warn("[LINUX] no initrd module found"); return (void *)0; } /* ===== rootfs module finder ===== * * 在 Limine boot module 中查找 Arch rootfs 镜像(路径含 "rootfs")。 * 返回模块数据指针,*size_out 为大小;未找到返回 NULL。 * 该镜像通过 virtio-blk(memory-backed)暴露给 Linux guest 作为 /dev/vdb。 */ void *linux_find_rootfs_module(u64 *size_out) { if (!g_module_request.response) return (void *)0; struct limine_module_response *resp = g_module_request.response; for (u64 i = 0; i < resp->module_count; i++) { struct limine_file *file = resp->modules[i]; if (!file || !file->path) continue; const char *p = file->path; int match = 0; for (const char *s = p; *s; s++) { /* 匹配 "rootfs"(如 linux-rootfs.img) */ if ((s[0] == 'r' || s[0] == 'R') && (s[1] == 'o' || s[1] == 'O') && (s[2] == 'o' || s[2] == 'O') && (s[3] == 't' || s[3] == 'T') && (s[4] == 'f' || s[4] == 'F') && (s[5] == 's' || s[5] == 'S')) { match = 1; break; } } if (!match) continue; if (size_out) *size_out = file->size; log_info("[LINUX] found rootfs module:"); log_info(file->path); log_hex64("[LINUX] rootfs size=", file->size); return file->address; } log_warn("[LINUX] no rootfs module found"); return (void *)0; } /* ===== extra rootfs module finder ===== * * VSCode Phase 4:在 Limine boot module 中查找持久 rw 卷镜像 * (路径含 "extra",如 linux-extra-rootfs.img)。 * 通过 virtio-blk slot6(可写)暴露给 Linux guest 作为 /dev/vdc。 */ void *linux_find_extra_rootfs_module(u64 *size_out) { if (!g_module_request.response) return (void *)0; struct limine_module_response *resp = g_module_request.response; for (u64 i = 0; i < resp->module_count; i++) { struct limine_file *file = resp->modules[i]; if (!file || !file->path) continue; const char *p = file->path; int match = 0; for (const char *s = p; *s; s++) { /* 匹配 "extra"(如 linux-extra-rootfs.img) */ if ((s[0] == 'e' || s[0] == 'E') && (s[1] == 'x' || s[1] == 'X') && (s[2] == 't' || s[2] == 'T') && (s[3] == 'r' || s[3] == 'R') && (s[4] == 'a' || s[4] == 'A')) { match = 1; break; } } if (!match) continue; if (size_out) *size_out = file->size; log_info("[LINUX] found extra-rootfs module:"); log_info(file->path); log_hex64("[LINUX] extra-rootfs size=", file->size); return file->address; } log_warn("[LINUX] no extra-rootfs module found"); return (void *)0; } /* ===== VSCode tarball module finder ===== * * VSCode Phase 5:在 Limine boot module 中查找 VSCode tarball * (路径含 "vscode",如 linux-vscode.tar.gz)。 * 由 lxc_vscode_install() 流式推入 guest 并解压到 /opt/vscode。 * 模块缺失属正常情况(未投放 tarball),返回 NULL 由调用方跳过。 */ void *linux_find_vscode_module(u64 *size_out) { if (!g_module_request.response) return (void *)0; struct limine_module_response *resp = g_module_request.response; for (u64 i = 0; i < resp->module_count; i++) { struct limine_file *file = resp->modules[i]; if (!file || !file->path) continue; const char *p = file->path; int match = 0; for (const char *s = p; *s; s++) { /* 匹配 "vscode"(如 linux-vscode.tar.gz) */ if ((s[0] == 'v' || s[0] == 'V') && (s[1] == 's' || s[1] == 'S') && (s[2] == 'c' || s[2] == 'C') && (s[3] == 'o' || s[3] == 'O') && (s[4] == 'd' || s[4] == 'D') && (s[5] == 'e' || s[5] == 'E')) { match = 1; break; } } if (!match) continue; if (size_out) *size_out = file->size; log_info("[LINUX] found vscode tarball module:"); log_info(file->path); log_hex64("[LINUX] vscode tarball size=", file->size); return file->address; } log_info("[LINUX] no vscode tarball module (install skipped)"); return (void *)0; } /* ===== bzImage parser ===== */ int linux_parse_bzimage(const void *bzimage, u64 size, u64 *payload_offset_out, u64 *payload_length_out, u64 *init_size_out, u64 *entry_offset_out) { const u8 *img = (const u8 *)bzimage; if (size < 0x300) { log_error("[LINUX] bzImage too small"); return -1; } /* Check boot_flag at 0x1fe */ u16 boot_flag = *(u16 *)&img[BZ_BOOT_FLAG_OFF]; if (boot_flag != 0xAA55) { log_hex64("[LINUX] bad boot_flag=", boot_flag); return -2; } /* Check header magic "HdrS" at 0x202 */ u32 magic = *(u32 *)&img[BZ_HEADER_OFF]; if (magic != BZ_HEADER_MAGIC) { log_hex64("[LINUX] bad header magic=", magic); return -3; } /* Check protocol version */ u16 version = *(u16 *)&img[BZ_VERSION_OFF]; log_hex64("[LINUX] boot protocol version=", version); if (version < 0x020c) { log_error("[LINUX] boot protocol too old"); return -4; } /* Check xloadflags for 64-bit kernel */ u16 xloadflags = *(u16 *)&img[BZ_XLOADFLAGS_OFF]; log_hex64("[LINUX] xloadflags=", xloadflags); if (!(xloadflags & XLF_KERNEL_64)) { log_error("[LINUX] not a 64-bit kernel"); return -5; } /* Payload offset and length */ u32 payload_offset = *(u32 *)&img[BZ_PAYLOAD_OFFSET_OFF]; u32 payload_length = *(u32 *)&img[BZ_PAYLOAD_LENGTH_OFF]; log_hex64("[LINUX] payload_offset=", payload_offset); log_hex64("[LINUX] payload_length=", payload_length); if (payload_offset + payload_length > size) { log_error("[LINUX] payload exceeds image size"); return -6; } /* Init size (total memory needed for kernel init) */ u32 init_size = *(u32 *)&img[BZ_INIT_SIZE_ACTUAL_OFF]; if (init_size == 0) init_size = payload_length; log_hex64("[LINUX] init_size=", init_size); /* The 64-bit entry point is at offset 0x200 from the payload start. * This is the "startup_64" symbol. */ u64 entry_offset = payload_offset + 0x200; log_hex64("[LINUX] entry_offset=", entry_offset); /* Setup sectors (for real-mode header size) */ u8 setup_sects = img[0x1f1]; if (setup_sects == 0) setup_sects = 4; u64 setup_size = (setup_sects + 1) * 512; log_hex64("[LINUX] setup_sects=", setup_sects); log_hex64("[LINUX] setup_size=", setup_size); if (payload_offset_out) *payload_offset_out = payload_offset; if (payload_length_out) *payload_length_out = payload_length; if (init_size_out) *init_size_out = init_size; if (entry_offset_out) *entry_offset_out = entry_offset; return 0; } /* ===== Boot params setup ===== */ static int setup_boot_params(void *bp_virt, u64 bp_gpa, u64 kernel_entry_gpa, u64 cmdline_gpa, u64 cmdline_len, u64 initrd_gpa, u64 initrd_size, const void *bzimage) { (void)bp_gpa; /* GPA is used by EPT mapping, not needed here */ const u8 *img = (const u8 *)bzimage; u8 *bp = (u8 *)bp_virt; /* Zero entire boot_params */ mem_zero(bp, BOOT_PARAMS_SIZE); /* Copy setup_header from bzImage (offset 0x1f1, length 0x1ef = 0x200 - 0x11) */ /* The setup_header in boot_params starts at 0x1f1 and goes to 0x2xx. * We copy from bzImage offset 0x1f1 to boot_params offset 0x1f1. */ u64 hdr_copy_len = 0x210; /* copy a generous chunk */ if (hdr_copy_len > BOOT_PARAMS_SIZE - SETUP_HEADER_OFFSET) { hdr_copy_len = BOOT_PARAMS_SIZE - SETUP_HEADER_OFFSET; } mem_copy(&bp[SETUP_HEADER_OFFSET], &img[SETUP_HEADER_OFFSET], hdr_copy_len); /* Override key fields */ bp[BZ_TYPE_OF_LOADER_OFF] = 0xFF; /* custom bootloader */ /* loadflags: LOADED_HIGH | CAN_USE_HEAP */ u8 loadflags = bp[BZ_LOADFLAGS_OFF]; loadflags |= LOADFLAG_LOADED_HIGH | LOADFLAG_CAN_USE_HEAP; bp[BZ_LOADFLAGS_OFF] = loadflags; /* Command line pointer and size */ *(u32 *)&bp[BZ_CMD_LINE_PTR_OFF] = (u32)cmdline_gpa; *(u32 *)&bp[BZ_CMDLINE_SIZE_OFF] = (u32)cmdline_len; /* Initrd */ if (initrd_size > 0) { *(u32 *)&bp[BZ_RAMDISK_IMAGE_OFF] = (u32)initrd_gpa; *(u32 *)&bp[BZ_RAMDISK_SIZE_OFF] = (u32)initrd_size; } /* code32_start: set to kernel entry (informational) */ *(u32 *)&bp[BZ_CODE32_START_OFF] = (u32)kernel_entry_gpa; /* Heap end pointer (offset 0x224): end of real-mode heap. * Not critical for 64-bit boot, but set a reasonable value. */ *(u16 *)&bp[0x224] = 0xFE00; /* heap ends at 0xFE00 */ /* Set up e820 memory map */ int e820_count = setup_e820(bp_virt); log_hex64("[LINUX] e820 entries=", (u64)e820_count); return 0; } /* ===== Main loader entry ===== */ int linux_loader_init(void) { log_info("[LINUX] loader init begin"); /* 1. Find bzImage module */ u64 bzimage_size = 0; void *bzimage = linux_find_bzimage_module(&bzimage_size); if (!bzimage) { log_warn("[LINUX] no bzImage found, skipping Linux guest"); return -1; } /* 2. Parse bzImage header */ u64 payload_offset, payload_length, init_size, entry_offset; if (linux_parse_bzimage(bzimage, bzimage_size, &payload_offset, &payload_length, &init_size, &entry_offset) != 0) { log_error("[LINUX] bzImage parse failed"); return -2; } /* 3. Allocate host physical memory for guest regions */ /* Kernel: round up init_size to page boundary, align to 2MB */ u64 kernel_alloc_size = ((init_size + 0xFFFFF) & ~0xFFFFFULL); if (kernel_alloc_size < payload_length) kernel_alloc_size = payload_length; dkm_dma_buffer kernel_buf; if (dma_alloc_pages(kernel_alloc_size / 4096, 2 * 1024 * 1024, 0, &kernel_buf) != 0) { log_error("[LINUX] failed to alloc kernel memory"); return -3; } log_hex64("[LINUX] kernel HPA=", kernel_buf.phys); log_hex64("[LINUX] kernel alloc size=", kernel_alloc_size); /* Boot params: 1 page */ dkm_dma_buffer bp_buf; if (dma_alloc_pages(1, 4096, 0, &bp_buf) != 0) { log_error("[LINUX] failed to alloc boot_params"); return -4; } /* Page tables: PGT_TOTAL_PAGES pages */ dkm_dma_buffer pgt_buf; if (dma_alloc_pages(PGT_TOTAL_PAGES, 4096, 0, &pgt_buf) != 0) { log_error("[LINUX] failed to alloc page tables"); return -5; } /* GDT: 1 page */ dkm_dma_buffer gdt_buf; if (dma_alloc_pages(1, 4096, 0, &gdt_buf) != 0) { log_error("[LINUX] failed to alloc GDT"); return -6; } /* Stack: 1 page (stack grows down from top) */ dkm_dma_buffer stack_buf; if (dma_alloc_pages(1, 4096, 0, &stack_buf) != 0) { log_error("[LINUX] failed to alloc stack"); return -7; } /* Command line: 1 page */ dkm_dma_buffer cmdline_buf; if (dma_alloc_pages(1, 4096, 0, &cmdline_buf) != 0) { log_error("[LINUX] failed to alloc cmdline"); return -8; } /* General RAM: VSCode integration Phase 1 expansion. * 1.4GB (LINUX_GUEST_RAM_SIZE) accommodates VSCode + Electron + GUI stack * (Xorg/modesetting + GTK + mesa softpipe). Falls back to 1GB / 512MB if * the host cannot allocate the full amount; 512MB is the floor for the * Xfbdev + xterm MVP (Phase 3). Original allocation was 64MB. */ u64 ram_size = LINUX_GUEST_RAM_SIZE; /* 1.4GB */ dkm_dma_buffer ram_buf; if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) { log_warn("[LINUX] failed to alloc guest RAM (1.4GB), trying 1GB"); ram_size = 1ULL * 1024 * 1024 * 1024; /* 1GB */ if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) { log_warn("[LINUX] failed to alloc guest RAM (1GB), trying 512MB"); ram_size = 512 * 1024 * 1024; /* 512MB floor for Phase 3 MVP */ if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) { log_error("[LINUX] failed to alloc guest RAM (512MB minimum)"); return -9; } } } log_hex64("[LINUX] guest RAM HPA=", ram_buf.phys); log_hex64("[LINUX] guest RAM size=", ram_size); /* 4. Load kernel image into allocated memory */ const u8 *kernel_src = (const u8 *)bzimage + payload_offset; mem_copy(kernel_buf.virt, kernel_src, payload_length); log_hex64("[LINUX] kernel loaded, size=", payload_length); /* 5. Set up guest page tables */ g_guest.pgt_gpa = LINUX_GUEST_PGT_GPA; build_guest_page_tables(pgt_buf.phys); log_hex64("[LINUX] page tables built at HPA=", pgt_buf.phys); /* 6. Set up guest GDT */ g_guest.gdt_gpa = LINUX_GUEST_GDT_GPA; build_guest_gdt(gdt_buf.phys); log_hex64("[LINUX] GDT built at HPA=", gdt_buf.phys); /* 7. Set up command line */ u64 cmdline_len = str_len(g_default_cmdline) + 1; /* include NUL */ mem_copy(cmdline_buf.virt, g_default_cmdline, cmdline_len); log_info("[LINUX] cmdline:"); log_info(g_default_cmdline); /* 8. Set up boot_params */ g_guest.bootparams_gpa = LINUX_GUEST_BOOTPARAMS_GPA; g_guest.kernel_gpa = LINUX_GUEST_KERNEL_GPA; g_guest.kernel_entry = LINUX_GUEST_KERNEL_GPA + entry_offset; g_guest.cmdline_gpa = LINUX_GUEST_CMDLINE_GPA; g_guest.cmdline_size = cmdline_len; /* 8.5. Load initrd (busybox initramfs) if present. * initrd 放在 GPA 0x03000000(initrd 区,上限 16MB 到 IPC 区 0x04000000)。 */ g_guest.initrd_gpa = 0; g_guest.initrd_size = 0; { u64 initrd_size = 0; void *initrd = linux_find_initrd_module(&initrd_size); if (initrd && initrd_size > 0) { u64 initrd_max = LINUX_GUEST_IPC_SHM_GPA - LINUX_GUEST_INITRD_GPA; /* 16MB */ if (initrd_size > initrd_max) { log_error("[LINUX] initrd too large (>16MB), skipped"); } else { u64 initrd_pages = (initrd_size + 4095) / 4096; dkm_dma_buffer initrd_buf; if (dma_alloc_pages(initrd_pages, 4096, 0, &initrd_buf) != 0) { log_error("[LINUX] failed to alloc initrd memory"); return -17; } mem_copy(initrd_buf.virt, initrd, initrd_size); if (ept_map_range(LINUX_GUEST_INITRD_GPA, initrd_buf.phys, initrd_pages * 4096, EPT_READ | EPT_WRITE) != 0) { log_error("[LINUX] EPT map initrd failed"); return -18; } g_guest.initrd_gpa = LINUX_GUEST_INITRD_GPA; g_guest.initrd_size = initrd_size; log_hex64("[LINUX] initrd mapped GPA=", g_guest.initrd_gpa); log_hex64("[LINUX] initrd size=", g_guest.initrd_size); } } else { log_warn("[LINUX] booting without initrd (no userland!)"); } } setup_boot_params(bp_buf.virt, g_guest.bootparams_gpa, g_guest.kernel_entry, g_guest.cmdline_gpa, cmdline_len, g_guest.initrd_gpa, g_guest.initrd_size, bzimage); log_hex64("[LINUX] boot_params at GPA=", g_guest.bootparams_gpa); /* 9. EPT-map all guest regions (GPA → HPA) */ /* Kernel: GPA 0x01000000 → kernel HPA, RWX */ if (ept_map_range(g_guest.kernel_gpa, kernel_buf.phys, kernel_alloc_size, EPT_RWX) != 0) { log_error("[LINUX] EPT map kernel failed"); return -10; } /* Boot params: GPA 0x00400000 → bp HPA, RW */ if (ept_map_range(g_guest.bootparams_gpa, bp_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) { log_error("[LINUX] EPT map boot_params failed"); return -11; } /* Page tables: GPA 0x00700000 → pgt HPA, RW */ if (ept_map_range(g_guest.pgt_gpa, pgt_buf.phys, PGT_TOTAL_SIZE, EPT_READ | EPT_WRITE) != 0) { log_error("[LINUX] EPT map page tables failed"); return -12; } /* GDT: GPA 0x00600000 → gdt HPA, RW */ if (ept_map_range(g_guest.gdt_gpa, gdt_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) { log_error("[LINUX] EPT map GDT failed"); return -13; } /* Stack: GPA 0x00800000 → stack HPA, RW */ if (ept_map_range(LINUX_GUEST_STACK_GPA, stack_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) { log_error("[LINUX] EPT map stack failed"); return -14; } /* Command line: GPA 0x00500000 → cmdline HPA, R */ if (ept_map_range(g_guest.cmdline_gpa, cmdline_buf.phys, 4096, EPT_READ) != 0) { log_error("[LINUX] EPT map cmdline failed"); return -15; } /* General RAM: map the allocated RAM at GPA 0x05000000 (after surface pool). * GPA layout (matches e820, VSCode integration Phase 1): * 0x00400000-0x009FFFFF: fixed regions (boot_params, GDT, pgt, stack, cmdline) * 0x01000000-0x02FFFFFF: kernel * 0x03000000-0x03FFFFFF: initrd (if any) * 0x04000000-0x040FFFFF: IPC shared memory (mapped by ipc_shm_init) * 0x04100000-0x04FFFFFF: Graphics surface pool (mapped below) * 0x05000000-0x5FFFFFFF: general RAM (~1.4GB, expanded Phase 1) * Linux sees this as usable RAM in e820. */ u64 ram_gpa = LINUX_GUEST_RAM_GPA; if (ept_map_range(ram_gpa, ram_buf.phys, ram_size, EPT_RWX) != 0) { log_error("[LINUX] EPT map guest RAM failed"); return -16; } log_hex64("[LINUX] guest RAM mapped GPA=", ram_gpa); /* VSCode integration Phase 1: allocate graphics surface pool and EPT-map * it into the Linux guest at LINUX_GUEST_SURFACE_GPA. Linux user-space * obtains a writable pointer to this region via UTSM_HCALL_SURFACE_INFO + * /dev/utsm mmap; UTSM reads it to blit guest graphics to the Deshab * framebuffer (Phase 3 virtio-gpu shadow scanout). Non-fatal on failure: * Linux guest still runs, VSCode integration phases 2+ will degrade. */ { dkm_dma_buffer surface_buf; u64 surface_size = LINUX_GUEST_SURFACE_SIZE; /* 16MB */ if (dma_alloc_pages(surface_size / 4096, 2 * 1024 * 1024, 0, &surface_buf) != 0) { log_warn("[LINUX] failed to alloc surface pool (16MB) — VSCode integration degraded"); g_guest.surface_hpa = 0; g_guest.surface_gpa = 0; g_guest.surface_size = 0; } else if (ept_map_range(LINUX_GUEST_SURFACE_GPA, surface_buf.phys, surface_size, EPT_READ | EPT_WRITE) != 0) { log_warn("[LINUX] EPT map surface pool failed — VSCode integration degraded"); g_guest.surface_hpa = 0; g_guest.surface_gpa = 0; g_guest.surface_size = 0; } else { /* Zero the surface pool so guest reads deterministic data before * the X server writes a real scanout. */ u8 *surface_virt = (u8 *)surface_buf.virt; for (u64 i = 0; i < surface_size; i++) surface_virt[i] = 0; g_guest.surface_hpa = surface_buf.phys; g_guest.surface_gpa = LINUX_GUEST_SURFACE_GPA; g_guest.surface_size = surface_size; g_surface_vaddr = surface_buf.virt; /* VSCode Phase 3: for virtio-gpu + desktop blit */ log_hex64("[LINUX] surface pool HPA=", surface_buf.phys); log_hex64("[LINUX] surface pool GPA=", g_guest.surface_gpa); log_hex64("[LINUX] surface pool size=", surface_size); } } /* Identity-map the first 1MB (BIOS area, needed by some Linux code paths) */ if (ept_identity_map(0, 0x100000, EPT_RWX) != 0) { log_warn("[LINUX] EPT identity map first 1MB failed (non-critical)"); } /* 10. Record guest info */ g_guest.kernel_size = payload_length; g_guest.loaded = 1; log_hex64("[LINUX] kernel GPA=", g_guest.kernel_gpa); log_hex64("[LINUX] kernel entry=", g_guest.kernel_entry); log_hex64("[LINUX] kernel size=", g_guest.kernel_size); log_info("[LINUX] loader init ok"); return 0; } const struct linux_guest_info *linux_get_guest_info(void) { return g_guest.loaded ? &g_guest : (struct linux_guest_info *)0; } /* VSCode integration Phase 1: query graphics surface pool. * Returns 0 on success (surface pool allocated and mapped), -1 if not * initialized. Caller may pass NULL for any output it doesn't need. */ int linux_get_surface_info(u64 *hpa_out, u64 *gpa_out, u64 *size_out) { if (!g_guest.loaded || g_guest.surface_hpa == 0) { return -1; } if (hpa_out) *hpa_out = g_guest.surface_hpa; if (gpa_out) *gpa_out = g_guest.surface_gpa; if (size_out) *size_out = g_guest.surface_size; return 0; } /* VSCode Phase 3: return host virtual address of the surface pool. * The virtio-gpu 2D backend composites the scanout here; the desktop reads it * for blitting to the framebuffer. Returns NULL if the pool is not allocated. */ void *linux_get_surface_vaddr(void) { return g_surface_vaddr; }