diff --git a/CODE/UTSM/MAKEFILE b/CODE/UTSM/MAKEFILE index 6e88ac0..56636d8 100644 --- a/CODE/UTSM/MAKEFILE +++ b/CODE/UTSM/MAKEFILE @@ -5,12 +5,12 @@ OBJCOPY := llvm-objcopy ROOT := ../.. OUT := $(ROOT)/SYSTEM/boot/utsm.elf -CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fno-pic -fno-pie -mcmodel=kernel -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -Iinclude -O2 -ASFLAGS := -target x86_64-unknown-none -ffreestanding -fno-pic -fno-pie -mcmodel=kernel -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Iinclude +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fno-pic -fno-pie -mcmodel=kernel -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -Iinclude -I../utsm-ipc -O2 +ASFLAGS := -target x86_64-unknown-none -ffreestanding -fno-pic -fno-pie -mcmodel=kernel -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Iinclude -I../utsm-ipc LDFLAGS := -nostdlib -static -z max-page-size=0x1000 -T linker.ld -C_SOURCES := $(wildcard arch/x86_64/*.c kernel/*.c runtime/*.c mm/*.c core/*.c crypto/*.c pckc/*.c utrw/*.c drr/*.c dkm/*.c test/*.c) -S_SOURCES := $(wildcard arch/x86_64/*.S) +C_SOURCES := $(wildcard arch/x86_64/*.c kernel/*.c runtime/*.c mm/*.c core/*.c crypto/*.c pckc/*.c utrw/*.c drr/*.c dkm/*.c test/*.c vmm/*.c) +S_SOURCES := $(wildcard arch/x86_64/*.S vmm/*.S) OBJECTS := $(C_SOURCES:.c=.o) $(S_SOURCES:.S=.o) .PHONY: all clean diff --git a/CODE/UTSM/include/utsm/deshab_ui.h b/CODE/UTSM/include/utsm/deshab_ui.h index fc39341..665ba10 100644 --- a/CODE/UTSM/include/utsm/deshab_ui.h +++ b/CODE/UTSM/include/utsm/deshab_ui.h @@ -706,4 +706,266 @@ static inline du_u32 du_theme_accent(du_theme t) { return (t == DU_THEME_DARK) ? DS_DARK_ACCENT : DS_LIGHT_ACCENT; } +/* =================================================================== + * L0+ — Winux-Kate 调色板 (eDEX-UI Neon Cyber Theme) + * + * 源自 D:\Code\Winux-Kate\src\styles\theme.css + * 风格:深蓝黑底 + 青色霓虹 + 绿色辅助 + 扫描线纹理 + * =================================================================== */ + +/* --- 主色调:深空蓝黑 (Deep Space Black) --- */ +#define DP_KATE_BG 0xFF02040Au /* --bg: #02040a */ +#define DP_KATE_BG2 0xFF050A18u /* --bg2: #050a18 */ +#define DP_KATE_PANEL 0xB20A1228u /* --panel: rgba(10,18,40,0.72) 半透明 */ +#define DP_KATE_PANEL_SOLID 0xFF0A1228u /* 不透明版本(用于无 alpha 混合场景) */ + +/* --- 强调色:霓虹青 (Neon Cyan) --- */ +#define DP_KATE_ACCENT 0xFF00E5FFu /* --accent: #00e5ff */ +#define DP_KATE_ACCENT_DIM 0x2E00E5FFu /* --accent-dim: rgba(0,229,255,0.18) */ +#define DP_KATE_ACCENT_DIM_SOLID 0xFF1A3A4Au /* 不透明近似 */ +#define DP_KATE_PANEL_BORDER 0x5900E5FFu /* --panel-border: rgba(0,229,255,0.35) */ +#define DP_KATE_PANEL_BORDER_SOLID 0xFF005A6Au /* 不透明近似 */ + +/* --- 辅助色:霓虹绿 (Neon Green) --- */ +#define DP_KATE_ACCENT2 0xFF39FF14u /* --accent2: #39ff14 */ + +/* --- 文本色 --- */ +#define DP_KATE_TEXT 0xFFCFEFFBu /* --text: #cfeffb */ +#define DP_KATE_TEXT_DIM 0xFF6F8AA8u /* --text-dim: #6f8aa8 */ + +/* --- 状态色 --- */ +#define DP_KATE_WARN 0xFFFFCC00u /* --warn: #ffcc00 */ +#define DP_KATE_DANGER 0xFFFF4D6Du /* --danger: #ff4d6d */ + +/* =================================================================== + * L1 — Winux-Kate 语义色值 + * =================================================================== */ + +#define KS_BG_PRIMARY DP_KATE_BG +#define KS_BG_SECONDARY DP_KATE_PANEL_SOLID +#define KS_BG_TERTIARY DP_KATE_BG2 + +#define KS_TEXT_PRIMARY DP_KATE_TEXT +#define KS_TEXT_DIM DP_KATE_TEXT_DIM +#define KS_TEXT_INVERT DP_KATE_BG + +#define KS_ACCENT DP_KATE_ACCENT +#define KS_ACCENT2 DP_KATE_ACCENT2 +#define KS_ACCENT_DIM DP_KATE_ACCENT_DIM_SOLID + +#define KS_BORDER DP_KATE_PANEL_BORDER_SOLID +#define KS_BORDER_FOCUS DP_KATE_ACCENT +#define KS_BORDER_DIM 0xFF1A2840u /* 暗边框 */ + +#define KS_DANGER DP_KATE_DANGER +#define KS_WARN DP_KATE_WARN + +/* =================================================================== + * Winux-Kate 布局常量(源自 theme.css / pages.css) + * =================================================================== */ + +#define KATE_TOPBAR_H 34 /* .topbar height */ +#define KATE_STATUSBAR_H 32 /* .statusbar height */ +#define KATE_TASKBAR_H 40 /* .taskbar height */ +#define KATE_PANEL_HEADER_H 24 /* .panel-header */ +#define KATE_TITLEBAR_H 28 /* 窗口标题栏 */ +#define KATE_ICON_W 104 /* .desktop-icon width */ +#define KATE_ICON_H 90 /* .desktop-icon height (含 label) */ +#define KATE_ICON_IMG 40 /* .di-img / .di-fallback */ +#define KATE_ICON_GAP 18 /* .desktop-grid gap */ +#define KATE_ICON_PAD 24 /* .desktop-canvas padding */ +#define KATE_TB_ITEM_MAX_W 200 /* .tb-item max-width */ +#define KATE_PAGE_PAD 8 /* 页面 padding */ + +/* =================================================================== + * Winux-Kate 特效函数 + * =================================================================== */ + +/* 绘制扫描线纹理( repeating-linear-gradient 0deg, 3px 周期) + * 在指定矩形区域内叠加半透明青色水平细线 */ +static inline void du_kate_scanlines(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h) { + for (du_i64 ry = 0; ry < h; ry++) { + du_i64 yy = y + ry; + if (yy < 0 || (du_u64)yy >= ctx->height) continue; /* 每 3 像素一条线 */ + du_u32 *line = (du_u32 *)((du_u8 *)ctx->fb + (du_u64)yy * ctx->pitch); + for (du_i64 rx = 0; rx < w; rx++) { + du_i64 xx = x + rx; + if (xx < 0 || (du_u64)xx >= ctx->width) continue; + /* 叠加极淡青色(alpha ~0.03 ≈ 8/255)*/ + du_u32 cur = line[(du_u64)xx]; + du_u32 b = (cur >> 16) & 0xFF; + du_u32 g = (cur >> 8) & 0xFF; + du_u32 r = cur & 0xFF; + b = (b + 2 < 255) ? b + 2 : 255; + g = (g + 4 < 255) ? g + 4 : 255; + r = (r + 2 < 255) ? r + 2 : 255; + line[(du_u64)xx] = 0xFF000000u | (b << 16) | (g << 8) | r; + } + } +} + +/* 绘制带光晕的矩形边框(box-shadow 效果) + * 外发光:在矩形外围绘制渐变青色光晕 */ +static inline void du_kate_glow_border(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, du_u32 glow_color) { + /* 2px 渐变光晕,alpha 混合到实际背景(逐像素读-混合-写) */ + for (du_i64 i = 2; i >= 1; i--) { + du_u32 alpha = (i == 2) ? 50 : 100; + /* 上下边 */ + for (du_i64 c = -i; c < w + i; c++) { + du_i64 xt = x + c; + du_pixel(ctx, xt, y - i, + du_blend(du_pixel_read(ctx, xt, y - i), glow_color, alpha)); + du_pixel(ctx, xt, y + h + i - 1, + du_blend(du_pixel_read(ctx, xt, y + h + i - 1), glow_color, alpha)); + } + /* 左右边 */ + for (du_i64 r = -i; r < h + i; r++) { + du_i64 yt = y + r; + du_pixel(ctx, x - i, yt, + du_blend(du_pixel_read(ctx, x - i, yt), glow_color, alpha)); + du_pixel(ctx, x + w + i - 1, yt, + du_blend(du_pixel_read(ctx, x + w + i - 1, yt), glow_color, alpha)); + } + } +} + +/* 绘制 Winux-Kate 面板(.panel 样式) + * - 半透明深蓝背景 + * - 青色边框 + * - 内外光晕 + * - 扫描线纹理 + * - 可选标题栏 */ +static inline void du_kate_panel(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, const char *header) { + /* 1. 背景 */ + du_fill_rounded_rect(ctx, x, y, w, h, KS_BG_SECONDARY, 2); + /* 2. 边框 */ + du_rect_outline(ctx, x, y, w, h, KS_BORDER, 2); + /* 3. 外光晕 */ + du_kate_glow_border(ctx, x, y, w, h, KS_ACCENT); + /* 4. 扫描线纹理 */ + du_kate_scanlines(ctx, x + 1, y + 1, w - 2, h - 2); + /* 5. 标题栏 */ + if (header) { + du_fill_rect(ctx, x, y, w, KATE_PANEL_HEADER_H, 0xFF062838u); /* 深青色标题栏 */ + du_divider_h(ctx, x, y + KATE_PANEL_HEADER_H, w, KS_BORDER); + /* 标题文字:青色,大写,letter-spacing */ + du_draw_string(ctx, header, x + 10, y + (KATE_PANEL_HEADER_H - DU_ASCII_LINE_H) / 2 + 2, + KS_ACCENT, 0xFF062838u, DU_ASCII_STEP); + /* 标题前的小绿点 */ + du_fill_rect(ctx, x + 4, y + KATE_PANEL_HEADER_H / 2 - 3, 6, 6, KS_ACCENT2); + } +} + +/* 绘制 Winux-Kate 顶部栏(.topbar 样式) + * - 34px 高 + * - 渐变背景 + * - 底部边框 + * - 品牌名 + 时钟槽位 */ +static inline void du_kate_topbar(du_context *ctx, du_i64 w, + const char *brand, const char *clock) { + /* 渐变背景:左青色微亮 → 右深色 */ + du_fill_rect_gradient(ctx, 0, 0, w, KATE_TOPBAR_H, + 0xFF0A3050u, 0xFF02040Au); + /* 底部分隔线 */ + du_divider_h(ctx, 0, KATE_TOPBAR_H, w, KS_BORDER); + /* 品牌名(青色,加粗,letter-spacing 4px)*/ + if (brand) { + du_draw_string(ctx, brand, 12, (KATE_TOPBAR_H - DU_ASCII_LINE_H) / 2 + 1, + KS_ACCENT, 0, DU_ASCII_STEP); + } + /* 时钟(右侧,绿色)*/ + if (clock) { + du_i64 cl = 0; + while (clock[cl]) cl++; + du_draw_string(ctx, clock, w - 12 - cl * DU_ASCII_STEP, + (KATE_TOPBAR_H - DU_ASCII_LINE_H) / 2 + 1, + KS_ACCENT2, 0, DU_ASCII_STEP); + } +} + +/* 绘制 Winux-Kate 状态栏(.statusbar 样式) + * - 32px 高 + * - 顶部边框 + * - 半透明青色背景 */ +static inline void du_kate_statusbar(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, const char *status_text) { + du_fill_rect(ctx, x, y, w, KATE_STATUSBAR_H, 0xFF051828u); + du_divider_h(ctx, x, y, w, KS_BORDER); + if (status_text) { + du_draw_string(ctx, status_text, x + 12, + y + (KATE_STATUSBAR_H - DU_ASCII_LINE_H) / 2 + 1, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + } +} + +/* 绘制 Winux-Kate 桌面图标(.desktop-icon 样式) + * - 104px 宽 + * - 40x40 图标区域 + * - 下方文字标签 + * - 选中态:青色边框 + 光晕 */ +static inline void du_kate_desktop_icon(du_context *ctx, du_i64 x, du_i64 y, + const char *label, int selected) { + /* 悬停/选中背景 */ + if (selected) { + du_fill_rect(ctx, x, y, KATE_ICON_W, KATE_ICON_H, KS_ACCENT_DIM); + du_rect_outline(ctx, x, y, KATE_ICON_W, KATE_ICON_H, KS_ACCENT, 1); + du_kate_glow_border(ctx, x, y, KATE_ICON_W, KATE_ICON_H, KS_ACCENT); + } + /* 图标占位区域(40x40 居中)*/ + du_i64 ix = x + (KATE_ICON_W - KATE_ICON_IMG) / 2; + du_i64 iy = y + 10; + du_rect_outline(ctx, ix, iy, KATE_ICON_IMG, KATE_ICON_IMG, KS_BORDER, 1); + /* 标签 */ + if (label) { + du_i64 ll = 0; + while (label[ll]) ll++; + du_i64 lx = x + (KATE_ICON_W - ll * DU_ASCII_STEP) / 2; + if (lx < x + 2) lx = x + 2; + du_draw_string(ctx, label, lx, y + KATE_ICON_IMG + 18, + KS_TEXT_PRIMARY, 0, DU_ASCII_STEP); + } +} + +/* 绘制 Winux-Kate 任务栏项(.tb-item 样式) */ +static inline void du_kate_tb_item(du_context *ctx, du_i64 x, du_i64 y, + const char *title, int active) { + du_i64 tl = 0; + while (title[tl]) tl++; + du_i64 w = tl * DU_ASCII_STEP + 24; + if (w > KATE_TB_ITEM_MAX_W) w = KATE_TB_ITEM_MAX_W; + + du_u32 fg = active ? KS_ACCENT : KS_TEXT_PRIMARY; + if (active) { + du_fill_rect(ctx, x, y, w, 28, KS_ACCENT_DIM); + } + du_rect_outline(ctx, x, y, w, 28, KS_BORDER, 1); + if (active) { + du_kate_glow_border(ctx, x, y, w, 28, KS_ACCENT); + } + du_draw_string(ctx, title, x + 8, y + (28 - DU_ASCII_LINE_H) / 2 + 1, + fg, 0, DU_ASCII_STEP); +} + +/* 绘制 Winux-Kate 按钮 (.btn 样式) */ +static inline void du_kate_button(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, const char *label, + int hover) { + du_u32 fg = KS_ACCENT; + if (hover) { + du_fill_rect(ctx, x, y, w, h, KS_ACCENT_DIM); + } + du_rect_outline(ctx, x, y, w, h, KS_BORDER, 1); + if (hover) { + du_kate_glow_border(ctx, x, y, w, h, KS_ACCENT); + } + du_i64 ll = 0; + while (label[ll]) ll++; + du_i64 lx = x + (w - ll * DU_ASCII_STEP) / 2; + du_draw_string(ctx, label, lx, y + (h - DU_ASCII_LINE_H) / 2 + 1, + fg, hover ? KS_ACCENT_DIM : KS_BG_SECONDARY, DU_ASCII_STEP); +} + #endif /* DESHAB_UI_H */ diff --git a/CODE/UTSM/include/utsm/ept.h b/CODE/UTSM/include/utsm/ept.h new file mode 100644 index 0000000..aa3335b --- /dev/null +++ b/CODE/UTSM/include/utsm/ept.h @@ -0,0 +1,33 @@ +#ifndef UTSM_EPT_H +#define UTSM_EPT_H + +#include + +/* EPT 页表结构:4 级页表(PML4 / PDPT / PD / PT),每级 512 项,每页 4KB。 */ + +#define EPT_PAGE_SIZE 4096ULL +#define EPT_ENTRIES 512ULL + +/* EPTP 构造:bits [5:0]=memory type, bits [7:6]=walk length-1, bits [51:12]=PML4 HPA */ +#define EPTP_WALK_LEN_4 (3ULL << 6) /* 4 级页表 */ +#define EPTP_MEMTYPE_WB (EPT_MEMORY_TYPE_WB) + +/* 初始化 EPT:分配 PML4,置零。返回 0 成功。 */ +int ept_init(void); + +/* 构造 EPTP 值(用于 VMCS EPT_POINTER 字段)。 */ +u64 ept_get_eptp(void); + +/* 在 EPT 中映射一段 GPA→HPA 区间,权限由 flags 决定(EPT_READ/WRITE/EXECUTE)。 + * 自动按 4KB 分配中间页表页。返回 0 成功。 */ +int ept_map_range(u64 gpa, u64 hpa, u64 size, u64 flags); + +/* 便利函数:1:1 映射(GPA==HPA)。 */ +int ept_identity_map(u64 gpa, u64 size, u64 flags); + +/* Walk the EPT to translate a GPA to its mapped HPA. + * Returns the HPA on success, 0 if the GPA is not mapped. + * Handles 4KB pages and 2MB large pages. */ +u64 ept_gpa_to_hpa(u64 gpa); + +#endif diff --git a/CODE/UTSM/include/utsm/hypercall.h b/CODE/UTSM/include/utsm/hypercall.h new file mode 100644 index 0000000..832355c --- /dev/null +++ b/CODE/UTSM/include/utsm/hypercall.h @@ -0,0 +1,39 @@ +#ifndef UTSM_HYPERCALL_H +#define UTSM_HYPERCALL_H + +/* UTSM-side VMCALL hypercall handler. + * + * When the Linux guest executes a VMCALL instruction, it triggers a VM-Exit + * with exit reason EXIT_VMCALL (18). The guest places: + * RAX = UTSM_HCALL_MAGIC | op (identifies this as a UTSM hypercall) + * RDI = arg0 + * RSI = arg1 + * RDX = arg2 + * + * The handler validates the magic, dispatches to the operation handler, + * writes the return value into GUEST_RAX, and advances GUEST_RIP past + * the VMCALL instruction so the guest resumes normally. + * + * If the magic doesn't match, the handler returns a VMFAIL indication + * (by setting the VMCS vm-exit instruction error), which causes the guest + * to see VMCALL fail. Phase 1.3: we just return -UTSM_HCALL_NOSYS. + * + * Protocol details in CODE/utsm-ipc/ipc_proto.h. + */ + +#include + +/* VM-Exit handler for VMCALL (exit reason 18). + * + * Reads guest RAX/RDI/RSI/RDX from the VMCS save area (passed in by the + * assembly exit stub), dispatches the hypercall, writes the result to + * GUEST_RAX, and advances GUEST_RIP. + * + * Returns 1 to resume the guest, 0 to terminate. */ +int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx, + u64 guest_rip, u64 instr_len); + +/* Statistics */ +u64 hypercall_get_count(void); + +#endif /* UTSM_HYPERCALL_H */ diff --git a/CODE/UTSM/include/utsm/ipc_shm.h b/CODE/UTSM/include/utsm/ipc_shm.h new file mode 100644 index 0000000..7c0adb8 --- /dev/null +++ b/CODE/UTSM/include/utsm/ipc_shm.h @@ -0,0 +1,68 @@ +#ifndef UTSM_IPC_SHM_H +#define UTSM_IPC_SHM_H + +/* UTSM-side IPC shared memory manager. + * + * Allocates a 1MB contiguous physical region, initializes the + * utsm_ipc_shm structure (header + two ring buffers + payload pool), + * and EPT-maps it at LINUX_GUEST_IPC_SHM_GPA so the Linux guest can + * access it after querying the GPA via UTSM_HCALL_SHM_INFO. + * + * The shared protocol structures are defined in CODE/utsm-ipc/ipc_proto.h, + * which is included by both UTSM and the Linux utsm-ipc driver. + * + * Ring buffer discipline: + * utsm_to_linux: UTSM produces (ipc_shm_send), Linux consumes + * linux_to_utsm: Linux produces, UTSM consumes (ipc_shm_recv) + */ + +#include +#include /* shared UTSM↔Linux IPC protocol (struct utsm_ipc_msg, etc.) */ + +/* ===== Initialization ===== */ + +/* Allocate, initialize, and EPT-map the IPC shared memory region. + * Must be called after vmm_init() (EPT is required) and before linux_launch(). + * Returns 0 on success, negative on error. */ +int ipc_shm_init(void); + +/* Shut down IPC: mark UTSM as not ready. + * Does not free the shared memory (it may still be referenced by EPT). */ +void ipc_shm_shutdown(void); + +/* Query state */ +int ipc_shm_is_ready(void); + +/* Get the HPA of the shared memory region (for EPT mapping). + * Returns 0 if not initialized. */ +u64 ipc_shm_get_hpa(void); + +/* Get the GPA of the shared memory region (passed to Linux via hypercall). */ +u64 ipc_shm_get_gpa(void); + +/* Get the size of the shared memory region. */ +u64 ipc_shm_get_size(void); + +/* ===== Message API (UTSM-side) ===== */ + +/* Send a message from UTSM to Linux (enqueues into utsm_to_linux ring). + * Returns 0 on success, -1 if the ring is full. */ +int ipc_shm_send(u32 msg_type, const void *data, u32 data_len); + +/* Receive a message from Linux to UTSM (dequeues from linux_to_utsm ring). + * Returns 0 on success (msg filled in), -1 if the ring is empty. */ +int ipc_shm_recv(struct utsm_ipc_msg *msg_out); + +/* Check if there are pending messages from Linux. */ +int ipc_shm_has_pending(void); + +/* Notify Linux that a message is waiting (placeholder for IPI injection). + * Phase 1.3: logs the event; Phase 1.4+ will inject a guest IRQ. */ +void ipc_shm_notify_linux(void); + +/* ===== Debug ===== */ + +/* Dump ring buffer statistics to the UTSM serial console. */ +void ipc_shm_dump_stats(void); + +#endif /* UTSM_IPC_SHM_H */ diff --git a/CODE/UTSM/include/utsm/linux_loader.h b/CODE/UTSM/include/utsm/linux_loader.h new file mode 100644 index 0000000..0f6f204 --- /dev/null +++ b/CODE/UTSM/include/utsm/linux_loader.h @@ -0,0 +1,170 @@ +#ifndef UTSM_LINUX_LOADER_H +#define UTSM_LINUX_LOADER_H + +#include + +/* ===== Linux boot protocol structures (Documentation/x86/boot.rst) ===== */ + +/* bzImage setup_header — lives at offset 0x1f1 inside bzImage and boot_params. + * Only the fields we actually use are listed; gaps are reserved/padding. */ +struct linux_setup_header { + u8 setup_sects; /* 0x1f1: number of setup sectors */ + u16 root_flags; /* 0x1f2 */ + u32 syssize; /* 0x1f4: protected-mode code size / 16 */ + u16 ram_size; /* 0x1f8 */ + u16 vid_mode; /* 0x1fa */ + u16 boot_flag; /* 0x1fe: 0xAA55 */ + u16 jump; /* 0x200: jump insn */ + u32 header; /* 0x202: "HdrS" magic */ + u16 version; /* 0x206: boot protocol version */ + u32 realmode_swtch; /* 0x208 */ + u16 start_sys_seg; /* 0x20c */ + u16 kernel_version; /* 0x20e: offset to version string */ + u8 type_of_loader; /* 0x210 */ + u8 loadflags; /* 0x211 */ + u16 setup_move_size; /* 0x212 */ + u32 code32_start; /* 0x214: 32-bit entry (also 64-bit entry base) */ + u32 ramdisk_image; /* 0x218 */ + u32 ramdisk_size; /* 0x21c */ + u32 bootsect_kludge; /* 0x220 */ + u16 heap_end_ptr; /* 0x224 */ + u8 ext_loader_ver; /* 0x226 */ + u8 ext_loader_type; /* 0x227 */ + u32 cmd_line_ptr; /* 0x228: command line physical address */ + u32 init_size; /* 0x22c: total init memory needed */ + u32 acpi_rsdp_addr; /* 0x230 */ + u8 _pad0[4]; /* 0x234: relocatable_kernel + min_alignment */ + u16 xloadflags; /* 0x236: bit0=XLF_KERNEL_64 */ + u32 cmdline_size; /* 0x238 */ + u32 _pad1; /* 0x23c */ + u64 setup_data; /* 0x240: linked list of setup_data */ + u64 pref_address; /* 0x248 → wait, this is payload_offset */ + + /* NOTE: The real layout continues with payload_offset at 0x248. + * However, pref_address is at 0x258 and init_size at 0x260. + * The struct above is intentionally simplified — we read fields + * by offset rather than relying on this struct for the full layout. */ +} __attribute__((packed)); + +/* boot_params is 4096 bytes. The setup_header is at offset 0x1f1. */ +#define BOOT_PARAMS_SIZE 4096 +#define SETUP_HEADER_OFFSET 0x1f1 + +/* Key field offsets within bzImage / boot_params */ +#define BZ_BOOT_FLAG_OFF 0x1fe +#define BZ_HEADER_MAGIC 0x53726448 /* "HdrS" */ +#define BZ_HEADER_OFF 0x202 +#define BZ_VERSION_OFF 0x206 +#define BZ_TYPE_OF_LOADER_OFF 0x210 +#define BZ_LOADFLAGS_OFF 0x211 +#define BZ_CODE32_START_OFF 0x214 +#define BZ_RAMDISK_IMAGE_OFF 0x218 +#define BZ_RAMDISK_SIZE_OFF 0x21c +#define BZ_CMD_LINE_PTR_OFF 0x228 +#define BZ_INIT_SIZE_OFF 0x22c /* wait — actually init_size is at 0x260 */ +#define BZ_XLOADFLAGS_OFF 0x236 +#define BZ_CMDLINE_SIZE_OFF 0x238 +#define BZ_PAYLOAD_OFFSET_OFF 0x248 +#define BZ_PAYLOAD_LENGTH_OFF 0x24c +#define BZ_PREF_ADDRESS_OFF 0x258 +#define BZ_INIT_SIZE_ACTUAL_OFF 0x260 + +/* xloadflags bits */ +#define XLF_KERNEL_64 0x0001 +#define XLF_CAN_BE_LOADED_ABOVE 0x0002 +#define XLF_EFI_HANDOVER 0x0010 +#define XLF_EFI_KEXEC 0x0020 + +/* loadflags bits */ +#define LOADFLAG_LOADED_HIGH 0x01 +#define LOADFLAG_KEEP_ROOT 0x02 +#define LOADFLAG_CAN_USE_HEAP 0x80 + +/* e820 entry types */ +#define E820_TYPE_RAM 1 +#define E820_TYPE_RESERVED 2 +#define E820_TYPE_ACPI 3 +#define E820_TYPE_NVS 4 +#define E820_TYPE_UNUSABLE 5 +#define E820_TYPE_PMEM 7 + +/* e820_table entry in boot_params at offset 0x2e0 (actually 0x2d0 in older, + * 0x2e0 in newer). We use 0x2e0 and the e820_entries count at 0x1e8. + * Each entry is 20 bytes: u64 addr, u64 size, u32 type. */ +#define E820_ENTRIES_OFFSET 0x1e8 +#define E820_TABLE_OFFSET 0x2e0 +#define E820_MAX_ENTRIES 128 +#define E820_ENTRY_SIZE 20 + +/* ===== Guest memory layout (GPA) ===== + * Based on utsm-linux-dual-kernel-architecture.md. + * 0x00000000-0x003FFFFF : UTSM reserved (denied to Linux) + * 0x00400000-0x004FFFFF : boot_params / cmdline / GDT / page tables / stack + * 0x01000000-0x02FFFFFF : Linux kernel image + * 0x03000000-0x03FFFFFF : Linux initrd + * 0x04000000-0x040FFFFF : IPC shared memory (1MB, both kernels RW) + * 0x05000000-MAX : Linux general RAM + */ +#define LINUX_GUEST_KERNEL_GPA 0x01000000ULL /* 16 MB, 2MB-aligned */ +#define LINUX_GUEST_BOOTPARAMS_GPA 0x00400000ULL /* 4 MB */ +#define LINUX_GUEST_CMDLINE_GPA 0x00500000ULL /* 5 MB */ +#define LINUX_GUEST_INITRD_GPA 0x03000000ULL /* 48 MB (within kernel+initrd region) */ +#define LINUX_GUEST_STACK_GPA 0x00800000ULL /* 8 MB (stack grows down) */ +#define LINUX_GUEST_GDT_GPA 0x00600000ULL /* 6 MB (guest GDT) */ +#define LINUX_GUEST_PGT_GPA 0x00700000ULL /* 7 MB (guest page tables) */ +#define LINUX_GUEST_CMDLINE_MAX 4096 + +/* IPC shared memory GPA (must match CODE/utsm-ipc/ipc_proto.h) */ +#define LINUX_GUEST_IPC_SHM_GPA 0x04000000ULL /* 64 MB */ +#define LINUX_GUEST_IPC_SHM_SIZE (1024 * 1024) /* 1MB */ + +/* General RAM starts after IPC region */ +#define LINUX_GUEST_RAM_GPA 0x05000000ULL /* 80 MB */ + +/* ===== Linux loader API ===== */ + +/* Loaded Linux guest state */ +struct linux_guest_info { + u64 kernel_gpa; /* GPA where kernel was loaded */ + u64 kernel_entry; /* 64-bit entry point GPA */ + u64 kernel_size; /* Size of loaded kernel image */ + u64 bootparams_gpa; /* GPA of boot_params */ + u64 cmdline_gpa; /* GPA of command line */ + u64 cmdline_size; /* Command line length (with NUL) */ + u64 initrd_gpa; /* GPA of initrd (0 = none) */ + u64 initrd_size; /* Size of initrd */ + u64 pgt_gpa; /* GPA of guest page tables */ + u64 gdt_gpa; /* GPA of guest GDT */ + int loaded; /* 1 = successfully loaded */ +}; + +/* Find the Linux bzImage boot module among Limine modules. + * Returns pointer to the module data, or NULL if not found. + * Sets *size_out to the module size. */ +void *linux_find_bzimage_module(u64 *size_out); + +/* Parse bzImage header and extract key parameters. + * Returns 0 on success, negative on error. */ +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); + +/* Initialize the Linux guest: parse bzImage, allocate guest memory, + * set up boot_params, page tables, GDT, and EPT mappings. + * Returns 0 on success, negative on error. */ +int linux_loader_init(void); + +/* Get the loaded Linux guest info (NULL if not loaded). */ +const struct linux_guest_info *linux_get_guest_info(void); + +/* Configure VMCS for Linux guest and perform vmlaunch. + * Must be called after vmm_init() + linux_loader_init(). + * Returns 0 on successful guest termination, negative on error. */ +int linux_launch(void); + +/* Default Linux command line (can be overridden). */ +#define LINUX_DEFAULT_CMDLINE \ + "console=ttyS0,115200 earlyprintk=serial nokaslr " \ + "no_timer_check loglevel=7" + +#endif diff --git a/CODE/UTSM/include/utsm/utsm.h b/CODE/UTSM/include/utsm/utsm.h index 1740c5c..7dbe15a 100644 --- a/CODE/UTSM/include/utsm/utsm.h +++ b/CODE/UTSM/include/utsm/utsm.h @@ -9,6 +9,10 @@ #include #include #include +#include +#include +#include +#include void utsm_init(void); int utsm_selftest_run(void); diff --git a/CODE/UTSM/include/utsm/vmm.h b/CODE/UTSM/include/utsm/vmm.h new file mode 100644 index 0000000..d130267 --- /dev/null +++ b/CODE/UTSM/include/utsm/vmm.h @@ -0,0 +1,63 @@ +#ifndef UTSM_VMM_H +#define UTSM_VMM_H + +#include + +/* VMM 顶层 API:协调 VMX/EPT/VMCS/VMEXIT,启动 guest。 + * + * Phase 1.1(当前):vmm_init() 完成 VMX root 启用 + EPT 初始化, + * vmm_self_test() 启动一个最小 guest(HLT 指令)验证 VMEXIT 路径。 + * + * Phase 1.2+:vmm_launch_linux() 加载并启动 Linux 6.6 LTS guest。 + */ + +/* ===== Guest register save area ===== + * + * VMCS does NOT save guest GPRs (RAX-R15) on VM-Exit. The assembly VM-Exit + * handler (vmexit_asm.S) saves all 16 GPRs into this structure before + * calling the C dispatcher, and restores them before vmresume. + * + * C handlers (e.g. hypercall.c) can read guest arguments from this structure + * and write return values (e.g. g_guest_regs.rax = retval) which will be + * loaded into the CPU before vmresume resumes the guest. */ +struct vmexit_guest_regs { + u64 rax; + u64 rbx; + u64 rcx; + u64 rdx; + u64 rsi; + u64 rdi; + u64 rbp; + u64 r8; + u64 r9; + u64 r10; + u64 r11; + u64 r12; + u64 r13; + u64 r14; + u64 r15; +}; + +/* Global guest register save area (referenced by vmexit_asm.S). + * Non-static: shared between vmm.c, vmexit.c, hypercall.c, and vmexit_asm.S. */ +extern volatile struct vmexit_guest_regs g_guest_regs; + +/* ===== VMM API ===== */ + +/* 初始化 VMM:VMX root 模式 + EPT 表 + 分配 VMCS。 + * 返回 0 成功,负值失败。 */ +int vmm_init(void); + +/* 关闭 VMM:vmxoff。 */ +void vmm_shutdown(void); + +/* Phase 1.1 自检:启动最小 guest 验证 VMX 路径。 + * guest 代码为内嵌的 HLT 指令,预期触发 HLT VMEXIT。 + * 返回 0 成功。 */ +int vmm_self_test(void); + +/* VMM 状态查询 */ +int vmm_is_ready(void); +u64 vmm_get_eptp(void); + +#endif diff --git a/CODE/UTSM/include/utsm/vmx.h b/CODE/UTSM/include/utsm/vmx.h new file mode 100644 index 0000000..4d737f3 --- /dev/null +++ b/CODE/UTSM/include/utsm/vmx.h @@ -0,0 +1,268 @@ +#ifndef UTSM_VMX_H +#define UTSM_VMX_H + +#include + +/* ===== VMX MSRs ===== */ +#define IA32_VMX_BASIC 0x480 +#define IA32_VMX_PINBASED_CTLS 0x481 +#define IA32_VMX_PROCBASED_CTLS 0x482 +#define IA32_VMX_EXIT_CTLS 0x483 +#define IA32_VMX_ENTRY_CTLS 0x484 +#define IA32_VMX_PROCBASED_CTLS2 0x48B +#define IA32_VMX_EPT_VPID_CAP 0x48C +#define IA32_VMX_TRUE_PINBASED_CTLS 0x48D +#define IA32_VMX_TRUE_PROCBASED_CTLS 0x48E +#define IA32_VMX_TRUE_EXIT_CTLS 0x48F +#define IA32_VMX_TRUE_ENTRY_CTLS 0x490 + +/* ===== CR4 / CR0 bits ===== */ +#define CR4_VMXE_BIT 13ULL +#define CR4_VMXE (1ULL << CR4_VMXE_BIT) +#define CR4_PAE (1ULL << 5) +#define CR4_PGE (1ULL << 7) +#define CR4_PSE (1ULL << 4) + +#define CR0_PE (1ULL << 0) +#define CR0_NE (1ULL << 5) +#define CR0_WP (1ULL << 16) +#define CR0_PG (1ULL << 31) + +/* ===== VMCS field encodings (Intel SDM Vol 3, Appendix H) ===== */ +/* Field encoding: bit 13=1 -> host root, bit 12=1 -> guest, bits [11:10] = type, bits [9:1] = index */ +#define VMCS_FIELD_HOST 0x2000ULL +#define VMCS_FIELD_GUEST 0x1000ULL +#define VMCS_FIELD_CTRL 0x4000ULL +#define VMCS_FIELD_READ 0x6000ULL + +/* Control fields */ +#define VMCS_VPID 0x0000 +#define VMCS_GUEST_ES_ADDR 0x0006 +#define VMCS_GUEST_CS_ADDR 0x0008 +#define VMCS_GUEST_SS_ADDR 0x000A +#define VMCS_GUEST_DS_ADDR 0x000C +#define VMCS_GUEST_FS_ADDR 0x000E +#define VMCS_GUEST_GS_ADDR 0x0010 +#define VMCS_GUEST_LDTR_ADDR 0x0012 +#define VMCS_GUEST_TR_ADDR 0x0014 +#define VMCS_GUEST_INTR_STATUS 0x0009 +#define VMCS_IO_BITMAP_A 0x2000 +#define VMCS_IO_BITMAP_B 0x2002 +#define VMCS_MSR_BITMAP 0x2004 +#define VMCS_TSC_OFFSET 0x2010 +#define VMCS_EPT_POINTER 0x201A + +/* Read-only data fields */ +#define VMCS_GUEST_PHYSICAL_ADDR 0x2400 +#define VMCS_EXIT_REASON 0x4402 +#define VMCS_EXIT_QUALIFICATION 0x6400 +#define VMCS_IO_RCX 0x6404 +#define VMCS_IO_RSI 0x6406 +#define VMCS_IO_RDI 0x6408 +#define VMCS_IO_RIP 0x640A +#define VMCS_GUEST_LINEAR_ADDR 0x640A +#define VMCS_INSTRUCTION_LENGTH 0x440C +#define VMCS_VMX_INSTRUCTION_ERROR 0x4406 + +/* Guest-state fields */ +#define VMCS_GUEST_ES_SELECTOR 0x0800 +#define VMCS_GUEST_CS_SELECTOR 0x0802 +#define VMCS_GUEST_SS_SELECTOR 0x0804 +#define VMCS_GUEST_DS_SELECTOR 0x0806 +#define VMCS_GUEST_FS_SELECTOR 0x0808 +#define VMCS_GUEST_GS_SELECTOR 0x080A +#define VMCS_GUEST_LDTR_SELECTOR 0x080C +#define VMCS_GUEST_TR_SELECTOR 0x080E +#define VMCS_GUEST_CS_LIMIT 0x4800 +#define VMCS_GUEST_SS_LIMIT 0x4804 +#define VMCS_GUEST_DS_LIMIT 0x4806 +#define VMCS_GUEST_ES_LIMIT 0x4801 +#define VMCS_GUEST_FS_LIMIT 0x4808 +#define VMCS_GUEST_GS_LIMIT 0x480A +#define VMCS_GUEST_LDTR_LIMIT 0x480C +#define VMCS_GUEST_TR_LIMIT 0x480E +#define VMCS_GUEST_CS_ACCESS 0x4816 +#define VMCS_GUEST_SS_ACCESS 0x481A +#define VMCS_GUEST_DS_ACCESS 0x4818 +#define VMCS_GUEST_ES_ACCESS 0x4814 +#define VMCS_GUEST_FS_ACCESS 0x481C +#define VMCS_GUEST_GS_ACCESS 0x481E +#define VMCS_GUEST_LDTR_ACCESS 0x4820 +#define VMCS_GUEST_TR_ACCESS 0x4822 +#define VMCS_GUEST_INTERRUPTIBILITY 0x4824 +#define VMCS_GUEST_ACTIVITY_STATE 0x4826 +#define VMCS_GUEST_SYSENTER_CS 0x482A +#define VMCS_GUEST_PREEMPTION_TIMER 0x482E + +/* 64-bit guest-state fields(与 PDPTR 共享编码,仅在 EPT 启用时为 EFER/PAT) */ +#define VMCS_GUEST_IA32_EFER 0x2806 +#define VMCS_GUEST_IA32_PAT 0x2804 +#define VMCS_GUEST_IA32_PERF_GLOBAL_CTRL 0x2808 +#define VMCS_GUEST_CR0 0x6800 +#define VMCS_GUEST_CR3 0x6802 +#define VMCS_GUEST_CR4 0x6804 +#define VMCS_GUEST_ES_BASE 0x6806 +#define VMCS_GUEST_CS_BASE 0x6808 +#define VMCS_GUEST_SS_BASE 0x680A +#define VMCS_GUEST_DS_BASE 0x680C +#define VMCS_GUEST_FS_BASE 0x680E +#define VMCS_GUEST_GS_BASE 0x6810 +#define VMCS_GUEST_LDTR_BASE 0x6812 +#define VMCS_GUEST_TR_BASE 0x6814 +#define VMCS_GUEST_GDTR_BASE 0x6816 +#define VMCS_GUEST_GDTR_LIMIT 0x4810 +#define VMCS_GUEST_IDTR_BASE 0x6818 +#define VMCS_GUEST_IDTR_LIMIT 0x4812 +#define VMCS_GUEST_DR7 0x681A +#define VMCS_GUEST_RSP 0x681C +#define VMCS_GUEST_RIP 0x681E +#define VMCS_GUEST_RFLAGS 0x6820 +#define VMCS_GUEST_SYSENTER_ESP 0x6824 +#define VMCS_GUEST_SYSENTER_EIP 0x6826 + +/* Host-state fields (Intel SDM Vol 3D, Appendix H, Table H-6) */ +#define VMCS_HOST_CR0 0x6C00 +#define VMCS_HOST_CR3 0x6C02 +#define VMCS_HOST_CR4 0x6C04 +#define VMCS_HOST_ES_SELECTOR 0x0C00 +#define VMCS_HOST_CS_SELECTOR 0x0C02 +#define VMCS_HOST_SS_SELECTOR 0x0C04 +#define VMCS_HOST_DS_SELECTOR 0x0C06 +#define VMCS_HOST_FS_SELECTOR 0x0C08 +#define VMCS_HOST_GS_SELECTOR 0x0C0A +#define VMCS_HOST_TR_SELECTOR 0x0C0C +#define VMCS_HOST_FS_BASE 0x6C06 +#define VMCS_HOST_GS_BASE 0x6C08 +#define VMCS_HOST_TR_BASE 0x6C0A +#define VMCS_HOST_GDTR_BASE 0x6C0C +#define VMCS_HOST_IDTR_BASE 0x6C0E +#define VMCS_HOST_SYSENTER_CS 0x4C00 +#define VMCS_HOST_RSP 0x6C14 +#define VMCS_HOST_RIP 0x6C16 +#define VMCS_HOST_SYSENTER_ESP 0x6C10 +#define VMCS_HOST_SYSENTER_EIP 0x6C12 + +/* 64-bit host-state fields */ +#define VMCS_HOST_IA32_EFER 0x2C02 +#define VMCS_HOST_IA32_PAT 0x2C00 +#define VMCS_HOST_IA32_PERF_GLOBAL_CTRL 0x2C04 + +/* IA32_EFER MSR (0xC0000080) bits */ +#define EFER_SCE (1ULL << 0) +#define EFER_LME (1ULL << 8) +#define EFER_LMA (1ULL << 10) +#define EFER_NXE (1ULL << 11) +#define EFER_SVME (1ULL << 12) +#define EFER_LMSLE (1ULL << 13) +#define EFER_FFXSR (1ULL << 14) +#define EFER_TCE (1ULL << 15) + +/* VM-execution control fields */ +#define VMCS_PIN_BASED_VM_EXEC_CONTROL 0x4000 +#define VMCS_CPU_BASED_VM_EXEC_CONTROL 0x4002 +#define VMCS_EXCEPTION_BITMAP 0x4004 +#define VMCS_VM_EXIT_CONTROLS 0x400C +#define VMCS_VM_ENTRY_CONTROLS 0x4012 +#define VMCS_VM_ENTRY_INTERRUPT_INFO 0x4016 +#define VMCS_SECONDARY_VM_EXEC_CONTROL 0x401E + +/* ===== Pin-based controls ===== */ +#define PIN_EXT_INTERRUPT_EXITING (1ULL << 0) +#define PIN_NMI_EXITING (1ULL << 3) + +/* ===== Primary processor-based controls ===== */ +#define CPU_BASED_HLT_EXITING (1ULL << 7) +#define CPU_BASED_INVLPG_EXITING (1ULL << 9) +#define CPU_BASED_MWAIT_EXITING (1ULL << 10) +#define CPU_BASED_RDPMC_EXITING (1ULL << 11) +#define CPU_BASED_RDTSC_EXITING (1ULL << 12) +#define CPU_BASED_CR8_LOAD_EXITING (1ULL << 19) +#define CPU_BASED_CR8_STORE_EXITING (1ULL << 20) +#define CPU_BASED_MOV_DR_EXITING (1ULL << 23) +#define CPU_BASED_UNCOND_IO_EXITING (1ULL << 24) +#define CPU_BASED_USE_IO_BITMAPS (1ULL << 25) +#define CPU_BASED_PAUSE_EXITING (1ULL << 30) +#define CPU_BASED_ACTIVATE_SECONDARY (1ULL << 31) +#define CPU_BASED_USE_MSR_BITMAPS (1ULL << 28) + +/* ===== Secondary processor-based controls ===== */ +#define SEC_EXEC_ENABLE_EPT (1ULL << 1) +#define SEC_EXEC_ENABLE_VPID (1ULL << 5) +#define SEC_EXEC_UNRESTRICTED_GUEST (1ULL << 7) + +/* ===== VM-exit controls ===== */ +#define VM_EXIT_SAVE_DEBUG_CONTROLS (1ULL << 2) +#define VM_EXIT_HOST_ADDR_SPACE_SIZE (1ULL << 9) +#define VM_EXIT_LOAD_PERF_GLOBAL_CTRL (1ULL << 12) +#define VM_EXIT_ACK_INTR_ON_EXIT (1ULL << 15) +#define VM_EXIT_SAVE_GUEST_EFER (1ULL << 20) +#define VM_EXIT_LOAD_HOST_EFER (1ULL << 21) + +/* ===== VM-entry controls ===== */ +#define VM_ENTRY_LOAD_DEBUG_CONTROLS (1ULL << 2) +#define VM_ENTRY_IA32E_MODE_GUEST (1ULL << 9) +#define VM_ENTRY_LOAD_GUEST_EFER (1ULL << 15) + +/* ===== Exit reasons ===== */ +#define EXIT_EXCEPTION_NMI 0 +#define EXIT_EXTERNAL_INTERRUPT 1 +#define EXIT_TRIPLE_FAULT 2 +#define EXIT_INIT_SIGNAL 3 +#define EXIT_CPUID 10 +#define EXIT_INVD 13 +#define EXIT_RDMSR 31 +#define EXIT_WRMSR 32 +#define EXIT_MONITOR 39 +#define EXIT_MWAIT 36 +#define EXIT_VMCALL 18 +#define EXIT_VMCLEAR 19 +#define EXIT_VMLAUNCH 20 +#define EXIT_VMPTRLD 21 +#define EXIT_VMPTRST 22 +#define EXIT_VMREAD 23 +#define EXIT_VMRESUME 24 +#define EXIT_VMWRITE 25 +#define EXIT_VMXOFF 26 +#define EXIT_VMXON 27 +#define EXIT_CONTROL_ACCESS 28 +#define EXIT_IO_INSTRUCTION 30 +#define EXIT_RDPMC 15 +#define EXIT_RDTSC 16 +#define EXIT_HLT 12 +#define EXIT_INVLPG 14 +#define EXIT_EPT_VIOLATION 48 +#define EXIT_EPT_MISCONFIG 49 +#define EXIT_APIC_ACCESS 44 + +/* ===== EPT memory types ===== */ +#define EPT_MEMORY_TYPE_UC 0ULL +#define EPT_MEMORY_TYPE_WC 1ULL +#define EPT_MEMORY_TYPE_WT 4ULL +#define EPT_MEMORY_TYPE_WP 5ULL +#define EPT_MEMORY_TYPE_WB 6ULL + +/* ===== EPT entry bits ===== */ +#define EPT_READ (1ULL << 0) +#define EPT_WRITE (1ULL << 1) +#define EPT_EXECUTE (1ULL << 2) +#define EPT_RWX (EPT_READ | EPT_WRITE | EPT_EXECUTE) +#define EPT_LARGE_PAGE (1ULL << 7) + +/* ===== VMX instruction errors ===== */ +#define VMXERR_VMCALL_INVALID 1 + +/* ===== Public API ===== */ +int vmx_supported(void); +int vmx_enable(void); +int vmx_disable(void); +int vmx_vmcs_alloc(u64 *phys_out); +int vmx_vmcs_load(u64 phys); +u64 vmx_vmcs_read(u64 field); +void vmx_vmcs_write(u64 field, u64 value); +int vmx_vmlaunch(void); +int vmx_vmresume(void); + +u64 vmx_read_msr(u32 msr); +u64 vmx_get_host_cr3(void); + +#endif diff --git a/CODE/UTSM/kernel/main.c b/CODE/UTSM/kernel/main.c index 53fb922..3eabec6 100644 --- a/CODE/UTSM/kernel/main.c +++ b/CODE/UTSM/kernel/main.c @@ -7,6 +7,7 @@ #include #include #include +#include void arch_halt_forever(void); @@ -43,6 +44,49 @@ void kernel_main(void) { int result = utsm_selftest_run(); if (result == 0) { log_info("[UTSM] SELFTEST PASS"); + + /* Phase 1.1: VMM (VMX/EPT) 初始化 + 自检 + * 若 VMX 不可用(如 QEMU 未启用),仅警告,不阻断 DSK 启动 */ + if (vmm_init() == 0) { + log_info("[UTSM] VMM init ok"); + int vmm_st = vmm_self_test(); + if (vmm_st == 0) { + log_info("[UTSM] VMM self-test PASS"); + + /* Phase 1.2: Linux 加载器 + 启动 + * 若 bzImage 模块存在,加载并启动 Linux guest。 + * 若无 bzImage,跳过 Linux 启动,继续 DSK 流程。 */ + if (linux_loader_init() == 0) { + log_info("[UTSM] Linux loader init ok"); + + /* Phase 1.3: Initialize IPC shared memory before + * launching Linux. Allocates 1MB, initializes ring + * buffers, and EPT-maps at GPA 0x04000000 so the + * Linux guest can access it via UTSM_HCALL_SHM_INFO. */ + if (ipc_shm_init() == 0) { + log_info("[UTSM] IPC shm init ok"); + } else { + log_error("[UTSM] IPC shm init failed (non-fatal)"); + } + + int lin_st = linux_launch(); + if (lin_st == 0) { + log_info("[UTSM] Linux guest terminated"); + } else { + log_error("[UTSM] Linux launch failed"); + log_hex64("[UTSM] Linux st=", (u64)(i64)lin_st); + } + } else { + log_warn("[UTSM] Linux loader unavailable (no bzImage)"); + } + } else { + log_error("[UTSM] VMM self-test FAIL"); + log_hex64("[UTSM] VMM st=", (u64)(i64)vmm_st); + } + } else { + log_warn("[UTSM] VMM unavailable (VMX not supported or disabled)"); + } + if (dsk_load_and_jump() != 0) { log_error("[UTSM] DSK jump failed"); } diff --git a/CODE/UTSM/kernel/main.o b/CODE/UTSM/kernel/main.o index 84e8746..0c4c444 100644 Binary files a/CODE/UTSM/kernel/main.o and b/CODE/UTSM/kernel/main.o differ diff --git a/CODE/UTSM/vmm/ept.c b/CODE/UTSM/vmm/ept.c new file mode 100644 index 0000000..197e634 --- /dev/null +++ b/CODE/UTSM/vmm/ept.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include "../arch/x86_64/limine.h" + +extern volatile struct limine_hhdm_request g_hhdm_request; + +/* EPT 4 级页表:PML4[512] -> PDPT[512] -> PD[512] -> PT[512] -> 4KB 页 + * 虚拟地址(GPA)拆分: + * bits [51:48] reserved + * bits [47:39] PML4 index + * bits [38:30] PDPT index + * bits [29:21] PD index + * bits [20:12] PT index + * bits [11:0] page offset + * + * EPT entry 格式(4KB 页): + * bits [2:0] R/W/X + * bits [5:3] memory type + * bit 6 ignore PAT + * bit 7 large page + * bits [11:8] ignored + * bits [51:12] physical address (page-aligned) + */ + +static u64 g_ept_pml4_phys; +static u64 g_ept_pml4_virt; +static int g_ept_ready; + +/* 物理地址 → 虚拟地址(HHDM 直映射) */ +static void *phys_to_virt(u64 phys) { + if (!g_hhdm_request.response) return (void *)0; + u64 hhdm = g_hhdm_request.response->offset; + return (void *)(hhdm + phys); +} + +/* 分配一页 4KB 对齐物理页,返回物理地址;置零。 */ +static u64 ept_alloc_page(void) { + dkm_dma_buffer buf; + if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &buf) != 0) { + log_error("[EPT] alloc page failed"); + return 0; + } + /* dma_alloc_pages 已清零 */ + return buf.phys; +} + +int ept_init(void) { + g_ept_pml4_phys = ept_alloc_page(); + if (g_ept_pml4_phys == 0) { + log_error("[EPT] failed to alloc PML4"); + return -1; + } + g_ept_pml4_virt = (u64)phys_to_virt(g_ept_pml4_phys); + g_ept_ready = 1; + log_hex64("[EPT] PML4 phys=", g_ept_pml4_phys); + log_info("[EPT] init ok"); + return 0; +} + +/* 读取 EPT 表项指针,必要时分配中间页表。 + * level: 0=PT, 1=PD, 2=PDPT, 3=PML4 */ +static u64 *ept_walk(u64 gpa, int alloc_missing) { + u64 *table = (u64 *)g_ept_pml4_virt; + u64 indices[4]; + indices[3] = (gpa >> 39) & 0x1FF; + indices[2] = (gpa >> 30) & 0x1FF; + indices[1] = (gpa >> 21) & 0x1FF; + indices[0] = (gpa >> 12) & 0x1FF; + + for (int level = 3; level > 0; level--) { + u64 entry = table[indices[level]]; + if (!(entry & EPT_READ)) { + if (!alloc_missing) return (u64 *)0; + u64 new_phys = ept_alloc_page(); + if (new_phys == 0) return (u64 *)0; + /* 中间页表项:RWX + WB 内存类型 */ + table[indices[level]] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3); + /* 切换到新分配的下一级页表 */ + table = (u64 *)phys_to_virt(new_phys); + } else { + /* 清除标志位取出物理地址 */ + u64 child_phys = entry & 0x000FFFFFFFFFF000ULL; + table = (u64 *)phys_to_virt(child_phys); + } + } + return &table[indices[0]]; +} + +int ept_map_range(u64 gpa, u64 hpa, u64 size, u64 flags) { + if (!g_ept_ready) return -1; + if ((gpa & (EPT_PAGE_SIZE - 1)) || (hpa & (EPT_PAGE_SIZE - 1)) || + (size & (EPT_PAGE_SIZE - 1))) { + log_error("[EPT] unaligned map request"); + return -2; + } + + u64 pages = size / EPT_PAGE_SIZE; + for (u64 i = 0; i < pages; i++) { + u64 cur_gpa = gpa + i * EPT_PAGE_SIZE; + u64 cur_hpa = hpa + i * EPT_PAGE_SIZE; + u64 *entry = ept_walk(cur_gpa, 1); + if (!entry) { + log_error("[EPT] walk failed"); + return -3; + } + *entry = cur_hpa | flags | (EPT_MEMORY_TYPE_WB << 3); + } + return 0; +} + +int ept_identity_map(u64 gpa, u64 size, u64 flags) { + return ept_map_range(gpa, gpa, size, flags); +} + +/* Walk the EPT to translate a GPA to its mapped HPA. + * Returns 0 if the GPA is not mapped (no present entry at any level). + * Handles 4KB pages and 2MB large pages (bit 7 = PS in PD entry). */ +u64 ept_gpa_to_hpa(u64 gpa) { + if (!g_ept_ready) return 0; + + u64 *table = (u64 *)g_ept_pml4_virt; + u64 pml4_idx = (gpa >> 39) & 0x1FF; + u64 pdpt_idx = (gpa >> 30) & 0x1FF; + u64 pd_idx = (gpa >> 21) & 0x1FF; + u64 pt_idx = (gpa >> 12) & 0x1FF; + u64 offset = gpa & 0xFFF; + + /* Level 3: PML4 */ + u64 entry = table[pml4_idx]; + if (!(entry & EPT_READ)) return 0; + table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL); + + /* Level 2: PDPT */ + entry = table[pdpt_idx]; + if (!(entry & EPT_READ)) return 0; + /* 1GB large page (bit 7 set in PDPT entry) */ + if (entry & EPT_LARGE_PAGE) { + u64 hpa = entry & 0x000FFFFFC0000000ULL; /* 1GB page base */ + return hpa + (gpa & 0x3FFFFFFF); /* offset within 1GB */ + } + table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL); + + /* Level 1: PD */ + entry = table[pd_idx]; + if (!(entry & EPT_READ)) return 0; + /* 2MB large page (bit 7 set in PD entry) */ + if (entry & EPT_LARGE_PAGE) { + u64 hpa = entry & 0x000FFFFFFFE00000ULL; /* 2MB page base */ + return hpa + (gpa & 0x1FFFFF); /* offset within 2MB */ + } + table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL); + + /* Level 0: PT */ + entry = table[pt_idx]; + if (!(entry & EPT_READ)) return 0; + u64 hpa = entry & 0x000FFFFFFFFFF000ULL; + return hpa + offset; +} + +u64 ept_get_eptp(void) { + if (!g_ept_ready) return 0; + /* EPTP: bits [5:0] = memtype, bits [7:6] = walk length-1 (3 for 4-level), + * bits [51:12] = PML4 HPA */ + return g_ept_pml4_phys | EPTP_WALK_LEN_4 | EPTP_MEMTYPE_WB; +} diff --git a/CODE/UTSM/vmm/hypercall.c b/CODE/UTSM/vmm/hypercall.c new file mode 100644 index 0000000..afc80f9 --- /dev/null +++ b/CODE/UTSM/vmm/hypercall.c @@ -0,0 +1,247 @@ +/* hypercall.c — UTSM-side VMCALL hypercall handler. + * + * When the Linux guest executes a VMCALL instruction, it triggers a VM-Exit + * with exit reason EXIT_VCALL (18). The guest places: + * RAX = UTSM_HCALL_MAGIC | op (identifies this as a UTSM hypercall) + * RDI = arg0 + * RSI = arg1 + * RDX = arg2 + * + * The VM-Exit assembly stub saves all guest GPRs into g_guest_regs before + * calling the C dispatcher. This handler reads arguments from g_guest_regs, + * dispatches to the operation handler, writes the return value into + * g_guest_regs.rax, and advances GUEST_RIP so the guest resumes after + * the VMCALL instruction. + * + * Protocol details in CODE/utsm-ipc/ipc_proto.h. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../arch/x86_64/limine.h" + +extern volatile struct limine_hhdm_request g_hhdm_request; + +static u64 g_hypercall_count; + +/* ===== Guest memory access helpers ===== + * + * The Linux guest uses identity-mapped page tables (GVA == GPA), so + * pointers passed via hypercall arguments are GPAs. We translate GPA→HPA + * via EPT walk, then add HHDM offset to get the host virtual address. */ + +static void *gpa_to_hva(u64 gpa) { + u64 hpa = ept_gpa_to_hpa(gpa); + if (hpa == 0) return (void *)0; + if (!g_hhdm_request.response) return (void *)0; + u64 hhdm = g_hhdm_request.response->offset; + return (void *)(hhdm + hpa); +} + +/* Write a u64 to guest memory at the given GPA. + * Returns 0 on success, -1 on failure. */ +static int guest_write_u64(u64 gpa, u64 value) { + void *hva = gpa_to_hva(gpa); + if (!hva) return -1; + *(volatile u64 *)hva = value; + return 0; +} + +/* Copy bytes from guest memory (GVA=GPA) to host buffer. + * Handles cross-page boundaries by translating each page separately. + * Returns 0 on success, -1 on failure. */ +static int guest_read_buf(u64 gpa, void *host_dst, u64 len) { + u8 *dst = (u8 *)host_dst; + u64 copied = 0; + while (copied < len) { + void *hva = gpa_to_hva(gpa + copied); + if (!hva) return -1; + u64 page_end = (gpa + copied + 4096) & ~0xFFFULL; + u64 chunk = page_end - (gpa + copied); + if (chunk > len - copied) chunk = len - copied; + const u8 *src = (const u8 *)hva; + for (u64 i = 0; i < chunk; i++) { + dst[copied + i] = src[i]; + } + copied += chunk; + } + return 0; +} + +/* ===== Individual hypercall handlers ===== */ + +static long hcall_ping(u64 a0, u64 a1, u64 a2) { + (void)a0; (void)a1; (void)a2; + /* Return a recognizable PONG value */ + return 0x504F4E47; /* "PONG" */ +} + +static long hcall_hello(u64 a0, u64 a1, u64 a2) { + (void)a0; (void)a1; (void)a2; + log_info("[HCALL] HELLO from Linux guest"); + + /* Linux is announcing itself; the IPC shared memory's linux_ready + * flag is set separately when Linux maps the shared memory. */ + if (ipc_shm_is_ready()) { + /* Send a HELLO_ACK message via the shared memory ring */ + const char *msg = "HELLO from UTSM"; + ipc_shm_send(UTSM_MSG_HELLO_ACK, msg, 15); + } + return UTSM_HCALL_OK; +} + +static long hcall_shm_info(u64 a0, u64 a1, u64 a2) { + /* a0 = GPA of gpa_out (u64), a1 = GPA of size_out (u64) */ + (void)a2; + if (!ipc_shm_is_ready()) { + return UTSM_HCALL_NOMEM; + } + + u64 shm_gpa = ipc_shm_get_gpa(); + u64 shm_size = ipc_shm_get_size(); + + /* Write results to guest memory */ + if (a0 != 0 && guest_write_u64(a0, shm_gpa) != 0) { + log_error("[HCALL] SHM_INFO: failed to write gpa to guest"); + return UTSM_HCALL_INVAL; + } + if (a1 != 0 && guest_write_u64(a1, shm_size) != 0) { + log_error("[HCALL] SHM_INFO: failed to write size to guest"); + return UTSM_HCALL_INVAL; + } + + log_hex64("[HCALL] SHM_INFO: gpa=", shm_gpa); + log_hex64("[HCALL] SHM_INFO: size=", shm_size); + return UTSM_HCALL_OK; +} + +static long hcall_cap_validate(u64 a0, u64 a1, u64 a2) { + /* Phase 1.3: stub — capability validation not yet implemented */ + (void)a0; (void)a1; (void)a2; + log_info("[HCALL] CAP_VALIDATE (stub)"); + return UTSM_HCALL_NOSYS; +} + +static long hcall_utrw_read(u64 a0, u64 a1, u64 a2) { + /* Phase 1.3: stub — sealed memory read not yet implemented */ + (void)a0; (void)a1; (void)a2; + log_info("[HCALL] UTRW_READ (stub)"); + return UTSM_HCALL_NOSYS; +} + +static long hcall_utrw_write(u64 a0, u64 a1, u64 a2) { + /* Phase 1.3: stub — sealed memory write not yet implemented */ + (void)a0; (void)a1; (void)a2; + log_info("[HCALL] UTRW_WRITE (stub)"); + return UTSM_HCALL_NOSYS; +} + +static long hcall_drr_checkpoint(u64 a0, u64 a1, u64 a2) { + /* Phase 1.3: stub — DRR checkpoint trigger not yet implemented */ + (void)a0; (void)a1; (void)a2; + log_info("[HCALL] DRR_CHECKPOINT (stub)"); + return UTSM_HCALL_NOSYS; +} + +static long hcall_console_write(u64 a0, u64 a1, u64 a2) { + /* a0 = GPA of string buffer, a1 = length */ + (void)a2; + if (a0 == 0 || a1 == 0) { + return UTSM_HCALL_INVAL; + } + + /* Cap length to prevent runaway reads */ + if (a1 > 4096) a1 = 4096; + + /* Read the string from guest memory */ + char buf[4097]; + if (guest_read_buf(a0, buf, a1) != 0) { + log_error("[HCALL] CONSOLE_WRITE: failed to read guest buffer"); + return UTSM_HCALL_INVAL; + } + buf[a1] = '\0'; + + /* Output to UTSM serial console with a [LINUX] prefix */ + serial_write("[LINUX] "); + serial_write(buf); + return (long)a1; +} + +static long hcall_console_read(u64 a0, u64 a1, u64 a2) { + /* Phase 1.3: stub — console read not yet implemented */ + (void)a0; (void)a1; (void)a2; + return UTSM_HCALL_NOSYS; +} + +/* ===== Main hypercall dispatcher ===== */ + +int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx, + u64 guest_rip, u64 instr_len) { + g_hypercall_count++; + + /* Validate magic: the high bits of RAX must be UTSM_HCALL_MAGIC */ + if ((guest_rax & ~0xFFFFULL) != UTSM_HCALL_MAGIC) { + log_hex64("[HCALL] bad magic: ", guest_rax); + /* Return error in guest RAX */ + g_guest_regs.rax = (u64)UTSM_HCALL_INVAL; + /* Still advance RIP to avoid infinite loop */ + vmx_vmcs_write(VMCS_GUEST_RIP, guest_rip + instr_len); + return 1; /* resume guest */ + } + + /* Extract operation from low 16 bits */ + u64 op = guest_rax & 0xFFFFULL; + long result = UTSM_HCALL_NOSYS; + + switch (op) { + case UTSM_HCALL_PING: + result = hcall_ping(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_HELLO: + result = hcall_hello(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_SHM_INFO: + result = hcall_shm_info(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_CAP_VALIDATE: + result = hcall_cap_validate(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_UTRW_READ: + result = hcall_utrw_read(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_UTRW_WRITE: + result = hcall_utrw_write(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_DRR_CHECKPOINT: + result = hcall_drr_checkpoint(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_CONSOLE_WRITE: + result = hcall_console_write(guest_rdi, guest_rsi, guest_rdx); + break; + case UTSM_HCALL_CONSOLE_READ: + result = hcall_console_read(guest_rdi, guest_rsi, guest_rdx); + break; + default: + log_hex64("[HCALL] unknown op: ", op); + result = UTSM_HCALL_NOSYS; + break; + } + + /* Write return value into guest RAX (restored by assembly before vmresume) */ + g_guest_regs.rax = (u64)result; + + /* Advance guest RIP past the VMCALL instruction */ + vmx_vmcs_write(VMCS_GUEST_RIP, guest_rip + instr_len); + + return 1; /* resume guest */ +} + +u64 hypercall_get_count(void) { + return g_hypercall_count; +} diff --git a/CODE/UTSM/vmm/ipc_shm.c b/CODE/UTSM/vmm/ipc_shm.c new file mode 100644 index 0000000..2e90381 --- /dev/null +++ b/CODE/UTSM/vmm/ipc_shm.c @@ -0,0 +1,208 @@ +/* ipc_shm.c — UTSM-side IPC shared memory manager. + * + * Allocates a 1MB contiguous physical region, initializes the shared + * utsm_ipc_shm structure (header + two ring buffers + payload pool), + * and EPT-maps it at LINUX_GUEST_IPC_SHM_GPA (0x04000000) so the Linux + * guest can access it after querying the GPA via UTSM_HCALL_SHM_INFO. + * + * Ring buffer discipline (single-producer, single-consumer): + * utsm_to_linux: UTSM produces via ipc_shm_send, Linux consumes + * linux_to_utsm: Linux produces, UTSM consumes via ipc_shm_recv + * + * Memory barriers ensure correct visibility across the two kernels + * (which share the same physical memory via EPT). + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../arch/x86_64/limine.h" + +extern volatile struct limine_hhdm_request g_hhdm_request; + +/* ===== Internal state ===== */ + +static dkm_dma_buffer g_shm_buf; /* allocated DMA buffer for the SHM region */ +static struct utsm_ipc_shm *g_shm; /* virtual address (HHDM) of the SHM region */ +static int g_shm_ready; +static u32 g_send_seq; /* monotonic sequence for UTSM→Linux messages */ +static u64 g_send_count; +static u64 g_recv_count; + +/* ===== Initialization ===== */ + +int ipc_shm_init(void) { + if (g_shm_ready) { + log_warn("[IPC] shm already initialized"); + return 0; + } + + log_info("[IPC] shm init begin"); + + /* 1. Allocate 1MB contiguous physical memory for the shared region. + * Alignment: 4KB (page-aligned). Max phys: none (EPT maps any HPA). */ + u64 page_count = UTSM_IPC_SHM_SIZE / 4096; + if (dma_alloc_pages(page_count, 4096, 0, &g_shm_buf) != 0) { + log_error("[IPC] failed to alloc shared memory (1MB)"); + return -1; + } + log_hex64("[IPC] shm HPA=", g_shm_buf.phys); + log_hex64("[IPC] shm virt=", (u64)g_shm_buf.virt); + log_hex64("[IPC] shm size=", g_shm_buf.size); + + /* 2. Zero the entire region */ + u8 *p = (u8 *)g_shm_buf.virt; + for (u64 i = 0; i < g_shm_buf.size; i++) { + p[i] = 0; + } + + /* 3. Initialize the shared memory structure */ + g_shm = (struct utsm_ipc_shm *)g_shm_buf.virt; + + /* Header */ + g_shm->header.magic = UTSM_IPC_MAGIC; + g_shm->header.version = UTSM_IPC_VERSION; + g_shm->header.header_size = sizeof(struct utsm_ipc_shm_header); + g_shm->header.shm_phys = LINUX_GUEST_IPC_SHM_GPA; /* GPA as seen by Linux */ + g_shm->header.shm_size = UTSM_IPC_SHM_SIZE; + g_shm->header.utsm_ready = 1; /* UTSM is ready now */ + g_shm->header.linux_ready = 0; /* Linux will set this when it connects */ + + /* Ring buffers */ + utsm_ipc_ring_init(&g_shm->utsm_to_linux); + utsm_ipc_ring_init(&g_shm->linux_to_utsm); + + /* Memory barrier: ensure all writes are visible before EPT mapping */ + utsm_ipc_mb(); + + /* 4. EPT-map the shared memory at the guest-visible GPA. + * Both kernels need read/write access. Execute is not needed. */ + if (ept_map_range(LINUX_GUEST_IPC_SHM_GPA, g_shm_buf.phys, + UTSM_IPC_SHM_SIZE, EPT_READ | EPT_WRITE) != 0) { + log_error("[IPC] EPT map shared memory failed"); + return -2; + } + log_hex64("[IPC] shm mapped GPA=", LINUX_GUEST_IPC_SHM_GPA); + + g_shm_ready = 1; + g_send_seq = 0; + g_send_count = 0; + g_recv_count = 0; + + log_info("[IPC] shm init ok"); + return 0; +} + +void ipc_shm_shutdown(void) { + if (!g_shm_ready) return; + g_shm->header.utsm_ready = 0; + utsm_ipc_mb(); + g_shm_ready = 0; + log_info("[IPC] shm shutdown"); +} + +int ipc_shm_is_ready(void) { + return g_shm_ready; +} + +u64 ipc_shm_get_hpa(void) { + return g_shm_ready ? g_shm_buf.phys : 0; +} + +u64 ipc_shm_get_gpa(void) { + return LINUX_GUEST_IPC_SHM_GPA; +} + +u64 ipc_shm_get_size(void) { + return UTSM_IPC_SHM_SIZE; +} + +/* ===== Message API (UTSM-side) ===== */ + +int ipc_shm_send(u32 msg_type, const void *data, u32 data_len) { + if (!g_shm_ready || !g_shm) { + return -1; + } + + /* Check if Linux is ready to receive */ + if (!g_shm->header.linux_ready) { + log_warn("[IPC] send: Linux not ready, dropping message"); + return -1; + } + + /* Cap data length to inline buffer size */ + if (data_len > UTSM_IPC_MSG_DATA_SIZE) { + data_len = UTSM_IPC_MSG_DATA_SIZE; + } + + /* Build the message */ + struct utsm_ipc_msg msg; + msg.type = msg_type; + msg.seq = g_send_seq++; + msg.data_len = data_len; + msg.flags = 0; + + /* Copy data if present */ + if (data && data_len > 0) { + const u8 *src = (const u8 *)data; + for (u32 i = 0; i < data_len; i++) { + msg.data[i] = src[i]; + } + } + + /* Enqueue into utsm_to_linux ring */ + if (utsm_ipc_ring_push(&g_shm->utsm_to_linux, &msg) != 0) { + log_warn("[IPC] send: ring full, dropping message"); + return -1; + } + + g_send_count++; + return 0; +} + +int ipc_shm_recv(struct utsm_ipc_msg *msg_out) { + if (!g_shm_ready || !g_shm || !msg_out) { + return -1; + } + + if (utsm_ipc_ring_pop(&g_shm->linux_to_utsm, msg_out) != 0) { + return -1; /* empty */ + } + + g_recv_count++; + return 0; +} + +int ipc_shm_has_pending(void) { + if (!g_shm_ready || !g_shm) return 0; + return !utsm_ipc_ring_empty(&g_shm->linux_to_utsm); +} + +void ipc_shm_notify_linux(void) { + /* Phase 1.3: log only. + * Phase 1.4+: inject a guest IRQ (via VMCS VM-entry interruption info) + * to notify Linux that a message is waiting in the utsm_to_linux ring. */ + log_info("[IPC] notify_linux (IPI injection not yet implemented)"); +} + +/* ===== Debug ===== */ + +void ipc_shm_dump_stats(void) { + if (!g_shm_ready) { + log_info("[IPC] not initialized"); + return; + } + + log_hex64("[IPC] utsm_ready=", g_shm->header.utsm_ready); + log_hex64("[IPC] linux_ready=", g_shm->header.linux_ready); + log_hex64("[IPC] utsm_to_linux head=", g_shm->utsm_to_linux.head); + log_hex64("[IPC] utsm_to_linux tail=", g_shm->utsm_to_linux.tail); + log_hex64("[IPC] linux_to_utsm head=", g_shm->linux_to_utsm.head); + log_hex64("[IPC] linux_to_utsm tail=", g_shm->linux_to_utsm.tail); + log_hex64("[IPC] send_count=", g_send_count); + log_hex64("[IPC] recv_count=", g_recv_count); +} diff --git a/CODE/UTSM/vmm/linux_boot.c b/CODE/UTSM/vmm/linux_boot.c new file mode 100644 index 0000000..b880620 --- /dev/null +++ b/CODE/UTSM/vmm/linux_boot.c @@ -0,0 +1,343 @@ +/* linux_boot.c — VMCS configuration and vmlaunch for Linux 6.6 guest. + * + * Configures the VMCS guest state for 64-bit Linux entry (startup_64): + * - RIP = kernel 64-bit entry point + * - RSI = boot_params GPA (Linux calling convention) + * - RSP = guest stack top + * - CR3 = guest page tables GPA (identity-mapped 4GB) + * - GDT = guest GDT GPA (64-bit code/data/TSS) + * - EFER.LME | EFER.LMA | EFER.NXE + * + * After vmlaunch, the VM-Exit handler (vmexit.c) processes exits and + * resumes the guest. + */ + +#include +#include +#include +#include +#include +#include +#include "../arch/x86_64/limine.h" + +extern volatile struct limine_hhdm_request g_hhdm_request; + +/* VM-Exit assembly entry and C dispatcher (from vmm.c / vmexit_asm.S) */ +extern void vmx_vm_exit_handler(void); +extern int vmexit_dispatch(void); +extern u64 vmexit_get_count(void); + +/* Globals shared with vmexit_asm.S (must match vmm.c) */ +extern volatile u64 g_saved_host_rsp; +extern volatile u64 g_saved_return_rip; +extern volatile int g_guest_terminated; +extern volatile u64 g_last_exit_reason; + +/* Host stack for VM-Exit (reuse the one from vmm.c) */ +extern u8 g_host_stack[8192]; + +/* CR helpers */ +static inline u64 read_cr0_local(void) { + u64 v; + __asm__ volatile("mov %%cr0, %0" : "=r"(v)); + return v; +} +static inline u64 read_cr3_local(void) { + u64 v; + __asm__ volatile("mov %%cr3, %0" : "=r"(v)); + return v; +} +static inline u64 read_cr4_local(void) { + u64 v; + __asm__ volatile("mov %%cr4, %0" : "=r"(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) */ +static u8 g_linux_msr_bitmap[4096] __attribute__((aligned(4096))); + +/* ===== VMCS host state (same as vmm.c, but for Linux guest) ===== */ + +static void linux_vmcs_setup_host_state(void) { + vmx_vmcs_write(VMCS_HOST_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG); + vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local()); + vmx_vmcs_write(VMCS_HOST_CR4, read_cr4_local() | CR4_VMXE); + + /* Host segment selectors: UTSM GDT 0x08 (code) / 0x10 (data) */ + vmx_vmcs_write(VMCS_HOST_CS_SELECTOR, 0x08); + vmx_vmcs_write(VMCS_HOST_SS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_HOST_DS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_HOST_ES_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_HOST_FS_SELECTOR, 0); + vmx_vmcs_write(VMCS_HOST_GS_SELECTOR, 0); + vmx_vmcs_write(VMCS_HOST_FS_BASE, 0); + vmx_vmcs_write(VMCS_HOST_GS_BASE, 0); + + /* Host GDT/IDT + TR */ + { + struct __attribute__((packed)) { u16 limit; u64 base; } gdtr, idtr; + __asm__ volatile("sgdt %0" : "=m"(gdtr)); + __asm__ volatile("sidt %0" : "=m"(idtr)); + vmx_vmcs_write(VMCS_HOST_GDTR_BASE, gdtr.base); + vmx_vmcs_write(VMCS_HOST_IDTR_BASE, idtr.base); + + u16 tr_sel; + u64 tr_base; + __asm__ volatile("str %0" : "=r"(tr_sel)); + u64 gdt_base = gdtr.base; + u64 *tss_desc = (u64 *)(gdt_base + (tr_sel & 0xFFF8)); + u64 low = tss_desc[0]; + u64 high = tss_desc[1]; + tr_base = (low >> 16) & 0xFFFFFF; + tr_base |= ((low >> 32) & 0xFF) << 24; + tr_base |= (high & 0xFFFFFFFF) << 32; + vmx_vmcs_write(VMCS_HOST_TR_SELECTOR, tr_sel); + vmx_vmcs_write(VMCS_HOST_TR_BASE, tr_base); + } + + /* Host SYSENTER */ + { + u64 sysenter_cs; + __asm__ volatile("rdmsr" : "=a"(sysenter_cs) : "c"(0x174)); + vmx_vmcs_write(VMCS_HOST_SYSENTER_CS, sysenter_cs & 0xFFFFFFFF); + vmx_vmcs_write(VMCS_HOST_SYSENTER_ESP, 0); + vmx_vmcs_write(VMCS_HOST_SYSENTER_EIP, 0); + } + + /* Host EFER */ + { + u32 low, high; + __asm__ volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(0xC0000080)); + u64 host_efer = ((u64)high << 32) | low; + vmx_vmcs_write(VMCS_HOST_IA32_EFER, host_efer); + } + + /* Host RSP/RIP: VM-Exit handler */ + vmx_vmcs_write(VMCS_HOST_RSP, (u64)&g_host_stack[sizeof(g_host_stack) - 16]); + vmx_vmcs_write(VMCS_HOST_RIP, (u64)vmx_vm_exit_handler); +} + +/* ===== VMCS guest state for Linux ===== */ + +static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) { + /* CR0/CR3/CR4: long mode with paging */ + vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP); + /* CR3 points to guest PML4 (GPA) */ + vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_gpa); + vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE); + + /* Segment selectors from guest GDT */ + vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08); + vmx_vmcs_write(VMCS_GUEST_SS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_GUEST_DS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_GUEST_ES_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_GUEST_FS_SELECTOR, 0); + vmx_vmcs_write(VMCS_GUEST_GS_SELECTOR, 0); + vmx_vmcs_write(VMCS_GUEST_LDTR_SELECTOR, 0); + vmx_vmcs_write(VMCS_GUEST_TR_SELECTOR, 0x18); + + /* Segment bases: flat model, TR points to TSS in GDT page */ + vmx_vmcs_write(VMCS_GUEST_CS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_SS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_DS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_ES_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_FS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_GS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_LDTR_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_TR_BASE, gi->gdt_gpa + 0x100); + vmx_vmcs_write(VMCS_GUEST_GDTR_BASE, gi->gdt_gpa); + vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0); + + /* Segment limits */ + vmx_vmcs_write(VMCS_GUEST_CS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_SS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_DS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_ES_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_FS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_GS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_LDTR_LIMIT, 0xFFFF); + vmx_vmcs_write(VMCS_GUEST_TR_LIMIT, 0x67); + vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28); + vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF); + + /* Access rights */ + vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); /* 64-bit code, L=1 */ + vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82); /* unusable */ + vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */ + + /* Guest RIP = Linux 64-bit entry (startup_64) */ + vmx_vmcs_write(VMCS_GUEST_RIP, gi->kernel_entry); + + /* Guest RSP = top of stack page */ + vmx_vmcs_write(VMCS_GUEST_RSP, LINUX_GUEST_STACK_GPA + 0x1000); + + /* NOTE: RSI (boot_params address) is NOT a VMCS field. + * VMX does not save/restore GPRs (RAX-R15) in VMCS. + * RSI is set via the inline asm launch stub in linux_launch(). */ + + vmx_vmcs_write(VMCS_GUEST_RFLAGS, 0x2); /* interrupts disabled */ + + /* Guest EFER: long mode active */ + vmx_vmcs_write(VMCS_GUEST_IA32_EFER, EFER_LME | EFER_LMA | EFER_NXE); + + /* Guest SYSENTER (unused but required by VMX) */ + vmx_vmcs_write(VMCS_GUEST_SYSENTER_CS, 0); + vmx_vmcs_write(VMCS_GUEST_SYSENTER_ESP, 0); + vmx_vmcs_write(VMCS_GUEST_SYSENTER_EIP, 0); + + /* Guest DR7, activity state, interruptibility */ + vmx_vmcs_write(VMCS_GUEST_DR7, 0x400); + vmx_vmcs_write(VMCS_GUEST_ACTIVITY_STATE, 0); + vmx_vmcs_write(VMCS_GUEST_INTERRUPTIBILITY, 0); +} + +/* ===== VMCS controls for Linux ===== */ + +static void linux_vmcs_setup_controls(u64 eptp) { + u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING, + IA32_VMX_TRUE_PINBASED_CTLS); + + /* CPU-based: exit on HLT, INVLPG, MWAIT, IO, use MSR bitmap, + * activate secondary controls. Don't intercept RDTSC (Linux needs it). */ + u64 cpu = CPU_BASED_HLT_EXITING + | CPU_BASED_ACTIVATE_SECONDARY + | CPU_BASED_USE_MSR_BITMAPS + | CPU_BASED_UNCOND_IO_EXITING + | CPU_BASED_INVLPG_EXITING; + cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); + + /* Secondary: EPT + VPID */ + u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID; + { + u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2); + u32 allowed1 = (u32)(msr >> 32); + cpu2 &= allowed1; + cpu2 |= SEC_EXEC_ENABLE_EPT; + } + + u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS + | VM_EXIT_HOST_ADDR_SPACE_SIZE + | VM_EXIT_LOAD_HOST_EFER; + exit_ctrl = vmx_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS); + + u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS + | VM_ENTRY_IA32E_MODE_GUEST + | VM_ENTRY_LOAD_GUEST_EFER; + entry_ctrl = vmx_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS); + + vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin); + vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu); + vmx_vmcs_write(VMCS_SECONDARY_VM_EXEC_CONTROL, cpu2); + vmx_vmcs_write(VMCS_VM_EXIT_CONTROLS, exit_ctrl); + vmx_vmcs_write(VMCS_VM_ENTRY_CONTROLS, entry_ctrl); + + /* MSR bitmap */ + { + u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; + u64 msr_bitmap_phys = (u64)g_linux_msr_bitmap - hhdm; + vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys); + } + + vmx_vmcs_write(VMCS_EPT_POINTER, eptp); + vmx_vmcs_write(VMCS_VPID, 2); /* VPID 1 = self-test, VPID 2 = Linux */ + + /* Exception bitmap: catch #GP, #PF, #UD, #DF (triple fault handled separately) */ + vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6) | (1ULL << 8)); +} + +/* ===== Global for passing RSI to launch asm ===== */ +static volatile u64 g_linux_rsi; + +/* ===== Linux launch ===== + * + * This function is called after vmm_init() + linux_loader_init(). + * It configures the VMCS for the Linux guest and performs vmlaunch. + * + * NOTE: VMX general registers (RAX-R15) are NOT saved in VMCS. + * We use inline assembly to set RSI = boot_params GPA before vmlaunch. + * + * Returns 0 on successful guest termination, negative on error. + */ +int linux_launch(void) { + const struct linux_guest_info *gi = linux_get_guest_info(); + if (!gi || !gi->loaded) { + log_error("[LINUX] guest not loaded"); + return -1; + } + + if (!vmm_is_ready()) { + log_error("[LINUX] VMM not ready"); + return -2; + } + + log_info("[LINUX] launch begin"); + + /* Reset VM-Exit state */ + g_guest_terminated = 0; + g_last_exit_reason = 0xFFFFFFFFULL; + + /* Configure VMCS */ + linux_vmcs_setup_guest_state(gi); + linux_vmcs_setup_controls(vmm_get_eptp()); + linux_vmcs_setup_host_state(); + + /* Set RSI for Linux boot_params (passed via global, loaded in asm) */ + g_linux_rsi = gi->bootparams_gpa; + + log_hex64("[LINUX] RIP=", gi->kernel_entry); + log_hex64("[LINUX] RSP=", LINUX_GUEST_STACK_GPA + 0x1000); + log_hex64("[LINUX] RSI=", gi->bootparams_gpa); + log_hex64("[LINUX] CR3=", gi->pgt_gpa); + log_hex64("[LINUX] GDTR=", gi->gdt_gpa); + log_info("[LINUX] vmlaunch"); + + /* vmlaunch with RSI = boot_params. + * Same control flow pattern as vmm_self_test: + * - On failure: failed=1, falls through + * - On success: enters guest, VM-Exit → vmx_vm_exit_handler + * - On terminate: handler restores RSP, jmps to post_guest label */ + int failed; + __asm__ volatile( + "movq %%rsp, g_saved_host_rsp(%%rip)\n\t" + "leaq 1f(%%rip), %%rax\n\t" + "movq %%rax, g_saved_return_rip(%%rip)\n\t" + "movq g_linux_rsi(%%rip), %%rsi\n\t" /* RSI = boot_params GPA */ + "vmlaunch\n\t" + /* ---- failure path ---- */ + "movl $1, %0\n\t" + "jmp 2f\n\t" + /* ---- post_guest: terminate path jmps here ---- */ + "1:\n\t" + "movl $0, %0\n\t" + "2:\n\t" + : "=r"(failed) + :: "rax", "rcx", "rdx", "rsi", "rdi", + "r8", "r9", "r10", "r11", "memory" + ); + + if (failed) { + u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR); + log_hex64("[LINUX] vmlaunch failed, error=", error); + return -3; + } + + /* Guest terminated */ + log_info("[LINUX] guest terminated"); + log_hex64("[LINUX] last exit reason=", g_last_exit_reason); + log_hex64("[LINUX] vmexit count=", vmexit_get_count()); + + return 0; +} diff --git a/CODE/UTSM/vmm/linux_loader.c b/CODE/UTSM/vmm/linux_loader.c new file mode 100644 index 0000000..bb45b01 --- /dev/null +++ b/CODE/UTSM/vmm/linux_loader.c @@ -0,0 +1,608 @@ +/* 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; + +/* ===== 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-0x0FFFFFFF: usable RAM (general RAM, ~188MB) */ + table[n].addr = 0x04100000; + table[n].size = 0x0BF00000; /* 191MB */ + 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; +} + +/* ===== 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: 64MB for Linux, mapped at GPA 0x05000000 (after IPC region) */ + u64 ram_size = 64 * 1024 * 1024; + dkm_dma_buffer ram_buf; + if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) { + log_error("[LINUX] failed to alloc guest RAM (64MB)"); + /* Try smaller: 32MB */ + ram_size = 32 * 1024 * 1024; + if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) { + log_error("[LINUX] failed to alloc guest RAM (32MB)"); + 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; + + /* For now, no initrd (Phase 1.3+ will add it) */ + g_guest.initrd_gpa = 0; + g_guest.initrd_size = 0; + + 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 IPC region). + * GPA layout (matches e820): + * 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) + * 0x05000000-0x08FFFFFF: general RAM (64MB) + * 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); + + /* 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; +} diff --git a/CODE/UTSM/vmm/vmexit.c b/CODE/UTSM/vmm/vmexit.c new file mode 100644 index 0000000..176bf5f --- /dev/null +++ b/CODE/UTSM/vmm/vmexit.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include + +/* VM-Exit 处理器:从 VMCS 读取 exit reason 与 guest 状态,分发处理。 + * + * Phase 1.1:处理 HLT(终止 guest)、EPT violation(动态映射)、异常。 + * Phase 1.3:增加 VMCALL hypercall 处理(读 guest GPR、分发、写返回值)。 + */ + +extern void vmx_vmresume_wrapper(void); + +static u64 g_vmexit_count; + +static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len) { + log_hex64("[VMEXIT] reason=", reason); + log_hex64("[VMEXIT] qual=", qualification); + log_hex64("[VMEXIT] rip=", rip); + log_hex64("[VMEXIT] ilen=", len); +} + +/* 处理 HLT:Phase 1.1 自检场景下表示 guest 完成测试,停止 guest。 */ +static int handle_hlt(u64 rip, u64 instr_len, int *out_resume) { + *out_resume = 0; /* 不 resume,guest 终止 */ + log_info("[VMEXIT] HLT - guest halted"); + (void)rip; (void)instr_len; + return 0; +} + +/* 处理 EPT violation:动态映射缺失的 GPA。 + * Phase 1.1:1:1 映射缺失的页面,权限 RWX。 */ +static int handle_ept_violation(u64 qualification, int *out_resume) { + u64 gpa = vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR); + u64 page = gpa & ~(EPT_PAGE_SIZE - 1); + log_hex64("[VMEXIT] EPT violation gpa=", gpa); + (void)qualification; + /* 1:1 映射该页(假设 HPA=GPA) */ + if (ept_identity_map(page, EPT_PAGE_SIZE, EPT_RWX) != 0) { + log_error("[VMEXIT] failed to map EPT page"); + *out_resume = 0; + return -1; + } + *out_resume = 1; + return 0; +} + +/* 处理通用异常:打印诊断信息,终止 guest。 */ +static int handle_exception(u64 qualification, u64 rip, int *out_resume) { + u64 intr_info = vmx_vmcs_read(0x4404); /* VM_EXIT_INTR_INFO */ + log_hex64("[VMEXIT] exception intr_info=", intr_info); + log_hex64("[VMEXIT] exception qual=", qualification); + log_hex64("[VMEXIT] exception rip=", rip); + *out_resume = 0; + return 0; +} + +/* VM-Exit 主分发器。从 vmx_vm_exit_handler(汇编入口)调用。 + * 返回 1 表示 vmresume;0 表示终止 guest。 */ +int vmexit_dispatch(void) { + u64 reason = vmx_vmcs_read(VMCS_EXIT_REASON) & 0xFFFF; + u64 qualification = vmx_vmcs_read(VMCS_EXIT_QUALIFICATION); + u64 rip = vmx_vmcs_read(VMCS_GUEST_RIP); + u64 instr_len = vmx_vmcs_read(VMCS_INSTRUCTION_LENGTH); + int resume = 0; + + g_vmexit_count++; + + switch (reason) { + case EXIT_HLT: + handle_hlt(rip, instr_len, &resume); + break; + case EXIT_EPT_VIOLATION: + handle_ept_violation(qualification, &resume); + break; + case EXIT_EXCEPTION_NMI: + handle_exception(qualification, rip, &resume); + break; + case EXIT_TRIPLE_FAULT: + log_error("[VMEXIT] triple fault"); + resume = 0; + break; + case EXIT_CPUID: + /* 简化:让 guest 通过 CPUID,前进 RIP。Phase 1.2+ 完整模拟。 */ + log_info("[VMEXIT] CPUID - skip"); + vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len); + resume = 1; + break; + case EXIT_IO_INSTRUCTION: + /* 简化:跳过 IO 指令。Phase 1.4 virtio-console 完整处理。 */ + vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len); + resume = 1; + break; + case EXIT_RDMSR: + case EXIT_WRMSR: + vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len); + resume = 1; + break; + case EXIT_VMCALL: + /* VMCALL hypercall: read guest GPRs from g_guest_regs (saved by + * vmexit_asm.S), dispatch to the hypercall handler, and write + * the return value into g_guest_regs.rax. The handler also + * advances GUEST_RIP past the VMCALL instruction. */ + { + int hcall_resume = hypercall_handle( + g_guest_regs.rax, g_guest_regs.rdi, + g_guest_regs.rsi, g_guest_regs.rdx, + rip, instr_len); + resume = hcall_resume; + } + break; + default: + log_exit_diagnostics(reason, qualification, rip, instr_len); + resume = 0; + break; + } + + (void)qualification; + return resume; +} + +u64 vmexit_get_count(void) { return g_vmexit_count; } diff --git a/CODE/UTSM/vmm/vmexit_asm.S b/CODE/UTSM/vmm/vmexit_asm.S new file mode 100644 index 0000000..415037c --- /dev/null +++ b/CODE/UTSM/vmm/vmexit_asm.S @@ -0,0 +1,90 @@ +/* VM-Exit assembly entry. + * + * On VM-Exit, CPU loads host state from VMCS: + * - host CR0/CR3/CR4, segment registers + * - host RSP = VMCS_HOST_RSP (top of g_host_stack) + * - host RIP = VMCS_HOST_RIP (this function) + * + * VMCS does NOT save guest GPRs (RAX-R15). They remain in the CPU registers + * and must be saved explicitly if the C dispatcher wants to read or modify + * them (e.g. for VMCALL hypercall arguments and return values). + * + * This function: + * 1. Saves all 16 guest GPRs into g_guest_regs + * 2. Calls vmm_vmexit_entry() (C dispatcher, returns void) + * - Internally calls vmexit_dispatch(); sets g_guest_terminated if !resume + * 3. Restores all 16 guest GPRs from g_guest_regs (C may have modified rax) + * 4. Checks g_guest_terminated: + * - 0: executes vmresume (GPRs = guest values, possibly modified by C) + * If vmresume fails, falls through to terminate + * - 1: restores saved host RSP, jmps to saved return RIP + * + * The saved RSP/RIP point into the vmlaunch inline asm block (in vmm_self_test + * or linux_launch). After the jmp, C flow continues with failed=0. + */ + +.section .text +.global vmx_vm_exit_handler +vmx_vm_exit_handler: + /* Step 1: Save all 16 guest GPRs to g_guest_regs. + * GPRs at this point hold guest values (VMCS doesn't save them). */ + movq %rax, g_guest_regs + 0(%rip) + movq %rbx, g_guest_regs + 8(%rip) + movq %rcx, g_guest_regs + 16(%rip) + movq %rdx, g_guest_regs + 24(%rip) + movq %rsi, g_guest_regs + 32(%rip) + movq %rdi, g_guest_regs + 40(%rip) + movq %rbp, g_guest_regs + 48(%rip) + movq %r8, g_guest_regs + 56(%rip) + movq %r9, g_guest_regs + 64(%rip) + movq %r10, g_guest_regs + 72(%rip) + movq %r11, g_guest_regs + 80(%rip) + movq %r12, g_guest_regs + 88(%rip) + movq %r13, g_guest_regs + 96(%rip) + movq %r14, g_guest_regs + 104(%rip) + movq %r15, g_guest_regs + 112(%rip) + + /* Step 2: Align stack to 16 bytes and call C dispatcher. + * The host RSP (from VMCS_HOST_RSP) is 16-byte aligned minus 8 + * (for the implicit return address), so we sub 8 for alignment. */ + subq $8, %rsp + call vmm_vmexit_entry + addq $8, %rsp + + /* Step 3: Restore all 16 guest GPRs from g_guest_regs. + * C handlers may have modified fields (e.g. rax = hypercall return value). */ + movq g_guest_regs + 0(%rip), %rax + movq g_guest_regs + 8(%rip), %rbx + movq g_guest_regs + 16(%rip), %rcx + movq g_guest_regs + 24(%rip), %rdx + movq g_guest_regs + 32(%rip), %rsi + movq g_guest_regs + 40(%rip), %rdi + movq g_guest_regs + 48(%rip), %rbp + movq g_guest_regs + 56(%rip), %r8 + movq g_guest_regs + 64(%rip), %r9 + movq g_guest_regs + 72(%rip), %r10 + movq g_guest_regs + 80(%rip), %r11 + movq g_guest_regs + 88(%rip), %r12 + movq g_guest_regs + 96(%rip), %r13 + movq g_guest_regs + 104(%rip), %r14 + movq g_guest_regs + 112(%rip), %r15 + + /* Step 4: Check terminate flag */ + movl g_guest_terminated(%rip), %eax + testl %eax, %eax + jnz .Lterminate + + /* Resume guest: GPRs are now restored (with any C modifications). + * vmresume does not return on success — CPU enters guest. */ + vmresume + /* If we reach here, vmresume failed (CF=1 or ZF=1). */ + movl $1, g_guest_terminated(%rip) + /* Fall through to terminate */ + +.Lterminate: + /* Terminate: restore saved RSP and jmp to saved return RIP. + * The saved RSP is the caller's stack pointer before vmlaunch. + * The saved RIP is the "1:" label in the vmlaunch inline asm, + * which sets failed=0 and continues C flow. */ + movq g_saved_host_rsp(%rip), %rsp + jmpq *g_saved_return_rip(%rip) diff --git a/CODE/UTSM/vmm/vmm.c b/CODE/UTSM/vmm/vmm.c new file mode 100644 index 0000000..b973286 --- /dev/null +++ b/CODE/UTSM/vmm/vmm.c @@ -0,0 +1,523 @@ +#include +#include +#include +#include +#include +#include +#include "../arch/x86_64/limine.h" + +extern volatile struct limine_hhdm_request g_hhdm_request; + +/* ===== VMX 指令错误码 ===== */ +static const char *vmx_error_str(u64 err) { + switch (err) { + case 1: return "VMCALL/VMCLEAR/VMLAUNCH/VMRESUME with non-launched VMCS"; + case 2: return "VMRESUME after VMXOFF"; + case 3: return "VMRESUME with corrupted VMCS"; + case 4: return "vmlaunch with non-clear VMCS"; + case 5: return "VM operation invalid"; + case 6: return "VMCS revision mismatch"; + case 7: return "VMCS shadowing mismatch"; + case 8: return "invalid guest state"; + case 9: return "host state invalid"; + case 10: return "VM-execution control invalid"; + case 11: return "VM-exit control invalid"; + case 12: return "VM-entry control invalid"; + case 13: return "VM-exit control fields invalid"; + case 15: return "address out of width"; + case 16: return "MSR bitmap addr invalid"; + case 17: return "VMREAD/VMWRITE from unsupported field"; + case 18: return "VMCS addr invalid"; + default: return "unknown"; + } +} + +/* ===== VMM 全局状态 ===== */ +static int g_vmm_ready; +static u64 g_vmcs_phys; + +/* VM-Exit 后回到 host 的栈与 RIP 恢复点。 + * vmm_self_test 在 vmlaunch 前保存当前 RSP 与"launch 后"的 RIP, + * VM-Exit 处理器在决定终止 guest 时恢复 RSP 并 jmp 到该 RIP。 + * 注意:这些变量必须是非 static 的全局符号,因为 vmexit_asm.S 通过 + * RIP 相对寻址引用它们。 */ +volatile u64 g_saved_host_rsp; +volatile u64 g_saved_return_rip; +volatile int g_guest_terminated; +volatile u64 g_last_exit_reason; + +/* Guest GPR save area (referenced by vmexit_asm.S). + * VM-Exit 时 CPU 不自动保存 guest GPR,由汇编入口保存到此结构。 + * C 处理器可读写此结构(如 VMCALL 返回值写入 rax)。 */ +volatile struct vmexit_guest_regs g_guest_regs; + +/* host 栈:VM-Exit 时使用(HOST_RSP)。 + * 非static:linux_boot.c 共用同一个 host 栈。 */ +u8 g_host_stack[8192] __attribute__((aligned(16))); + +/* VM-Exit 汇编入口(vmexit_asm.S) */ +extern void vmx_vm_exit_handler(void); + +/* C 分发器(vmexit.c) */ +extern int vmexit_dispatch(void); +extern u64 vmexit_get_count(void); + +/* ===== Phase 1.1 自检 guest 代码 ===== + * guest 在 GPA 0x1000 运行,执行 nop;nop;nop;hlt。 + * HLT 触发 VMEXIT,dispatch 返回 0(终止)。 */ +static u8 g_guest_code[4096] __attribute__((aligned(4096))) = { + 0x90, 0x90, 0x90, 0xF4, /* nop; nop; nop; hlt */ +}; + +/* guest 栈页 */ +static u8 g_guest_stack[4096] __attribute__((aligned(4096))); + +/* MSR bitmap(4KB 全 0 = 不拦截 MSR) */ +static u8 g_msr_bitmap[4096] __attribute__((aligned(4096))); + +/* guest GDT(GPA 0x3000)与 TSS(GPA 0x4000)。 + * VMX 要求 guest TR 非空且引用 GDT 中的 TSS 描述符。 */ +static u8 g_guest_gdt[4096] __attribute__((aligned(4096))); +static u8 g_guest_tss[4096] __attribute__((aligned(4096))); + +/* 64-bit GDT entry */ +typedef struct __attribute__((packed)) { + u16 limit_low; + u16 base_low; + u8 base_mid; + u8 access; + u8 flags_limit_high; + u8 base_high; +} gdt_entry64; + +/* 64-bit TSS descriptor (16 bytes) */ +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 void build_guest_gdt(void) { + /* 清零 */ + for (int i = 0; i < 4096; i++) g_guest_gdt[i] = 0; + for (int i = 0; i < 4096; i++) g_guest_tss[i] = 0; + + gdt_entry64 *gdt = (gdt_entry64 *)g_guest_gdt; + /* [0x00] null */ + /* [0x08] code: base=0, limit=0xFFFFF, L=1, G=1, present, DPL0, type=0x9B */ + gdt[1].limit_low = 0xFFFF; + gdt[1].base_low = 0; + gdt[1].base_mid = 0; + gdt[1].access = 0x9B; /* present, DPL0, code, executed/read */ + gdt[1].flags_limit_high = 0xAF; /* G=1, L=1 (64-bit), limit[19:16]=0xF */ + gdt[1].base_high = 0; + /* [0x10] data: base=0, limit=0xFFFFF, G=1, present, DPL0, type=0x93 */ + 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] TSS descriptor (16 bytes, 64-bit TSS) */ + tss_desc64 *tss_d = (tss_desc64 *)&g_guest_gdt[0x18]; + u64 tss_base = 0x4000; /* TSS at GPA 0x4000 */ + u32 tss_limit = 0x67; /* 104-byte minimum 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 */ + tss_d->flags_limit_high = (u8)((tss_limit >> 16) & 0x0F) | 0x00; + tss_d->base_high = 0; + tss_d->base_upper = 0; + tss_d->reserved = 0; +} + +/* ===== CR 读取辅助 ===== */ +static inline u64 read_cr0_local(void) { + u64 v; + __asm__ volatile("mov %%cr0, %0" : "=r"(v)); + return v; +} +static inline u64 read_cr4_local(void) { + u64 v; + __asm__ volatile("mov %%cr4, %0" : "=r"(v)); + return v; +} +static inline u64 read_cr3_local(void) { + u64 v; + __asm__ volatile("mov %%cr3, %0" : "=r"(v)); + return v; +} + +/* ===== VMCS 配置 ===== */ + +static u64 vmx_adjust_control(u64 value, u32 msr) { + u32 allowed0 = (u32)vmx_read_msr(msr); + u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32); + u32 adjusted = (u32)value | allowed0; + adjusted &= allowed1; + return adjusted; +} + +static void vmcs_setup_host_state(void) { + vmx_vmcs_write(VMCS_HOST_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG); + vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local()); + vmx_vmcs_write(VMCS_HOST_CR4, read_cr4_local() | CR4_VMXE); + + /* 段选择子:UTSM GDT 0x08 (code) / 0x10 (data) */ + vmx_vmcs_write(VMCS_HOST_CS_SELECTOR, 0x08); + vmx_vmcs_write(VMCS_HOST_SS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_HOST_DS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_HOST_ES_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_HOST_FS_SELECTOR, 0); + vmx_vmcs_write(VMCS_HOST_GS_SELECTOR, 0); + + vmx_vmcs_write(VMCS_HOST_FS_BASE, 0); + vmx_vmcs_write(VMCS_HOST_GS_BASE, 0); + + /* Host GDT/IDT base + TR(VM-Exit 后 CPU 加载 host TR,必须有效) */ + { + struct __attribute__((packed)) { u16 limit; u64 base; } gdtr, idtr; + __asm__ volatile("sgdt %0" : "=m"(gdtr)); + __asm__ volatile("sidt %0" : "=m"(idtr)); + vmx_vmcs_write(VMCS_HOST_GDTR_BASE, gdtr.base); + vmx_vmcs_write(VMCS_HOST_IDTR_BASE, idtr.base); + + /* 读取当前 TR 寄存器(UTSM 已加载 TSS) */ + u16 tr_sel; + u64 tr_base; + __asm__ volatile("str %0" : "=r"(tr_sel)); + /* 从 GDT 中读取 TR base:TR 选择子指向的描述符 */ + u64 gdt_base = gdtr.base; + u64 *tss_desc = (u64 *)(gdt_base + (tr_sel & 0xFFF8)); + /* 64-bit TSS 描述符 16 字节:base 由 bits 拼接 */ + u64 low = tss_desc[0]; + u64 high = tss_desc[1]; + tr_base = (low >> 16) & 0xFFFFFF; /* base[15:0] + base[23:16] */ + tr_base |= ((low >> 32) & 0xFF) << 24; /* base[31:24] */ + tr_base |= (high & 0xFFFFFFFF) << 32; /* base[63:32] */ + vmx_vmcs_write(VMCS_HOST_TR_SELECTOR, tr_sel); + vmx_vmcs_write(VMCS_HOST_TR_BASE, tr_base); + } + + /* Host SYSENTER(VMX 要求字段有效) */ + { + u64 sysenter_cs; + __asm__ volatile("rdmsr" : "=a"(sysenter_cs) : "c"(0x174)); + vmx_vmcs_write(VMCS_HOST_SYSENTER_CS, sysenter_cs & 0xFFFFFFFF); + vmx_vmcs_write(VMCS_HOST_SYSENTER_ESP, 0); + vmx_vmcs_write(VMCS_HOST_SYSENTER_EIP, 0); + } + + /* Host EFER:VM_EXIT_LOAD_HOST_EFER 控制位要求 VMCS 中有 host EFER 值, + * VM-Exit 时加载。读取当前 host EFER MSR 填入。 */ + { + u32 low, high; + __asm__ volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(0xC0000080)); + u64 host_efer = ((u64)high << 32) | low; + vmx_vmcs_write(VMCS_HOST_IA32_EFER, host_efer); + } + + /* Host RSP/RIP:VM-Exit 后跳到 vmx_vm_exit_handler */ + vmx_vmcs_write(VMCS_HOST_RSP, (u64)&g_host_stack[sizeof(g_host_stack) - 16]); + vmx_vmcs_write(VMCS_HOST_RIP, (u64)vmx_vm_exit_handler); +} + +static void vmcs_setup_guest_state_phase1(void) { + /* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表 */ + vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP); + vmx_vmcs_write(VMCS_GUEST_CR3, read_cr3_local()); + vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE); + + /* 段选择子:flat 模型。 + * CS=0x08 (GDT[1] code), DS/SS/ES=0x10 (GDT[2] data), + * TR=0x18 (GDT[3] 64-bit TSS)。 */ + vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08); + vmx_vmcs_write(VMCS_GUEST_SS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_GUEST_DS_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_GUEST_ES_SELECTOR, 0x10); + vmx_vmcs_write(VMCS_GUEST_FS_SELECTOR, 0); + vmx_vmcs_write(VMCS_GUEST_GS_SELECTOR, 0); + vmx_vmcs_write(VMCS_GUEST_LDTR_SELECTOR, 0); + vmx_vmcs_write(VMCS_GUEST_TR_SELECTOR, 0x18); + + /* 段 base:flat 模型全 0,TR base 指向 GPA 0x4000 的 TSS */ + vmx_vmcs_write(VMCS_GUEST_CS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_SS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_DS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_ES_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_FS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_GS_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_LDTR_BASE, 0); + vmx_vmcs_write(VMCS_GUEST_TR_BASE, 0x4000); + vmx_vmcs_write(VMCS_GUEST_GDTR_BASE, 0x3000); + vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0); + + /* 段 limit:代码/数据段 4GB,GDTR 覆盖 0x28 字节(3 个描述符 + TSS), + * TR limit = 0x67(104 字节最小 TSS) */ + vmx_vmcs_write(VMCS_GUEST_CS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_SS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_DS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_ES_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_FS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_GS_LIMIT, 0xFFFFFFFF); + vmx_vmcs_write(VMCS_GUEST_LDTR_LIMIT, 0xFFFF); + vmx_vmcs_write(VMCS_GUEST_TR_LIMIT, 0x67); + vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28); + vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF); + + /* 段 access rights: + * CS: 0xA09B = present, DPL0, code, L=1 (64-bit), executed/read + * DS/SS/ES/FS/GS: 0xC093 = present, DPL0, data, writable + * LDTR: 0x82 = unusable (L=0 in high nibble) + * TR: 0x8B = present, DPL0, 64-bit busy TSS */ + vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); + vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093); + vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82); + vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); + + /* Guest RIP/RSP/RFLAGS */ + vmx_vmcs_write(VMCS_GUEST_RIP, 0x1000); + vmx_vmcs_write(VMCS_GUEST_RSP, 0x8000); + vmx_vmcs_write(VMCS_GUEST_RFLAGS, 0x2); + + /* Guest SYSENTER(VMX 要求字段有效,即使 guest 不使用) */ + vmx_vmcs_write(VMCS_GUEST_SYSENTER_CS, 0); + vmx_vmcs_write(VMCS_GUEST_SYSENTER_ESP, 0); + vmx_vmcs_write(VMCS_GUEST_SYSENTER_EIP, 0); + + /* Guest DR7 = 默认值 */ + vmx_vmcs_write(VMCS_GUEST_DR7, 0x400); + + /* Guest EFER:64-bit guest (CS.L=1) 要求 EFER.LME=1 且 EFER.LMA=1, + * 否则 VMX guest-state 检查失败(error 8: invalid guest state)。 + * NXE 允许 guest 使用 NX 位。 */ + vmx_vmcs_write(VMCS_GUEST_IA32_EFER, EFER_LME | EFER_LMA | EFER_NXE); + + /* Guest activity state = 0 (active) */ + vmx_vmcs_write(VMCS_GUEST_ACTIVITY_STATE, 0); + /* Guest interruptibility = 0(无阻塞) */ + vmx_vmcs_write(VMCS_GUEST_INTERRUPTIBILITY, 0); +} + +static void vmcs_setup_controls(u64 eptp) { + u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING, + IA32_VMX_TRUE_PINBASED_CTLS); + + u64 cpu = CPU_BASED_HLT_EXITING + | CPU_BASED_ACTIVATE_SECONDARY + | CPU_BASED_USE_MSR_BITMAPS + | CPU_BASED_UNCOND_IO_EXITING + | CPU_BASED_INVLPG_EXITING; + cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); + + /* Secondary controls:启用 EPT + VPID */ + u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID; + { + u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2); + u32 allowed1 = (u32)(msr >> 32); + cpu2 &= allowed1; + cpu2 |= SEC_EXEC_ENABLE_EPT; + } + + u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS + | VM_EXIT_HOST_ADDR_SPACE_SIZE + | VM_EXIT_LOAD_HOST_EFER; + exit_ctrl = vmx_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS); + + u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS + | VM_ENTRY_IA32E_MODE_GUEST + | VM_ENTRY_LOAD_GUEST_EFER; + entry_ctrl = vmx_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS); + + vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin); + vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu); + vmx_vmcs_write(VMCS_SECONDARY_VM_EXEC_CONTROL, cpu2); + vmx_vmcs_write(VMCS_VM_EXIT_CONTROLS, exit_ctrl); + vmx_vmcs_write(VMCS_VM_ENTRY_CONTROLS, entry_ctrl); + + /* MSR bitmap 物理地址 */ + { + u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; + u64 msr_bitmap_phys = (u64)g_msr_bitmap - hhdm; + vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys); + } + + vmx_vmcs_write(VMCS_EPT_POINTER, eptp); + vmx_vmcs_write(VMCS_VPID, 1); + + /* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */ + vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6)); +} + +/* ===== VMM API ===== */ + +int vmm_init(void) { + log_info("[VMM] init begin"); + + if (vmx_enable() != 0) { + log_error("[VMM] VMX enable failed"); + return -1; + } + + if (ept_init() != 0) { + log_error("[VMM] EPT init failed"); + return -2; + } + + if (vmx_vmcs_alloc(&g_vmcs_phys) != 0) { + log_error("[VMM] VMCS alloc failed"); + return -3; + } + log_hex64("[VMM] VMCS phys=", g_vmcs_phys); + + if (vmx_vmcs_load(g_vmcs_phys) != 0) { + log_error("[VMM] VMCS load failed"); + return -4; + } + + /* VMCS 字段读写自检 */ + vmx_vmcs_write(VMCS_VPID, 0x1234); + u64 vpid_readback = vmx_vmcs_read(VMCS_VPID); + if (vpid_readback != 0x1234) { + log_error("[VMM] VMCS read/write verify failed"); + log_hex64("[VMM] wrote=0x1234 read=", vpid_readback); + return -5; + } + vmx_vmcs_write(VMCS_VPID, 1); + + g_vmm_ready = 1; + log_info("[VMM] init ok"); + return 0; +} + +void vmm_shutdown(void) { + if (!g_vmm_ready) return; + vmx_disable(); + g_vmm_ready = 0; +} + +int vmm_is_ready(void) { return g_vmm_ready; } +u64 vmm_get_eptp(void) { return ept_get_eptp(); } + +int vmm_self_test(void) { + if (!g_vmm_ready) { + log_error("[VMM] not ready"); + return -1; + } + + log_info("[VMM] self-test begin"); + + /* 重置状态 */ + g_guest_terminated = 0; + g_last_exit_reason = 0xFFFFFFFFULL; + + /* 构建 guest GDT + TSS(满足 VMX 对 guest TR 非空的要求) */ + build_guest_gdt(); + + /* EPT 映射 guest 代码页(GPA 0x1000)、栈页(GPA 0x8000)、 + * GDT 页(GPA 0x3000)、TSS 页(GPA 0x4000)。 + * g_guest_* 是 UTSM 虚拟地址,物理地址 = virt - HHDM */ + u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; + u64 guest_code_phys = (u64)g_guest_code - hhdm; + u64 guest_stack_phys = (u64)g_guest_stack - hhdm; + u64 guest_gdt_phys = (u64)g_guest_gdt - hhdm; + u64 guest_tss_phys = (u64)g_guest_tss - hhdm; + + if (ept_map_range(0x1000, guest_code_phys, 4096, EPT_RWX) != 0) { + log_error("[VMM] map guest code failed"); + return -3; + } + if (ept_map_range(0x8000, guest_stack_phys, 4096, EPT_RWX) != 0) { + log_error("[VMM] map guest stack failed"); + return -4; + } + if (ept_map_range(0x3000, guest_gdt_phys, 4096, EPT_READ | EPT_WRITE) != 0) { + log_error("[VMM] map guest GDT failed"); + return -5; + } + if (ept_map_range(0x4000, guest_tss_phys, 4096, EPT_READ | EPT_WRITE) != 0) { + log_error("[VMM] map guest TSS failed"); + return -6; + } + + /* 配置 VMCS */ + vmcs_setup_guest_state_phase1(); + vmcs_setup_controls(ept_get_eptp()); + vmcs_setup_host_state(); + + log_info("[VMM] launching guest"); + + /* vmlaunch 控制流: + * - 失败:执行下一条指令,failed=1 + * - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。 + * - guest 终止时,handler 恢复 saved_rsp 并 jmp 到 post_guest 标签。 + * + * 我们在 vmlaunch 前保存 RSP 与"vmlaunch 后"的 RIP(= post_guest 标签地址)。 */ + int failed; + __asm__ volatile( + "movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */ + "leaq 1f(%%rip), %%rax\n\t" /* 取 post_guest 标签地址 */ + "movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */ + "vmlaunch\n\t" /* 启动 guest */ + /* ---- 失败路径 ---- */ + "movl $1, %0\n\t" + "jmp 2f\n\t" + /* ---- post_guest 标签:终止路径 jmp 到这里 ---- */ + "1:\n\t" + "movl $0, %0\n\t" + "2:\n\t" + : "=r"(failed) + :: "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory" + ); + + if (failed) { + u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR); + log_hex64("[VMM] vmlaunch failed, error=", error); + serial_write("[VMM] "); + serial_write(vmx_error_str(error)); + serial_putc('\n'); + return -7; + } + + /* 到达这里:guest 已终止(HLT 被处理) */ + log_info("[VMM] guest terminated successfully"); + log_hex64("[VMM] last exit reason=", g_last_exit_reason); + log_hex64("[VMM] vmexit count=", vmexit_get_count()); + + if (g_last_exit_reason != EXIT_HLT) { + log_warn("[VMM] expected HLT exit"); + return -8; + } + + log_info("[VMM] self-test PASS"); + return 0; +} + +/* 由 vmexit_asm.S 调用:dispatch 返回 1 = vmresume,返回 0 = 终止。 + * 终止时,asm 侧恢复 saved_rsp 并 jmp saved_return_rip。 + * + * 注意:vmresume 现在在 vmexit_asm.S 中执行(在恢复 guest GPR 之后), + * 不再在 C 中调用。这样 C 处理器可以修改 g_guest_regs.rax 等, + * 汇编恢复后 vmresume 时 CPU GPR = 修改后的 guest 值。 */ +void vmm_vmexit_entry(void) { + int resume = vmexit_dispatch(); + g_last_exit_reason = vmx_vmcs_read(VMCS_EXIT_REASON) & 0xFFFF; + + if (resume) { + /* 汇编侧会恢复 GPR 并执行 vmresume,无需在此调用。 + * 如果 vmresume 失败,汇编会设置 g_guest_terminated=1。 */ + } else { + g_guest_terminated = 1; + } +} + +/* 供 vmexit_asm.S 读取的全局变量符号已在文件顶部声明为非 static 全局变量。 */ diff --git a/CODE/UTSM/vmm/vmx.c b/CODE/UTSM/vmm/vmx.c new file mode 100644 index 0000000..92555f9 --- /dev/null +++ b/CODE/UTSM/vmm/vmx.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include "../arch/x86_64/limine.h" + +extern volatile struct limine_hhdm_request g_hhdm_request; + +/* ===== MSR / CR helpers ===== */ + +u64 vmx_read_msr(u32 msr) { + u32 lo, hi; + __asm__ volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr)); + return ((u64)hi << 32) | lo; +} + +static void vmx_write_msr(u32 msr, u64 value) { + u32 lo = (u32)(value & 0xFFFFFFFFULL); + u32 hi = (u32)(value >> 32); + __asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr)); +} + +static inline u64 read_cr0(void) { + u64 v; + __asm__ volatile("mov %%cr0, %0" : "=r"(v)); + return v; +} + +static inline u64 read_cr4(void) { + u64 v; + __asm__ volatile("mov %%cr4, %0" : "=r"(v)); + return v; +} + +static inline void write_cr4(u64 v) { + __asm__ volatile("mov %0, %%cr4" :: "r"(v)); +} + +static inline u64 read_cr3(void) { + u64 v; + __asm__ volatile("mov %%cr3, %0" : "=r"(v)); + return v; +} + +static inline u64 read_rflags(void) { + u64 v; + __asm__ volatile("pushfq; popq %0" : "=r"(v)); + return v; +} + +/* ===== CPUID ===== */ + +static void cpuid(u32 leaf, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) { + __asm__ volatile("cpuid" + : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx) + : "a"(leaf)); +} + +int vmx_supported(void) { + u32 eax, ebx, ecx, edx; + cpuid(1, &eax, &ebx, &ecx, &edx); + return (ecx & (1U << 5)) != 0; /* CPUID.1:ECX[5] = VMX */ +} + +/* ===== VMXON region ===== */ + +static u64 g_vmxon_phys; +static u64 g_vmcs_revision; +static u64 g_vmx_basic; +static int g_vmx_enabled; + +/* VMX 要求 CR0 固定位必须为 1(VMX operation 不允许清除)。 + * 这些位由 IA32_VMX_BASIC MSR 的高 32 位(CR0 fixed-0)和 + * IA32_VMX_CR0_FIXED1 MSR 决定。简化:直接 OR 上 PE/NE/PG/WP。 */ +static u64 vmx_required_cr0(void) { + return read_cr0() | CR0_PE | CR0_NE | CR0_WP | CR0_PG; +} + +static u64 vmx_required_cr4(void) { + return read_cr4() | CR4_VMXE | CR4_PAE | CR4_PGE; +} + +int vmx_enable(void) { + if (g_vmx_enabled) return 0; + + if (!vmx_supported()) { + log_error("[VMX] CPU does not support VMX"); + return -1; + } + + /* 读 IA32_VMX_BASIC,获取 VMCS revision ID 与 region 大小 */ + g_vmx_basic = vmx_read_msr(IA32_VMX_BASIC); + g_vmcs_revision = g_vmx_basic & 0x7FFFFFFFULL; + u32 vmcs_size = (u32)((g_vmx_basic >> 32) & 0x1FFF); + if (vmcs_size == 0) vmcs_size = 4096; + + log_hex64("[VMX] VMX_BASIC=", g_vmx_basic); + log_hex64("[VMX] VMCS revision=", g_vmcs_revision); + + /* 分配 VMXON region(4KB 对齐,物理页) */ + dkm_dma_buffer vmxon_buf; + if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &vmxon_buf) != 0) { + log_error("[VMX] failed to alloc VMXON region"); + return -2; + } + g_vmxon_phys = vmxon_buf.phys; + + /* 在 VMXON region 偏移 0 写入 VMCS revision ID */ + u32 *vmxon_ptr = (u32 *)vmxon_buf.virt; + vmxon_ptr[0] = (u32)g_vmcs_revision; + log_hex64("[VMX] VMXON phys=", g_vmxon_phys); + + /* 设置 CR4.VMXE */ + u64 cr4 = vmx_required_cr4(); + write_cr4(cr4); + log_hex64("[VMX] CR4=", cr4); + + /* 确保 CR0 满足 VMX 要求(这里只读不写,UEFI 已配置好) */ + (void)vmx_required_cr0(); + + /* 执行 vmxon */ + int err; + __asm__ volatile( + "vmxon %1\n" + "jnc 1f\n" + "jmp 2f\n" + "1:\n" + "mov $0, %0\n" + "jmp 3f\n" + "2:\n" + "mov $1, %0\n" + "3:\n" + : "=r"(err) + : "m"(g_vmxon_phys) + : "memory" + ); + + if (err) { + /* 第二次尝试:读 vmx instruction error 通过 VMCS 不适用 vmxon; + * 简化:返回失败。 */ + log_error("[VMX] vmxon failed"); + return -3; + } + + g_vmx_enabled = 1; + log_info("[VMX] VMX root mode enabled"); + return 0; +} + +int vmx_disable(void) { + if (!g_vmx_enabled) return 0; + __asm__ volatile("vmxoff" ::: "memory"); + g_vmx_enabled = 0; + /* 清 CR4.VMXE */ + u64 cr4 = read_cr4(); + cr4 &= ~CR4_VMXE; + write_cr4(cr4); + log_info("[VMX] VMX root mode disabled"); + return 0; +} + +int vmx_vmcs_alloc(u64 *phys_out) { + if (!phys_out) return -1; + dkm_dma_buffer buf; + if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &buf) != 0) { + log_error("[VMX] failed to alloc VMCS region"); + return -2; + } + /* 偏移 0 写入 VMCS revision ID */ + u32 *p = (u32 *)buf.virt; + p[0] = (u32)g_vmcs_revision; + *phys_out = buf.phys; + return 0; +} + +int vmx_vmcs_load(u64 phys) { + int err; + __asm__ volatile( + "vmptrld %1\n" + "jnc 1f\n" + "jmp 2f\n" + "1:\n" + "mov $0, %0\n" + "jmp 3f\n" + "2:\n" + "mov $1, %0\n" + "3:\n" + : "=r"(err) + : "m"(phys) + : "memory" + ); + if (err) { + log_error("[VMX] vmptrld failed"); + return -1; + } + return 0; +} + +u64 vmx_vmcs_read(u64 field) { + u64 value; + __asm__ volatile( + "vmread %1, %0\n" + : "=r"(value) + : "r"(field) + : "memory" + ); + return value; +} + +void vmx_vmcs_write(u64 field, u64 value) { + __asm__ volatile( + "vmwrite %1, %0\n" + :: "r"(field), "r"(value) + : "memory" + ); +} + +int vmx_vmlaunch(void) { + int err; + __asm__ volatile( + "vmlaunch\n" + "jnc 1f\n" + "jmp 2f\n" + "1:\n" + "mov $0, %0\n" + "jmp 3f\n" + "2:\n" + "mov $1, %0\n" + "3:\n" + : "=r"(err) + :: "memory" + ); + return err; +} + +int vmx_vmresume(void) { + int err; + __asm__ volatile( + "vmresume\n" + "jnc 1f\n" + "jmp 2f\n" + "1:\n" + "mov $0, %0\n" + "jmp 3f\n" + "2:\n" + "mov $1, %0\n" + "3:\n" + : "=r"(err) + :: "memory" + ); + return err; +} + +/* 暴露给其它模块用的辅助 */ +u64 vmx_get_host_cr3(void) { return read_cr3(); } +u64 vmx_get_host_rflags(void) { return read_rflags(); } diff --git a/CODE/desktop/linker.ld b/CODE/desktop/linker.ld index 473e5fc..ffdf62d 100644 --- a/CODE/desktop/linker.ld +++ b/CODE/desktop/linker.ld @@ -17,8 +17,10 @@ SECTIONS *(.data*) *(.data.rel.ro*) *(COMMON) - *(.bss*) } :data .rela.dyn : ALIGN(8) { *(.rela.dyn*) } :data .dynamic : ALIGN(8) { *(.dynamic*) } :data :dynamic + .bss : ALIGN(4K) { + *(.bss*) + } :data } diff --git a/CODE/desktop/main.c b/CODE/desktop/main.c index 5e25b72..bbfffe7 100644 --- a/CODE/desktop/main.c +++ b/CODE/desktop/main.c @@ -1,20 +1,38 @@ -/* Deshab Desktop Manager — 图形桌面环境 +/* Deshab Desktop Manager — Winux-Kate 结构完整复刻 * - * 功能:窗口管理、任务栏、桌面图标、事件路由、应用加载 - * 风格:Sealed Arc 视觉设计语言 - * 输入:PS/2 键盘 + PS/2 鼠标 + * 布局结构与 D:\Code\Winux-Kate 完全一致: + * app-shell + * ├── topbar (34px):品牌 + 工作区切换 + 页面切换 + PG 时钟 + * ├── page-host + * │ ├── 1·DASHBOARD:TERM-01 / TERM-02 / EDITOR / FILES 四面板 + StatusBar + * │ ├── 2·IDE:标签栏 + 编辑宿主 + * │ ├── 3·IM:栏 + 分屏槽(未检测卡片) + * │ ├── 4·DESKTOP:图标网格 + 窗口 + 任务栏 + * │ └── 5+/CUSTOM:外部 .elf 全屏宿主(双击启动) + * └── task-view overlay(工作区卡片) + * + * 新 UI 特性(Kate 之外): + * - RTC 实时时钟(秒级)+ 开机至今 UP 计时 + * - VOL / BRIGHT 可拖滑块(BRIGHT 真实调暗全屏) + * - 开机打字机 Boot 屏 + * - FILES 面板双击文件载入 EDITOR(FAT32 真实读写 + SAVE 回写) + * - 窗口单实例聚焦策略 */ #include "../UTSM/include/utsm/dsk.h" -/* block_read 函数类型(从 kernel_api + 0xA8 获取) */ -typedef int (*desktop_block_read_fn)(void *ctx, unsigned long long lba, unsigned int count, void *buffer); +/* block 设备函数类型(从 kernel_api + 0xA8 获取 block_api,read @ +0x10, write @ +0x18) */ +typedef int (*desktop_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer); +typedef int (*desktop_block_write_fn)(u32 index, u64 lba, u32 count, const void *buffer); /* 先包含 ascii_bitmaps.c(定义 g_ascii),再包含 deshab_ui.h(引用 g_ascii) */ #include "../firstInit/ascii_bitmaps.c" #include "../UTSM/include/utsm/deshab_ui.h" +/* 用户态 FAT32 读写 */ +#include "../tools/fat32_io.h" typedef unsigned char u8; +typedef signed char i8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64; @@ -32,24 +50,57 @@ static void sputc(char c) { static void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } } static void slog(const char *s) { swrite("[desktop] "); swrite(s); swrite("\n"); } +static int kstrlen(const char *s) { int n = 0; while (s[n]) n++; return n; } +static void kstrcpy(char *d, const char *s, int cap) { + int i = 0; + for (; i < cap - 1 && s[i]; i++) d[i] = s[i]; + d[i] = 0; +} +static void kstrcat(char *d, const char *s, int cap) { + int n = kstrlen(d); + if (n < cap) kstrcpy(d + n, s, cap - n); +} + +static u64 rdtsc(void) { + u32 lo, hi; + __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi)); + return ((u64)hi << 32) | lo; +} + /* 前向声明 */ static void redraw_all(void); static int ps2_mouse_poll(void); +static void draw_statusbar_full(void); +static void files_refresh(void); /* ============================================================ - * 常量 + * 常量(Winux-Kate 布局) * ============================================================ */ #define MAX_WINDOWS 8 -#define TASKBAR_H 40 -#define TITLEBAR_H 28 -#define BORDER_W 2 -#define DESKTOP_ICON_W 80 -#define DESKTOP_ICON_H 72 -#define ICON_GUTTER_X 16 -#define ICON_GUTTER_Y 16 +#define BORDER_W 1 #define CURSOR_SIZE 24 +#define ICON_W KATE_ICON_W /* 104 */ +#define ICON_H KATE_ICON_H /* 90 */ +#define ICON_GAP KATE_ICON_GAP /* 18 */ +#define ICON_PAD KATE_ICON_PAD /* 24 */ + +#define CLOSE_BTN_SIZE 16 +#define CLOSE_BTN_OFFSET 22 + +#define MAX_WS 6 /* 工作区上限 */ +#define MAX_CPAGES 4 /* 自定义页上限 */ +#define MAX_FILES 24 /* FILES 面板条目上限 */ + +/* 应用固定状态地址(单实例策略 → 每类应用唯一实例) */ +#define ADDR_TERM1 0x4000000ULL +#define ADDR_TERM2 0x4100000ULL +#define ADDR_TERMWIN 0x4200000ULL +#define ADDR_EDDASH 0x5000000ULL +#define ADDR_EDWIN 0x5100000ULL +#define ADDR_CALC 0x6000000ULL + /* ============================================================ * 事件系统 * ============================================================ */ @@ -64,7 +115,7 @@ typedef enum { typedef struct { ev_type type; - int mx, my; /* 鼠标全局坐标 */ + int mx, my; int button; /* 0=左, 1=右 */ u8 scancode; int shift, ctrl, alt; @@ -81,18 +132,13 @@ typedef struct desktop_window { int focused; int minimized; char title[48]; - int app_id; /* 关联应用 ID */ - void *app_state; /* 应用私有状态 */ - int closing; /* 关闭中标记 */ - int dirty; /* 需要重绘 */ + int app_id; + void *app_state; + int closing; + int dirty; + int ws; /* 所属工作区 */ } desktop_window; -/* ============================================================ - * 应用描述符 - * ============================================================ */ - -/* 前向声明 */ -struct desktop_window; typedef struct desktop_window desktop_win; typedef struct app_ctx { @@ -100,7 +146,8 @@ typedef struct app_ctx { desktop_win *win; int client_x, client_y, client_w, client_h; int should_exit; - desktop_block_read_fn block_read; + desktop_block_read_fn block_read; + desktop_block_write_fn block_write; const void *kernel_api; } app_ctx; @@ -117,8 +164,39 @@ typedef struct { app_event_fn on_event; app_draw_fn on_draw; app_destroy_fn on_destroy; + const char *elf_name; } app_descriptor; +/* 桌面图标 */ +typedef struct { + int app_id; + int x, y; + const char *label; + int selected; +} desktop_icon; + +#define MAX_ICONS 8 + +/* 工作区 */ +typedef struct { + char name[16]; + int page; /* 该工作区离开时的页面 */ +} workspace; + +/* 自定义页 */ +typedef struct { + char name[12]; + char elf11[11]; + int id; /* 页面号 5..8 */ +} cpage; + +/* FILES 面板条目 */ +typedef struct { + char name11[11]; + char disp[13]; + u32 size; +} fentry; + /* ============================================================ * 全局状态 * ============================================================ */ @@ -126,11 +204,14 @@ typedef struct { static du_context g_fb; static u64 g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch; static const void *g_kernel_api; -static desktop_block_read_fn g_block_read; +static desktop_block_read_fn g_block_read; +static desktop_block_write_fn g_block_write; +static const dsk_boot_context *g_boot_ctx = 0; /* 鼠标状态 */ static int g_mouse_x = 400, g_mouse_y = 300; -static int g_mouse_btn = 0; /* bit0=左键 */ +static int g_mouse_btn = 0; /* bit0=左 bit1=右 */ +static int g_left_pressed = 0, g_left_released = 0, g_right_pressed = 0; static u8 g_mouse_buf[3]; static int g_mouse_idx = 0; static int g_mouse_has_pkt = 0; @@ -160,17 +241,75 @@ static int g_drag_off_x, g_drag_off_y; static app_descriptor g_apps[MAX_APPS]; static int g_app_count = 0; -/* 桌面图标 */ -typedef struct { - int app_id; - int x, y; /* 图标位置 */ - const char *label; -} desktop_icon; - -#define MAX_ICONS 8 static desktop_icon g_icons[MAX_ICONS]; static int g_icon_count = 0; +/* ---- Kate 外壳状态 ---- */ +static int g_booted = 0; +static int g_page = 1; /* 1..4 内置, 5..8 自定义 */ +static int g_taskview = 0; +static int g_dash_focus = 0; /* 0=t1 1=t2 2=ed 3=files */ + +static workspace g_ws[MAX_WS]; +static int g_ws_count = 1; +static int g_ws_cur = 0; + +static cpage g_cpages[MAX_CPAGES]; +static int g_cpage_count = 0; + +/* 右键菜单(移到工作区) */ +static int g_ctx_open = 0; +static int g_ctx_x = 0, g_ctx_y = 0; +static int g_ctx_win = -1; + +/* 状态栏滑块 */ +static int g_vol = 65; +static int g_bright = 100; +static int g_drag_slider = 0; /* 1=vol 2=bright */ +static int g_vol_rect[4]; /* x,y,w,h 滑轨 */ +static int g_bri_rect[4]; + +static int g_quit = 0; /* EXIT 按钮 → 返回 DSK */ + +/* 时钟 */ +static int g_rtc_boot_sec = 0; /* 开机时刻(秒级当天秒) */ +static int g_last_sec = -1; +static u64 g_tsc_per_sec = 0; + +/* FILES 面板 */ +static fentry g_files[MAX_FILES]; +static int g_file_count = 0; +static int g_file_sel = -1; +static int g_files_loaded = 0; + +/* 双击检测 */ +static u64 g_last_click_tsc = 0; +static int g_last_click_x = -100, g_last_click_y = -100; + +/* 面板/控件命中矩形(绘制时更新,点击时使用) */ +static int g_dash_rect[4][4]; /* 4 面板 x,y,w,h */ +static int g_save_rect[4]; +static int g_pg_rect[8][4]; /* 页面切换按钮 */ +static int g_pg_rm_rect[8][4]; +static int g_pgadd_rect[4]; +static int g_tvbtn_rect[4]; /* 任务视图按钮 */ +static int g_ws_rect[MAX_WS][4]; +static int g_wsadd_rect[4]; +static int g_tb_rect[MAX_WINDOWS][4]; /* 任务栏项 */ +static int g_tb_close_rect[MAX_WINDOWS][4]; +static int g_tb_quit_rect[4]; +static int g_tb_adopt_rect[4]; /* 收纳窗口按钮 */ +static int g_tv_card[MAX_WS + 1][4]; /* 任务视图卡片(+add) */ +static int g_tv_close[MAX_WS][4]; +static int g_ctx_rect[4]; +static int g_ctx_item[MAX_WS + 1][4]; +static int g_ctx_item_ws[MAX_WS + 1]; /* 菜单行 → 真实工作区索引,-1=新建 */ +static int g_ide_host[4]; +static int g_ide_tabx[4]; /* IDE 标签 × */ +static int g_ide_new_rect[4]; /* IDE "+ NEW IDE" 按钮 */ +static int g_im_toggle_rect[4]; /* IM bar 切换视图按钮 */ +static int g_cp_host[4]; /* 自定义页宿主 */ + /* ============================================================ * 箭头光标形状(24×24,1=前景 0=透明) * ============================================================ */ @@ -203,70 +342,130 @@ static const u8 cursor_shape[24][24] = { }; /* ============================================================ - * 应用图标形状(32×32 像素数据,用程序化方式生成) + * RTC 时钟 * ============================================================ */ -/* 绘制终端图标(32×32)到指定位置 */ -static void draw_icon_terminal(du_context *ctx, int cx, int cy) { - u32 bg = DP_ABYSS_700; - u32 fg = DP_SEAL_300; - u32 border = DP_SEAL_500; - /* 圆角矩形背景 */ - du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); - du_rect_outline(ctx, cx, cy, 32, 32, border, 6); - /* >_ 符号 */ - du_fill_rect(ctx, cx+5, cy+10, 8, 2, fg); - du_fill_rect(ctx, cx+5, cy+10, 2, 6, fg); - du_fill_rect(ctx, cx+13, cy+12, 2, 2, fg); - du_fill_rect(ctx, cx+17, cy+16, 6, 2, fg); +static int bcd2bin(u8 v) { return (v >> 4) * 10 + (v & 0xF); } + +static void rtc_read(int *yy, int *mo, int *dd, int *hh, int *mi, int *ss) { + outb(0x70, 0x00); *ss = bcd2bin(inb(0x71)); + outb(0x70, 0x02); *mi = bcd2bin(inb(0x71)); + outb(0x70, 0x04); *hh = bcd2bin(inb(0x71)); + outb(0x70, 0x07); *dd = bcd2bin(inb(0x71)); + outb(0x70, 0x08); *mo = bcd2bin(inb(0x71)); + outb(0x70, 0x09); *yy = bcd2bin(inb(0x71)); } -/* 绘制编辑器图标(32×32) */ -static void draw_icon_editor(du_context *ctx, int cx, int cy) { - u32 bg = DP_ABYSS_700; - u32 fg = DP_SEAL_500; - u32 border = DP_SEAL_300; - du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); - du_rect_outline(ctx, cx, cy, 32, 32, border, 6); - /* 文本行 */ - for (int i = 0; i < 5; i++) { - int lw = (i == 0) ? 20 : (i == 3 ? 12 : 16); - du_fill_rect(ctx, cx+6, cy+6+i*5, lw, 2, fg); +static int rtc_day_sec(void) { + int y, mo, d, h, mi, s; + rtc_read(&y, &mo, &d, &h, &mi, &s); + return h * 3600 + mi * 60 + s; +} + +/* 格式化 HH:MM:SS */ +static void fmt_hms(char *out, int sec) { + int h = sec / 3600, m = (sec / 60) % 60, s = sec % 60; + out[0] = '0' + h / 10; out[1] = '0' + h % 10; out[2] = ':'; + out[3] = '0' + m / 10; out[4] = '0' + m % 10; out[5] = ':'; + out[6] = '0' + s / 10; out[7] = '0' + s % 10; out[8] = 0; +} + +/* 校准 TSC(约 1 秒):用于双击间隔判定 */ +static void tsc_calibrate(void) { + int s0 = rtc_day_sec(); + while (rtc_day_sec() == s0) __asm__("pause"); + u64 t0 = rdtsc(); + int s1 = rtc_day_sec(); + while (rtc_day_sec() == s1) __asm__("pause"); + g_tsc_per_sec = rdtsc() - t0; +} + +/* ============================================================ + * Winux-Kate 应用图标(40×40,青色几何图形) + * ============================================================ */ + +static void draw_circle_outline(du_context *ctx, int cx, int cy, int r, u32 color) { + for (int dy = -r; dy <= r; dy++) { + for (int dx = -r; dx <= r; dx++) { + int d2 = dx * dx + dy * dy; + if (d2 <= r * r && d2 > (r - 1) * (r - 1)) { + du_pixel(ctx, cx + dx, cy + dy, color); + } + } } } -/* 绘制文件夹图标(32×32) */ -static void draw_icon_folder(du_context *ctx, int cx, int cy) { - u32 bg = DP_ARC_500; - u32 fg = DP_ARC_300; - u32 border = DP_ARC_700; - du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); - du_rect_outline(ctx, cx, cy, 32, 32, border, 6); - /* 文件夹标签 */ - du_fill_rect(ctx, cx+4, cy+6, 12, 3, fg); - /* 文件夹主体 */ - du_fill_rect(ctx, cx+4, cy+12, 24, 2, border); - du_fill_rect(ctx, cx+4, cy+14, 24, 12, fg); +static void draw_icon_terminal(du_context *ctx, int cx, int cy) { + du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); + du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); + du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, KS_ACCENT2); + du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, KS_ACCENT2); + du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, KS_ACCENT2); + du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, KS_ACCENT2); + du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, KS_ACCENT2); + du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, KS_ACCENT); +} + +static void draw_icon_editor(du_context *ctx, int cx, int cy) { + du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); + du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); + int widths[] = {24, 20, 22, 14, 20, 18}; + for (int i = 0; i < 6; i++) { + du_fill_rect(ctx, cx + 8, cy + 8 + i * 4, widths[i], 2, + (i == 0) ? KS_ACCENT2 : KS_ACCENT); + } +} + +static void draw_icon_folder(du_context *ctx, int cx, int cy) { + du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); + du_rect_outline(ctx, cx, cy, 40, 40, KS_BORDER, 2); + du_fill_rect(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT_DIM); + du_rect_outline(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT, 1); + du_fill_rect(ctx, cx + 6, cy + 14, 28, 16, KS_BG_SECONDARY); + du_rect_outline(ctx, cx + 6, cy + 14, 28, 16, KS_ACCENT, 1); + du_fill_rect(ctx, cx + 6, cy + 14, 28, 1, KS_ACCENT); + du_fill_rect(ctx, cx + 10, cy + 19, 12, 1, KS_ACCENT2); + du_fill_rect(ctx, cx + 10, cy + 23, 16, 1, KS_TEXT_DIM); + du_fill_rect(ctx, cx + 10, cy + 26, 10, 1, KS_TEXT_DIM); } -/* 绘制计算器图标(32×32) */ static void draw_icon_calc(du_context *ctx, int cx, int cy) { - u32 bg = DP_ABYSS_700; - u32 fg = DP_WARNING; - u32 border = DP_WARNING; - du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); - du_rect_outline(ctx, cx, cy, 32, 32, border, 6); - /* 屏幕 */ - du_fill_rect(ctx, cx+4, cy+4, 24, 6, DP_ABYSS_900); - du_fill_rect(ctx, cx+22, cy+5, 4, 4, fg); - /* 按键网格 */ + du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); + du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); + du_fill_rect(ctx, cx + 6, cy + 6, 28, 8, KS_BG_PRIMARY); + du_rect_outline(ctx, cx + 6, cy + 6, 28, 8, KS_ACCENT2, 1); + du_fill_rect(ctx, cx + 28, cy + 8, 4, 4, KS_ACCENT2); for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { - du_fill_rect(ctx, cx+4+c*8, cy+14+r*6, 6, 4, DP_NEUTRAL_400); + int bx = cx + 6 + c * 9; + int by = cy + 18 + r * 7; + du_fill_rect(ctx, bx, by, 7, 5, KS_ACCENT_DIM); + du_rect_outline(ctx, bx, by, 7, 5, KS_ACCENT, 1); } } } +static void draw_icon_browser(du_context *ctx, int cx, int cy) { + du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); + du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); + int ccx = cx + 20, ccy = cy + 20, r = 11; + draw_circle_outline(ctx, ccx, ccy, r, KS_ACCENT); + du_fill_rect(ctx, ccx - r, ccy, 2 * r + 1, 1, KS_ACCENT); + du_fill_rect(ctx, ccx, ccy - r, 1, 2 * r + 1, KS_ACCENT); + du_pixel(ctx, ccx, ccy, KS_ACCENT2); +} + +static void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) { + switch (app_id) { + case 0: draw_icon_terminal(ctx, cx, cy); break; + case 1: draw_icon_editor(ctx, cx, cy); break; + case 2: draw_icon_folder(ctx, cx, cy); break; + case 3: draw_icon_calc(ctx, cx, cy); break; + case 4: draw_icon_browser(ctx, cx, cy); break; + default: draw_icon_terminal(ctx, cx, cy); break; + } +} + /* ============================================================ * 光标管理 * ============================================================ */ @@ -306,15 +505,13 @@ static void cursor_draw(int mx, int my) { for (int r = 0; r < CURSOR_SIZE; r++) { for (int c = 0; c < CURSOR_SIZE; c++) { if (cursor_shape[r][c]) { - du_pixel(&g_fb, mx + c, my + r, DS_DARK_TEXT_PRIMARY); + du_pixel(&g_fb, mx + c, my + r, KS_ACCENT); } } } } -/* 非阻塞延时:动画期间持续轮询鼠标,保持光标响应。 - * 将 ms 个 50000-iter pause 块拆开,每块结束后轮询一次鼠标。 - * 若鼠标有新数据包,立即擦除旧光标、在新位置保存背景并重绘光标。 */ +/* 非阻塞延时:动画期间持续轮询鼠标,保持光标响应。 */ static void anim_delay_poll_mouse(u32 ms) { for (u32 i = 0; i < ms; i++) { for (volatile u32 j = 0; j < 50000; j++) { @@ -340,10 +537,10 @@ static desktop_window *win_find(int id) { } static desktop_window *win_find_at(int mx, int my) { - /* 从后往前搜索(后面的窗口在上面) */ for (int i = g_win_count - 1; i >= 0; i--) { desktop_window *w = &g_windows[i]; if (!w->visible || w->minimized) continue; + if (w->ws != g_ws_cur) continue; if (mx >= w->x && mx < w->x + w->w && my >= w->y && my < w->y + w->h) { return w; } @@ -364,6 +561,15 @@ static void win_bring_to_front(desktop_window *w) { g_windows[g_win_count - 1] = tmp; } +static void win_focus(desktop_window *w) { + int id = w->id; /* bring_to_front 会重排数组,先保存 id 避免悬空指针 */ + win_bring_to_front(w); + for (int i = 0; i < g_win_count; i++) + g_windows[i].focused = (g_windows[i].id == id); + g_focused_win = id; + if (g_win_count > 0) g_windows[g_win_count - 1].minimized = 0; +} + static desktop_window *win_create(int app_id, const char *title, int x, int y, int w, int h) { if (g_win_count >= MAX_WINDOWS) return 0; desktop_window *win = &g_windows[g_win_count++]; @@ -377,10 +583,9 @@ static desktop_window *win_create(int app_id, const char *title, int x, int y, i win->app_state = 0; win->closing = 0; win->dirty = 1; - /* 拷贝标题 */ + win->ws = g_ws_cur; for (int i = 0; i < 47 && title[i]; i++) win->title[i] = title[i]; win->title[47] = 0; - /* 取消其他窗口焦点 */ for (int i = 0; i < g_win_count - 1; i++) g_windows[i].focused = 0; g_focused_win = id; return win; @@ -393,200 +598,103 @@ static void win_destroy(desktop_window *w) { } if (idx < 0) return; - /* Window close animation - shrinking rectangle */ + /* 窗口关闭动画 — 收缩矩形 */ { int wx = w->x, wy = w->y, ww = w->w, wh = w->h; + int was_cur = (w->ws == g_ws_cur); w->visible = 0; - for (int step = 5; step >= 0; step--) { - int scale = (step + 1) * 100 / 6; - int aw = ww * scale / 100; - int ah = wh * scale / 100; - int ax = wx + (ww - aw) / 2; - int ay = wy + (wh - ah) / 2; - cursor_restore_bg(); - redraw_all(); - /* redraw_all 末尾已绘制光标,这里先擦除以保证保存的背景干净(不含光标像素) */ - cursor_restore_bg(); - du_rect_outline(&g_fb, ax, ay, aw, ah, DS_DARK_ACCENT, DU_RADIUS_SM); - cursor_save_bg(g_mouse_x, g_mouse_y); - cursor_draw(g_mouse_x, g_mouse_y); - anim_delay_poll_mouse(16); + if (was_cur) { + for (int step = 5; step >= 0; step--) { + int scale = (step + 1) * 100 / 6; + int aw = ww * scale / 100; + int ah = wh * scale / 100; + int ax = wx + (ww - aw) / 2; + int ay = wy + (wh - ah) / 2; + cursor_restore_bg(); + redraw_all(); + cursor_restore_bg(); + du_rect_outline(&g_fb, ax, ay, aw, ah, KS_ACCENT, DU_RADIUS_SM); + cursor_save_bg(g_mouse_x, g_mouse_y); + cursor_draw(g_mouse_x, g_mouse_y); + anim_delay_poll_mouse(16); + } } } - /* 调用应用的 on_destroy */ if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_destroy && w->app_state) { g_apps[w->app_id].on_destroy(w->app_state); } for (int i = idx; i < g_win_count - 1; i++) g_windows[i] = g_windows[i+1]; g_win_count--; if (g_focused_win == w->id) { - g_focused_win = g_win_count > 0 ? g_windows[g_win_count-1].id : -1; - if (g_focused_win >= 0) win_find(g_focused_win)->focused = 1; + g_focused_win = -1; + for (int i = g_win_count - 1; i >= 0; i--) { + if (g_windows[i].ws == g_ws_cur && g_windows[i].visible && !g_windows[i].minimized) { + g_focused_win = g_windows[i].id; + g_windows[i].focused = 1; + break; + } + } } } static int win_hit_titlebar(desktop_window *w, int mx, int my) { return mx >= w->x && mx < w->x + w->w && - my >= w->y && my < w->y + TITLEBAR_H; + my >= w->y && my < w->y + KATE_TITLEBAR_H; } static int win_hit_close_btn(desktop_window *w, int mx, int my) { - int bx = w->x + w->w - 28; - int by = w->y + 4; - return mx >= bx && mx < bx + 24 && my >= by && my < by + 20; + int bx = w->x + w->w - CLOSE_BTN_OFFSET; + int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2; + return mx >= bx && mx < bx + CLOSE_BTN_SIZE && + my >= by && my < by + CLOSE_BTN_SIZE; +} + +static int ws_win_count(int ws) { + int n = 0; + for (int i = 0; i < g_win_count; i++) + if (g_windows[i].ws == ws && g_windows[i].visible) n++; + return n; } /* ============================================================ - * 窗口装饰绘制 + * 窗口装饰绘制(Winux-Kate 风格) * ============================================================ */ static void draw_window_frame(desktop_window *w) { if (!w->visible || w->minimized) return; - u32 titlebar_bg = w->focused ? DP_ABYSS_600 : DP_ABYSS_800; - u32 border_col = w->focused ? DS_DARK_BORDER_FOCUS : DS_DARK_BORDER; + u32 border = w->focused ? KS_BORDER_FOCUS : KS_BORDER; - /* 发光效果(焦点窗口) */ if (w->focused) { - du_rect_glow(&g_fb, w->x-2, w->y-2, w->w+4, w->h+4, - DS_DARK_BORDER_FOCUS, 10, 3); + du_kate_glow_border(&g_fb, w->x, w->y, w->w, w->h, KS_ACCENT); } - /* 窗口主体背景 */ - du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, - DS_DARK_BG_SECONDARY, DU_RADIUS_LG); + du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, KS_BG_SECONDARY, 2); - /* 标题栏 */ - du_fill_rect(&g_fb, w->x + BORDER_W, w->y + BORDER_W, - w->w - 2*BORDER_W, TITLEBAR_H - BORDER_W, titlebar_bg); + du_fill_rect_gradient(&g_fb, w->x + 1, w->y + 1, w->w - 2, KATE_TITLEBAR_H - 1, + 0xFF0A3050u, KS_BG_SECONDARY); + du_divider_h(&g_fb, w->x, w->y + KATE_TITLEBAR_H, w->w, KS_BORDER); + + du_fill_rect(&g_fb, w->x + 6, w->y + KATE_TITLEBAR_H / 2 - 2, 4, 4, KS_ACCENT2); - /* 标题文字 */ du_draw_string(&g_fb, w->title, - w->x + (i64)DU_SPACE_SM, - w->y + (TITLEBAR_H - (i64)DU_ASCII_CELL_H) / 2, - DS_DARK_TEXT_PRIMARY, titlebar_bg, DU_ASCII_STEP); + w->x + 14, + w->y + (KATE_TITLEBAR_H - (i64)DU_ASCII_LINE_H) / 2 + 1, + KS_ACCENT, 0xFF0A3050u, DU_ASCII_STEP); - /* 关闭按钮 */ - int bx = w->x + w->w - 28; - int by = w->y + 4; - du_fill_rounded_rect(&g_fb, bx, by, 24, 20, DP_ERROR, DU_RADIUS_SM); - du_draw_string(&g_fb, "X", bx + 8, by + 1, DP_NEUTRAL_900, DP_ERROR, DU_ASCII_STEP); + int bx = w->x + w->w - CLOSE_BTN_OFFSET; + int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2; + du_rect_outline(&g_fb, bx, by, CLOSE_BTN_SIZE, CLOSE_BTN_SIZE, KS_DANGER, 1); + du_draw_string(&g_fb, "X", bx + 3, by - 1, + KS_DANGER, KS_BG_SECONDARY, DU_ASCII_STEP); - /* 边框 */ - du_rect_outline(&g_fb, w->x, w->y, w->w, w->h, border_col, DU_RADIUS_LG); - - /* 标题栏底线 */ - du_fill_rect(&g_fb, w->x + BORDER_W, w->y + TITLEBAR_H, - w->w - 2*BORDER_W, 1, border_col); -} - -/* ============================================================ - * 任务栏绘制 - * ============================================================ */ - -static void draw_taskbar(void) { - i64 tb_y = (i64)g_fb_h - TASKBAR_H; - du_fill_rect(&g_fb, 0, tb_y, (i64)g_fb_w, TASKBAR_H, DP_ABYSS_800); - /* 顶部强调线 */ - du_fill_rect(&g_fb, 0, tb_y, (i64)g_fb_w, 2, DS_DARK_ACCENT); - - /* Start 按钮 */ - du_fill_rounded_rect(&g_fb, (i64)DU_SPACE_SM, tb_y + 4, 48, 32, - DP_ABYSS_600, DU_RADIUS_SM); - du_draw_string(&g_fb, "D", - (i64)DU_SPACE_SM + 18, tb_y + 8, - DS_DARK_ACCENT_LIGHT, DP_ABYSS_600, DU_ASCII_STEP); - - /* 快速启动图标 */ - int qx = 64; - for (int i = 0; i < g_app_count && i < 4; i++) { - int icon_x = qx + i * 40; - switch (i) { - case 0: draw_icon_terminal(&g_fb, icon_x + 4, (int)tb_y + 4); break; - case 1: draw_icon_editor(&g_fb, icon_x + 4, (int)tb_y + 4); break; - case 2: draw_icon_folder(&g_fb, icon_x + 4, (int)tb_y + 4); break; - case 3: draw_icon_calc(&g_fb, icon_x + 4, (int)tb_y + 4); break; - } - } - - /* 窗口按钮 */ - int wx = qx + 4 * 40 + (int)DU_SPACE_LG; - for (int i = 0; i < g_win_count; i++) { - desktop_window *w = &g_windows[i]; - if (w->minimized) continue; - u32 btn_bg = w->focused ? DS_DARK_ACCENT : DP_ABYSS_600; - du_fill_rounded_rect(&g_fb, wx, tb_y + 4, 100, 32, btn_bg, DU_RADIUS_SM); - /* Truncate title to fit button width (100px - 8px padding = ~11 chars) */ - char truncated[16]; - int max_chars = 11; - int tlen = 0; - while (w->title[tlen] && tlen < max_chars) { truncated[tlen] = w->title[tlen]; tlen++; } - if (tlen == max_chars && w->title[tlen]) { - /* Add ellipsis */ - if (tlen > 8) tlen = 8; - truncated[tlen++] = '.'; - truncated[tlen++] = '.'; - truncated[tlen++] = '.'; - } - truncated[tlen] = 0; - du_draw_string(&g_fb, truncated, wx + 4, tb_y + 8, - DS_DARK_TEXT_PRIMARY, btn_bg, DU_ASCII_STEP); - wx += 104; - if (wx > (int)g_fb_w - 200) break; - } - - /* 时钟 */ - u8 rtc_h = 0, rtc_m = 0; - outb(0x70, 4); rtc_h = inb(0x71); - outb(0x70, 2); rtc_m = inb(0x71); - /* BCD → 二进制 */ - rtc_h = (u8)((rtc_h >> 4) * 10 + (rtc_h & 0xF)); - rtc_m = (u8)((rtc_m >> 4) * 10 + (rtc_m & 0xF)); - char clock_str[8]; - clock_str[0] = '0' + rtc_h / 10; - clock_str[1] = '0' + rtc_h % 10; - clock_str[2] = ':'; - clock_str[3] = '0' + rtc_m / 10; - clock_str[4] = '0' + rtc_m % 10; - clock_str[5] = 0; - du_draw_string(&g_fb, clock_str, - (i64)g_fb_w - 5 * (i64)DU_ASCII_STEP - (i64)DU_SPACE_MD, - tb_y + (TASKBAR_H - (i64)DU_ASCII_CELL_H) / 2, - DS_DARK_TEXT_SECONDARY, DP_ABYSS_800, DU_ASCII_STEP); -} - -/* ============================================================ - * 桌面图标绘制 - * ============================================================ */ - -static void draw_desktop_icons(void) { - for (int i = 0; i < g_icon_count; i++) { - desktop_icon *ic = &g_icons[i]; - int cx = ic->x + (DESKTOP_ICON_W - 32) / 2; /* center icon in slot */ - int cy = ic->y; - /* 根据应用 ID 画不同图标 */ - switch (ic->app_id) { - case 0: draw_icon_terminal(&g_fb, cx, cy); break; - case 1: draw_icon_editor(&g_fb, cx, cy); break; - case 2: draw_icon_folder(&g_fb, cx, cy); break; - case 3: draw_icon_calc(&g_fb, cx, cy); break; - default: draw_icon_terminal(&g_fb, cx, cy); break; - } - /* 标签文字 */ - int label_len = 0; - while (ic->label[label_len]) label_len++; - int label_x = ic->x + (DESKTOP_ICON_W - label_len * (int)DU_ASCII_STEP) / 2; - if (label_x < ic->x) label_x = ic->x; - du_draw_string(&g_fb, ic->label, label_x, cy + 36, - DS_DARK_TEXT_PRIMARY, DS_DARK_BG_PRIMARY, DU_ASCII_STEP); - } + du_rect_outline(&g_fb, w->x, w->y, w->w, w->h, border, 2); } /* ============================================================ * PS/2 鼠标处理 * ============================================================ */ -/* PS/2 控制器等待 — 输入缓冲区空(可写) */ static int ps2_wait_write(void) { for (int t = 0; t < 200000; t++) { if (!(inb(0x64) & 0x02)) return 0; @@ -595,7 +703,6 @@ static int ps2_wait_write(void) { return -1; } -/* PS/2 控制器等待 — 输出缓冲区满(可读) */ static int ps2_wait_read(void) { for (int t = 0; t < 200000; t++) { if (inb(0x64) & 0x01) return 0; @@ -604,7 +711,6 @@ static int ps2_wait_read(void) { return -1; } -/* 排干输出缓冲区中所有残留数据(带超时) */ static void ps2_drain(void) { for (int i = 0; i < 16; i++) { if (!(inb(0x64) & 0x01)) break; @@ -613,56 +719,45 @@ static void ps2_drain(void) { } static void ps2_mouse_init(void) { - /* 1. 刷新固件/上一阶段留下的残留数据 */ ps2_drain(); - /* 2. 启用 AUX(鼠标)端口 */ ps2_wait_write(); outb(0x64, 0xA8); - /* 3. 读取控制器配置字节 */ ps2_wait_write(); outb(0x64, 0x20); ps2_wait_read(); u8 cfg = inb(0x60); - /* 4. 设置配置:bit5=0(启用 AUX 时钟), bit1=1(启用 AUX IRQ12), bit6=1(Set2→Set1 转换) */ - cfg &= ~0x20; /* enable AUX clock */ - cfg |= 0x02; /* enable AUX IRQ12 */ - cfg |= 0x40; /* enable Set2→Set1 translation */ + cfg &= ~0x20; + cfg |= 0x02; + cfg |= 0x40; - /* 5. 写回控制器配置 */ ps2_wait_write(); outb(0x64, 0x60); ps2_wait_write(); outb(0x60, cfg); - /* 6. 复位鼠标 (0xFF) — 响应为 ACK(0xFA) + 自检(0xAA) + 设备ID(0x00),共 3 字节 */ ps2_wait_write(); outb(0x64, 0xD4); ps2_wait_write(); outb(0x60, 0xFF); - /* 排干所有复位响应字节(最多 8 字节,带超时) */ for (int i = 0; i < 8; i++) { if (ps2_wait_read() != 0) break; inb(0x60); } - /* 7. 启用数据流模式 (0xF4) — 响应为 ACK(0xFA) */ ps2_wait_write(); outb(0x64, 0xD4); ps2_wait_write(); outb(0x60, 0xF4); - /* 排干 ACK */ for (int i = 0; i < 4; i++) { if (ps2_wait_read() != 0) break; inb(0x60); } - /* 8. 最终刷新:丢弃任何杂散字节,确保数据包从干净状态开始 */ ps2_drain(); - /* 重置鼠标数据包解析状态 */ g_mouse_idx = 0; g_mouse_has_pkt = 0; @@ -672,20 +767,18 @@ static void ps2_mouse_init(void) { static int ps2_mouse_poll(void) { u8 st = inb(0x64); if (!(st & 1)) return 0; - if (!(st & 0x20)) return 0; /* 不是鼠标数据(键盘数据留给键盘处理) */ + if (!(st & 0x20)) return 0; u8 data = inb(0x60); g_mouse_buf[g_mouse_idx++] = data; if (g_mouse_idx < 3) return 0; g_mouse_idx = 0; - /* 检查 bit3 同步 */ if (!(g_mouse_buf[0] & 0x08)) return 0; int dx = (int)(i8)g_mouse_buf[1]; int dy = (int)(i8)g_mouse_buf[2]; - /* (i8) 转换已处理符号位,无需再检查 sign bits */ - dy = -dy; /* PS/2 Y 向上为正,屏幕 Y 向下为正,需翻转 */ + dy = -dy; g_mouse_x += dx; g_mouse_y += dy; @@ -695,8 +788,10 @@ static int ps2_mouse_poll(void) { if (g_mouse_y >= (int)g_fb_h - CURSOR_SIZE) g_mouse_y = (int)g_fb_h - CURSOR_SIZE; int old_btn = g_mouse_btn; - g_mouse_btn = (g_mouse_buf[0] & 0x01) ? 1 : 0; - /* Only signal redraw if something visual changed */ + g_mouse_btn = g_mouse_buf[0] & 0x03; + if ((g_mouse_btn & 1) && !(old_btn & 1)) g_left_pressed = 1; + if (!(g_mouse_btn & 1) && (old_btn & 1)) g_left_released = 1; + if ((g_mouse_btn & 2) && !(old_btn & 2)) g_right_pressed = 1; if (dx == 0 && dy == 0 && g_mouse_btn == old_btn) return 0; g_mouse_has_pkt = 1; return 1; @@ -724,7 +819,7 @@ static char scan_to_ascii(u8 sc, int shift) { } /* ============================================================ - * 内置应用:终端 (Bash) + * 终端 (Bash) — 面板/窗口共用核心 * ============================================================ */ #define TERM_MAX_COLS 80 @@ -743,42 +838,24 @@ typedef struct { static const char *BASH_PROMPT = "deshab# "; -static void *bash_on_create(app_ctx *ctx) { - bash_state *s = (bash_state *)0x4000000; /* 固定地址分配,避免堆依赖 */ +static void bash_reset(bash_state *s, int w, int h) { for (int i = 0; i < TERM_MAX_CHARS; i++) { - s->ch[i] = ' '; s->fg[i] = DS_DARK_TEXT_PRIMARY; + s->ch[i] = ' '; s->fg[i] = KS_TEXT_PRIMARY; } - s->cols = (ctx->client_w - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_STEP; - s->rows = (ctx->client_h - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_LINE_H; + s->cols = (w - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_STEP; + s->rows = (h - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_LINE_H; if (s->cols > TERM_MAX_COLS) s->cols = TERM_MAX_COLS; if (s->rows > TERM_MAX_ROWS) s->rows = TERM_MAX_ROWS; + if (s->cols < 10) s->cols = 10; + if (s->rows < 3) s->rows = 3; s->cur_col = 0; s->cur_row = 0; s->input_len = 0; s->input_cursor = 0; - s->prompt_len = 0; - while (BASH_PROMPT[s->prompt_len]) s->prompt_len++; - - /* 打印欢迎 */ - const char *welcome = "Deshab Bash v0.1\n输入 help 查看命令\n\n"; - while (*welcome) { - if (*welcome == '\n') { - s->cur_col = 0; s->cur_row++; - } else { - if (s->cur_row < s->rows && s->cur_col < s->cols) { - int idx = s->cur_row * TERM_MAX_COLS + s->cur_col; - s->ch[idx] = (u8)*welcome; - s->fg[idx] = DS_DARK_TEXT_PRIMARY; - } - s->cur_col++; - } - welcome++; - } - return s; + s->prompt_len = kstrlen(BASH_PROMPT); } static void bash_putc(bash_state *s, char c, u32 color) { if (c == '\n') { s->cur_col = 0; s->cur_row++; return; } if (s->cur_row >= s->rows) { - /* 滚动 */ for (int r = 1; r < s->rows; r++) { for (int c2 = 0; c2 < s->cols; c2++) { s->ch[(r-1)*TERM_MAX_COLS+c2] = s->ch[r*TERM_MAX_COLS+c2]; @@ -802,66 +879,63 @@ static void bash_puts(bash_state *s, const char *str, u32 color) { static void bash_execute(bash_state *s, const char *cmd) { while (*cmd == ' ') cmd++; - bash_puts(s, BASH_PROMPT, DS_DARK_PROMPT); - bash_puts(s, cmd, DS_DARK_TEXT_PRIMARY); + bash_puts(s, BASH_PROMPT, KS_ACCENT2); + bash_puts(s, cmd, KS_TEXT_PRIMARY); bash_putc(s, '\n', 0); if (*cmd == 0) return; if (cmd[0]=='h'&&cmd[1]=='e'&&cmd[2]=='l'&&cmd[3]=='p') { - bash_puts(s, " help clear echo version\n", DP_SUCCESS); - bash_puts(s, " uname date about reboot\n", DP_SUCCESS); - bash_puts(s, " halt pwd whoami id\n", DP_SUCCESS); + bash_puts(s, " help clear echo version\n", KS_ACCENT2); + bash_puts(s, " uname about reboot halt\n", KS_ACCENT2); + bash_puts(s, " pwd whoami id\n", KS_ACCENT2); } else if (cmd[0]=='c'&&cmd[1]=='l'&&cmd[2]=='e'&&cmd[3]=='a'&&cmd[4]=='r') { for (int i = 0; i < TERM_MAX_CHARS; i++) { s->ch[i] = ' '; } s->cur_col = 0; s->cur_row = 0; } else if (cmd[0]=='v'&&cmd[1]=='e'&&cmd[2]=='r') { - bash_puts(s, "Deshab OS v0.1.0\n", DP_SUCCESS); + bash_puts(s, "Deshab OS v0.1.0 (Winux-Kate shell)\n", KS_ACCENT2); } else if (cmd[0]=='u'&&cmd[1]=='n'&&cmd[2]=='a') { - bash_puts(s, "Deshab\n", DS_DARK_TEXT_PRIMARY); + bash_puts(s, "Deshab\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='a'&&cmd[1]=='b'&&cmd[2]=='o'&&cmd[3]=='u'&&cmd[4]=='t') { - bash_puts(s, "Deshab OS - SAS-R0 Kernel\n", DP_SEAL_300); - bash_puts(s, "Sealed Arc UI Design\n", DP_ARC_300); + bash_puts(s, "Deshab OS - SAS-R0 Kernel\n", KS_ACCENT); + bash_puts(s, "Winux-Kate UI Structure\n", KS_ACCENT2); } else if (cmd[0]=='e'&&cmd[1]=='c'&&cmd[2]=='h'&&cmd[3]=='o') { const char *arg = cmd + 4; while (*arg == ' ') arg++; - bash_puts(s, arg, DS_DARK_TEXT_PRIMARY); + bash_puts(s, arg, KS_TEXT_PRIMARY); bash_putc(s, '\n', 0); } else if (cmd[0]=='p'&&cmd[1]=='w'&&cmd[2]=='d') { - bash_puts(s, "/\n", DS_DARK_TEXT_PRIMARY); + bash_puts(s, "/\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='w'&&cmd[1]=='h'&&cmd[2]=='o') { - bash_puts(s, "root\n", DS_DARK_TEXT_PRIMARY); + bash_puts(s, "root\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='i'&&cmd[1]=='d') { - bash_puts(s, "uid=0(root) gid=0(root)\n", DS_DARK_TEXT_PRIMARY); + bash_puts(s, "uid=0(root) gid=0(root)\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='r'&&cmd[1]=='e'&&cmd[2]=='b') { - bash_puts(s, "Rebooting...\n", DS_DARK_PROMPT); + bash_puts(s, "Rebooting...\n", KS_ACCENT2); outb(0x64, 0xFE); for(;;) __asm__("hlt"); } else if (cmd[0]=='h'&&cmd[1]=='a'&&cmd[2]=='l'&&cmd[3]=='t') { - bash_puts(s, "Halted.\n", DS_DARK_PROMPT); + bash_puts(s, "Halted.\n", KS_ACCENT2); for(;;) __asm__("hlt"); } else { - bash_puts(s, "unknown: ", DP_ERROR); - bash_puts(s, cmd, DP_ERROR); + bash_puts(s, "unknown: ", KS_DANGER); + bash_puts(s, cmd, KS_DANGER); bash_putc(s, '\n', 0); } } -static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) { - bash_state *s = (bash_state *)state; - if (ev->type != EV_KEY_DOWN) return; - u8 sc = ev->scancode; - if (sc == 0x1C) { /* Enter */ +static void bash_key(bash_state *s, u8 sc, int shift) { + if (sc == 0x1C) { s->input_buf[s->input_len] = 0; bash_execute(s, s->input_buf); s->input_len = 0; s->input_cursor = 0; - } else if (sc == 0x0E) { /* Backspace */ + } else if (sc == 0x0E) { if (s->input_cursor > 0) { for (int i = s->input_cursor-1; i < s->input_len-1; i++) s->input_buf[i] = s->input_buf[i+1]; s->input_len--; s->input_cursor--; } } else { - char c = scan_to_ascii(sc, ev->shift); + char c = scan_to_ascii(sc, shift); if (c && c >= 32 && c <= 126 && s->input_len < 255) { for (int i = s->input_len; i > s->input_cursor; i--) s->input_buf[i] = s->input_buf[i-1]; @@ -869,55 +943,59 @@ static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) { s->input_len++; } } - ctx->win->dirty = 1; } -static void bash_on_draw(void *state, app_ctx *ctx) { - bash_state *s = (bash_state *)state; - int cx = ctx->client_x; - int cy = ctx->client_y; - int cw = ctx->client_w; - int ch = ctx->client_h; +/* 绘制终端到指定矩形区域(面板或窗口客户区) */ +static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) { + du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY); - /* 背景 */ - du_fill_rect(&g_fb, cx, cy, cw, ch, DS_DARK_BG_PRIMARY); - - /* 渲染所有字符 */ for (int r = 0; r < s->rows; r++) { for (int c = 0; c < s->cols; c++) { int idx = r * TERM_MAX_COLS + c; if (s->ch[idx] == ' ') continue; - du_draw_char(&g_fb, s->ch[idx], - cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP, - cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H, - s->fg[idx], DS_DARK_BG_PRIMARY); + int px = cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP; + int py = cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H; + if (px + (int)DU_ASCII_CELL_W > cx + cw) continue; + if (py + (int)DU_ASCII_CELL_H > cy + ch) continue; + du_draw_char(&g_fb, s->ch[idx], px, py, s->fg[idx], KS_BG_PRIMARY); } } - /* 绘制当前输入行 */ int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H; - /* 提示符 */ - du_draw_string(&g_fb, BASH_PROMPT, - cx + (int)DU_SPACE_SM, input_y, - DS_DARK_PROMPT, DS_DARK_BG_PRIMARY, DU_ASCII_STEP); - /* 输入内容 */ - du_draw_string(&g_fb, s->input_buf, - cx + (int)DU_SPACE_SM + s->prompt_len * (int)DU_ASCII_STEP, - input_y, - DS_DARK_TEXT_PRIMARY, DS_DARK_BG_PRIMARY, DU_ASCII_STEP); - - /* 光标 */ - int cursor_x = cx + (int)DU_SPACE_SM + (s->prompt_len + s->input_cursor) * (int)DU_ASCII_STEP; - du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3, - (int)DU_ASCII_CELL_W, 2, DS_DARK_CURSOR); + if (input_y + (int)DU_ASCII_CELL_H <= cy + ch) { + du_draw_string(&g_fb, BASH_PROMPT, + cx + (int)DU_SPACE_SM, input_y, + KS_ACCENT2, KS_BG_PRIMARY, DU_ASCII_STEP); + du_draw_string(&g_fb, s->input_buf, + cx + (int)DU_SPACE_SM + s->prompt_len * (int)DU_ASCII_STEP, + input_y, + KS_TEXT_PRIMARY, KS_BG_PRIMARY, DU_ASCII_STEP); + int cursor_x = cx + (int)DU_SPACE_SM + (s->prompt_len + s->input_cursor) * (int)DU_ASCII_STEP; + du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3, + (int)DU_ASCII_CELL_W, 2, KS_ACCENT); + } } -static void bash_on_destroy(void *state) { - (void)state; +/* ---- 窗口适配 ---- */ +static void *bash_on_create(app_ctx *ctx) { + bash_state *s = (bash_state *)ADDR_TERMWIN; + bash_reset(s, ctx->client_w, ctx->client_h); + bash_puts(s, "Deshab Bash v0.1\nType help for commands\n\n", KS_TEXT_PRIMARY); + return s; } +static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) { + if (ev->type != EV_KEY_DOWN) return; + bash_key((bash_state *)state, ev->scancode, ev->shift); + ctx->win->dirty = 1; +} +static void bash_on_draw(void *state, app_ctx *ctx) { + bash_draw_to((bash_state *)state, ctx->client_x, ctx->client_y, + ctx->client_w, ctx->client_h); +} +static void bash_on_destroy(void *state) { (void)state; } /* ============================================================ - * 内置应用:文本编辑器 (Editor) + * 文本编辑器 (Editor) — 面板/IDE/窗口共用核心 * ============================================================ */ #define EDITOR_BUF_SIZE 65536 @@ -926,11 +1004,13 @@ typedef struct { char text[EDITOR_BUF_SIZE]; int text_len; int cursor_pos; - int scroll_y; /* 行偏移 */ + int scroll_y; int cursor_line; int cursor_col; int modified; - char filename[64]; + int has_file; /* 关联了磁盘文件 */ + char name11[11]; /* FAT32 8.3 名 */ + char disp[13]; /* 显示名 */ } editor_state; static void editor_recalc_cursor(editor_state *s) { @@ -950,37 +1030,20 @@ static int editor_line_start(editor_state *s, int line) { return s->text_len; } -static void *editor_on_create(app_ctx *ctx) { - editor_state *s = (editor_state *)0x5000000; /* 固定地址 */ +static void editor_reset(editor_state *s, const char *sample) { s->text_len = 0; s->cursor_pos = 0; s->scroll_y = 0; s->modified = 0; - s->filename[0] = 0; - - /* 默认内容 */ - const char *sample = "# Welcome to Deshab Editor\n#\n\nStart typing here...\n"; + s->has_file = 0; + s->disp[0] = 0; while (*sample && s->text_len < EDITOR_BUF_SIZE - 1) { s->text[s->text_len++] = *sample++; } s->cursor_pos = s->text_len; editor_recalc_cursor(s); - - /* 设置窗口标题 */ - du_draw_string(&g_fb, "", 0, 0, 0, 0, 0); /* 占位 */ - for (int i = 0; i < 63; i++) ctx->win->title[i] = 0; - const char *t = "Editor - untitled"; - for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i]; - - (void)ctx; - return s; } -static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) { - editor_state *s = (editor_state *)state; - if (ev->type != EV_KEY_DOWN) return; - u8 sc = ev->scancode; - int e0 = 0; /* TODO: 扩展键处理 */ - - if (sc == 0x1C) { /* Enter */ +static void editor_key(editor_state *s, u8 sc, int shift) { + if (sc == 0x1C) { if (s->text_len < EDITOR_BUF_SIZE - 1) { for (int i = s->text_len; i > s->cursor_pos; i--) s->text[i] = s->text[i-1]; @@ -988,14 +1051,14 @@ static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) { s->text_len++; s->cursor_pos++; s->modified = 1; } - } else if (sc == 0x0E) { /* Backspace */ + } else if (sc == 0x0E) { if (s->cursor_pos > 0) { for (int i = s->cursor_pos - 1; i < s->text_len - 1; i++) s->text[i] = s->text[i+1]; s->text_len--; s->cursor_pos--; s->modified = 1; } - } else if (sc == 0x48) { /* Up */ + } else if (sc == 0x48) { if (s->cursor_line > 0) { int prev_start = editor_line_start(s, s->cursor_line - 1); int col = s->cursor_col; @@ -1003,26 +1066,26 @@ static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) { for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++) s->cursor_pos++; } - } else if (sc == 0x50) { /* Down */ - if (s->cursor_line < 999) { - int next_start = editor_line_start(s, s->cursor_line + 1); + } else if (sc == 0x50) { + int next_start = editor_line_start(s, s->cursor_line + 1); + if (next_start < s->text_len || s->cursor_line < 999) { int col = s->cursor_col; s->cursor_pos = next_start; for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++) s->cursor_pos++; } - } else if (sc == 0x4B) { /* Left */ + } else if (sc == 0x4B) { if (s->cursor_pos > 0) s->cursor_pos--; - } else if (sc == 0x4D) { /* Right */ + } else if (sc == 0x4D) { if (s->cursor_pos < s->text_len) s->cursor_pos++; - } else if (sc == 0x47) { /* Home */ + } else if (sc == 0x47) { s->cursor_pos = editor_line_start(s, s->cursor_line); - } else if (sc == 0x4F) { /* End */ + } else if (sc == 0x4F) { s->cursor_pos = editor_line_start(s, s->cursor_line); while (s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n') s->cursor_pos++; } else { - char c = scan_to_ascii(sc, ev->shift); + char c = scan_to_ascii(sc, shift); if (c && c >= 32 && c <= 126 && s->text_len < EDITOR_BUF_SIZE - 1) { for (int i = s->text_len; i > s->cursor_pos; i--) s->text[i] = s->text[i-1]; @@ -1032,31 +1095,23 @@ static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) { } } editor_recalc_cursor(s); - ctx->win->dirty = 1; - (void)e0; } -static void editor_on_draw(void *state, app_ctx *ctx) { - editor_state *s = (editor_state *)state; - int cx = ctx->client_x; - int cy = ctx->client_y; - int cw = ctx->client_w; - int ch = ctx->client_h; - +/* 绘制编辑器到指定矩形区域 */ +static void editor_draw_to(editor_state *s, int cx, int cy, int cw, int ch) { int line_num_w = 5 * (int)DU_ASCII_STEP + (int)DU_SPACE_SM; int text_area_x = cx + line_num_w; int text_area_w = cw - line_num_w; - int visible_rows = ch / (int)DU_ASCII_LINE_H; + int visible_rows = (ch - 20) / (int)DU_ASCII_LINE_H; int visible_cols = text_area_w / (int)DU_ASCII_STEP; + if (visible_rows < 1) visible_rows = 1; + if (visible_cols < 4) visible_cols = 4; - /* 背景 */ - du_fill_rect(&g_fb, cx, cy, cw, ch, DS_DARK_BG_SECONDARY); + du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY); - /* 自动滚动 */ if (s->cursor_line < s->scroll_y) s->scroll_y = s->cursor_line; if (s->cursor_line >= s->scroll_y + visible_rows) s->scroll_y = s->cursor_line - visible_rows + 1; - /* 渲染可见行 */ int line = 0; int pos = 0; for (int i = 0; i < s->text_len || line <= s->cursor_line; ) { @@ -1065,26 +1120,23 @@ static void editor_on_draw(void *state, app_ctx *ctx) { if (line >= s->scroll_y) { int screen_y = cy + (line - s->scroll_y) * (int)DU_ASCII_LINE_H; - /* 行号 */ char ln[8]; ln[0] = '0' + ((line+1)/100)%10; ln[1] = '0' + ((line+1)/10)%10; ln[2] = '0' + (line+1)%10; ln[3] = 0; du_draw_string(&g_fb, ln, cx + 2, screen_y, - DS_DARK_TEXT_DIM, DS_DARK_BG_SECONDARY, DU_ASCII_STEP); + KS_TEXT_DIM, KS_BG_SECONDARY, DU_ASCII_STEP); - /* 行号分隔线 */ du_fill_rect(&g_fb, cx + line_num_w - 2, screen_y, 1, - (int)DU_ASCII_CELL_H, DS_DARK_DIVIDER); + (int)DU_ASCII_CELL_H, KS_BORDER_DIM); - /* 行文本 */ int col = 0; while (pos < s->text_len && s->text[pos] != '\n' && col < visible_cols) { du_draw_char(&g_fb, s->text[pos], text_area_x + col * (int)DU_ASCII_STEP, screen_y, - DS_DARK_TEXT_PRIMARY, DS_DARK_BG_SECONDARY); + KS_TEXT_PRIMARY, KS_BG_SECONDARY); col++; pos++; } if (pos < s->text_len && s->text[pos] == '\n') pos++; @@ -1096,19 +1148,19 @@ static void editor_on_draw(void *state, app_ctx *ctx) { if (pos >= s->text_len && line > s->cursor_line) break; } - /* 光标 */ + /* 光标(青色竖条) */ if (s->cursor_line >= s->scroll_y && s->cursor_line < s->scroll_y + visible_rows) { int cursor_y = cy + (s->cursor_line - s->scroll_y) * (int)DU_ASCII_LINE_H; int cursor_x = text_area_x + s->cursor_col * (int)DU_ASCII_STEP; - du_fill_rect(&g_fb, cursor_x, cursor_y, 2, (int)DU_ASCII_CELL_H, DS_DARK_CURSOR); + du_fill_rect(&g_fb, cursor_x, cursor_y, 2, (int)DU_ASCII_CELL_H, KS_ACCENT); } - /* 状态栏 */ + /* 底部迷你状态栏 */ int status_y = cy + ch - 20; - du_fill_rect(&g_fb, cx, status_y, cw, 20, DP_ABYSS_800); + du_fill_rect(&g_fb, cx, status_y, cw, 20, KS_BG_TERTIARY); + du_divider_h(&g_fb, cx, status_y, cw, KS_BORDER); char status[64]; int sp = 0; - /* 行:列 */ const char *p1 = "L:"; while(*p1) status[sp++] = *p1++; status[sp++] = '0' + (s->cursor_line+1)/10%10; status[sp++] = '0' + (s->cursor_line+1)%10; @@ -1116,15 +1168,37 @@ static void editor_on_draw(void *state, app_ctx *ctx) { status[sp++] = '0' + (s->cursor_col+1)/10%10; status[sp++] = '0' + (s->cursor_col+1)%10; if (s->modified) { const char *m = " *"; while(*m) status[sp++] = *m++; } + if (s->has_file) { + const char *m = " "; while(*m) status[sp++] = *m++; + for (int i = 0; s->disp[i] && sp < 60; i++) status[sp++] = s->disp[i]; + } status[sp] = 0; du_draw_string(&g_fb, status, cx + 4, status_y + 1, - DS_DARK_TEXT_DIM, DP_ABYSS_800, DU_ASCII_STEP); + KS_TEXT_DIM, KS_BG_TERTIARY, DU_ASCII_STEP); } +/* ---- 窗口适配 ---- */ +static void *editor_on_create(app_ctx *ctx) { + editor_state *s = (editor_state *)ADDR_EDWIN; + editor_reset(s, "# Welcome to Deshab Editor\n#\n\nStart typing here...\n"); + for (int i = 0; i < 47; i++) ctx->win->title[i] = 0; + const char *t = "Editor - untitled"; + for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i]; + return s; +} +static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) { + if (ev->type != EV_KEY_DOWN) return; + editor_key((editor_state *)state, ev->scancode, ev->shift); + ctx->win->dirty = 1; +} +static void editor_on_draw(void *state, app_ctx *ctx) { + editor_draw_to((editor_state *)state, ctx->client_x, ctx->client_y, + ctx->client_w, ctx->client_h); +} static void editor_on_destroy(void *state) { (void)state; } /* ============================================================ - * 内置应用:计算器 (Calculator) + * 计算器 (Calculator) * ============================================================ */ typedef struct { @@ -1132,7 +1206,7 @@ typedef struct { int display_len; i64 accumulator; i64 current; - int op; /* 0=none, 1=+, 2=-, 3=*, 4=/ */ + int op; int new_number; } calc_state; @@ -1150,7 +1224,7 @@ static void calc_update_display(calc_state *s) { } static void *calc_on_create(app_ctx *ctx) { - calc_state *s = (calc_state *)0x6000000; + calc_state *s = (calc_state *)ADDR_CALC; s->accumulator = 0; s->current = 0; s->op = 0; s->new_number = 1; calc_update_display(s); @@ -1192,20 +1266,17 @@ static void calc_on_draw(void *state, app_ctx *ctx) { int cw = ctx->client_w; int ch = ctx->client_h; - du_fill_rect(&g_fb, cx, cy, cw, ch, DS_DARK_BG_SECONDARY); + du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY); - /* 显示屏 */ int disp_h = 40; du_fill_rounded_rect(&g_fb, cx + 8, cy + 8, cw - 16, disp_h, - DP_ABYSS_900, DU_RADIUS_MD); + KS_BG_PRIMARY, DU_RADIUS_MD); du_rect_outline(&g_fb, cx + 8, cy + 8, cw - 16, disp_h, - DS_DARK_BORDER, DU_RADIUS_MD); - /* 数字右对齐 */ + KS_ACCENT, DU_RADIUS_MD); int text_x = cx + cw - 16 - s->display_len * (int)DU_ASCII_STEP - 8; du_draw_string(&g_fb, s->display, text_x, cy + 16, - DS_DARK_TEXT_PRIMARY, DP_ABYSS_900, DU_ASCII_STEP); + KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP); - /* 按钮网格 4×4 */ const char *btn_labels[] = { "C", "+/-", "%", "/", "7", "8", "9", "*", @@ -1221,15 +1292,17 @@ static void calc_on_draw(void *state, app_ctx *ctx) { int bx = cx + 8 + col * (btn_w + 3); int by = btn_y + row * (btn_h + 3); if (by + btn_h > cy + ch - 4) break; - u32 bg = (i < 4) ? DP_ABYSS_600 : - (i % 4 == 3) ? DS_DARK_ACCENT : - (i == 18) ? DP_SUCCESS : - DP_ABYSS_700; + u32 bg = (i < 4) ? KS_ACCENT_DIM : + (i % 4 == 3) ? KS_ACCENT : + (i == 18) ? KS_ACCENT2 : + KS_BG_TERTIARY; + u32 fg = ((i % 4 == 3) || (i == 18)) ? KS_TEXT_INVERT : KS_TEXT_PRIMARY; du_fill_rounded_rect(&g_fb, bx, by, btn_w, btn_h, bg, DU_RADIUS_SM); + du_rect_outline(&g_fb, bx, by, btn_w, btn_h, KS_BORDER, DU_RADIUS_SM); du_draw_string(&g_fb, btn_labels[i], bx + (btn_w - 1*(int)DU_ASCII_STEP)/2, by + (btn_h - (int)DU_ASCII_CELL_H)/2, - DS_DARK_TEXT_PRIMARY, bg, DU_ASCII_STEP); + fg, bg, DU_ASCII_STEP); } } @@ -1240,84 +1313,204 @@ static void calc_on_destroy(void *state) { (void)state; } * ============================================================ */ static void register_apps(void) { - /* 0: Terminal */ g_apps[0] = (app_descriptor){ "bash", "Terminal", 640, 440, - bash_on_create, bash_on_event, bash_on_draw, bash_on_destroy + bash_on_create, bash_on_event, bash_on_draw, bash_on_destroy, 0 }; g_app_count++; - - /* 1: Editor */ g_apps[1] = (app_descriptor){ "editor", "Editor", 600, 450, - editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy + editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0 }; g_app_count++; - - /* 2: File Manager */ g_apps[2] = (app_descriptor){ "fileman", "Files", 500, 400, - 0, 0, 0, 0 /* placeholder */ + 0, 0, 0, 0, "FILEMAN ELF" }; g_app_count++; - - /* 3: Calculator */ g_apps[3] = (app_descriptor){ "calc", "Calculator", 280, 400, - calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy + calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0 + }; + g_app_count++; + g_apps[4] = (app_descriptor){ + "browser", "Browser", 700, 500, + 0, 0, 0, 0, "BROWSER ELF" }; g_app_count++; } +/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px, + * 左→右排列,超出画布宽度则换行(align-content: start) */ static void setup_desktop_icons(void) { - int start_x = (int)g_fb_w - DESKTOP_ICON_W - ICON_GUTTER_X; - int y = ICON_GUTTER_Y; + int pad = KATE_ICON_PAD; /* 24 */ + int x = pad; + int y = KATE_TOPBAR_H + KATE_PAGE_PAD + pad; + int max_x = (int)g_fb_w - pad; /* canvas 右缘 */ for (int i = 0; i < g_app_count; i++) { - if (!g_apps[i].on_create) continue; + if (!g_apps[i].on_create && !g_apps[i].elf_name) continue; + if (x > pad && x + KATE_ICON_W > max_x) { + x = pad; + y += KATE_ICON_H + KATE_ICON_GAP; + } g_icons[g_icon_count].app_id = i; - g_icons[g_icon_count].x = start_x; + g_icons[g_icon_count].x = x; g_icons[g_icon_count].y = y; g_icons[g_icon_count].label = g_apps[i].display_name; + g_icons[g_icon_count].selected = 0; g_icon_count++; - y += DESKTOP_ICON_H; + x += KATE_ICON_W + KATE_ICON_GAP; } } /* ============================================================ - * 应用启动 + * 外部 ELF 加载 * ============================================================ */ +#define DT_ELFCLASS64 2 +#define DT_EM_X86_64 62 +#define DT_PT_LOAD 1 +#define DT_PT_DYNAMIC 2 +#define DT_DT_RELA 7 +#define DT_DT_RELASZ 8 +#define DT_R_X86_64_RELATIVE 8 + +typedef struct { u8 ident[16]; u16 type,machine; u32 ver; u64 entry,phoff,shoff; u32 flags; u16 ehsize,phentsize,phnum,shentsize,shnum,shstrndx; } dt_elf64_ehdr; +typedef struct { u32 type,flags; u64 offset,vaddr,paddr,filesz,memsz,align; } dt_elf64_phdr; +typedef struct { i64 tag; u64 val; } dt_elf64_dyn; +typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela; + +static u8 g_elf_image[262144]; + +static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) { + const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data; + if (data_size < sizeof(dt_elf64_ehdr)) return -1; + if (eh->ident[0] != 0x7F || eh->ident[4] != DT_ELFCLASS64) return -2; + if (eh->machine != DT_EM_X86_64) return -3; + u64 min_vaddr = ~0ULL, max_vaddr = 0; + u32 lc = 0; + for (u16 i = 0; i < eh->phnum; i++) { + const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize); + if (ph->type != DT_PT_LOAD) continue; + if (ph->filesz > ph->memsz) return -4; + if (ph->vaddr < min_vaddr) min_vaddr = ph->vaddr; + if (ph->vaddr + ph->memsz > max_vaddr) max_vaddr = ph->vaddr + ph->memsz; + lc++; + } + if (!lc || min_vaddr == ~0ULL) return -5; + u64 isize = (max_vaddr - min_vaddr + 0xFFF) & ~0xFFFULL; + if (isize > sizeof(g_elf_image)) return -6; + u8 *image = g_elf_image; + for (u64 i = 0; i < isize; i++) image[i] = 0; + for (u16 i = 0; i < eh->phnum; i++) { + const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize); + if (ph->type != DT_PT_LOAD) continue; + u64 off = ph->vaddr - min_vaddr; + for (u64 j = 0; j < ph->filesz; j++) + image[off + j] = data[ph->offset + j]; + } + for (u16 i = 0; i < eh->phnum; i++) { + const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize); + if (ph->type != DT_PT_DYNAMIC) continue; + const dt_elf64_dyn *dyn = (const dt_elf64_dyn *)(data + ph->offset); + u64 rela_off = 0, rela_sz = 0; + for (u32 j = 0; j < ph->filesz / sizeof(dt_elf64_dyn); j++) { + if (dyn[j].tag == DT_DT_RELA) rela_off = dyn[j].val; + else if (dyn[j].tag == DT_DT_RELASZ) rela_sz = dyn[j].val; + else if (dyn[j].tag == 0) break; + } + if (rela_off && rela_sz) { + const dt_elf64_rela *r = (const dt_elf64_rela *)(data + rela_off); + u32 rn = (u32)(rela_sz / sizeof(dt_elf64_rela)); + for (u32 j = 0; j < rn; j++) { + u32 type = (u32)(r[j].info & 0xFFFFFFFF); + if (type == DT_R_X86_64_RELATIVE) { + u64 target = r[j].offset - min_vaddr; + u64 val = min_vaddr + (u64)r[j].addend; + *(u64 *)(image + target) = val; + } + } + } + } + *entry_out = (void *)(image + (eh->entry - min_vaddr)); + return 0; +} + +static void launch_external_elf(const char *name11) { + u8 *data = 0; u32 size = 0; + if (f32_read_root_file(name11, &data, &size) != 0) { + slog("elf not found"); + return; + } + void *entry = 0; + if (dt_load_elf(data, size, &entry) != 0) { + slog("elf load failed"); + return; + } + slog("launching external elf"); + void (*elf_entry)(const dsk_boot_context *) = (void (*)(const dsk_boot_context *))entry; + elf_entry(g_boot_ctx); + g_cursor_saved = 0; + g_cursor_old_x = -1; g_cursor_old_y = -1; + files_refresh(); /* 外部应用可能改动了磁盘 */ + redraw_all(); + slog("external elf returned"); +} + +/* ============================================================ + * 应用启动(单实例聚焦策略) + * ============================================================ */ + +static void fill_app_ctx(app_ctx *ctx, desktop_window *win) { + ctx->fb = &g_fb; + ctx->win = win; + ctx->client_x = win->x + BORDER_W; + ctx->client_y = win->y + KATE_TITLEBAR_H; + ctx->client_w = win->w - 2 * BORDER_W; + ctx->client_h = win->h - KATE_TITLEBAR_H - BORDER_W; + ctx->should_exit = 0; + ctx->block_read = g_block_read; + ctx->block_write = g_block_write; + ctx->kernel_api = g_kernel_api; +} + static void launch_app(int app_id) { if (app_id < 0 || app_id >= g_app_count) return; app_descriptor *app = &g_apps[app_id]; + + if (app->elf_name) { + launch_external_elf(app->elf_name); + return; + } if (!app->on_create) return; - /* 计算窗口位置(居中偏移) */ - int wx = 80 + g_win_count * 30; - int wy = 40 + g_win_count * 30; - if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = 80; - if (wy + (int)app->default_h > (int)g_fb_h - TASKBAR_H - 20) wy = 40; + /* 单实例:已有同类窗口则聚焦并迁移到当前工作区 */ + for (int i = 0; i < g_win_count; i++) { + if (g_windows[i].app_id == app_id && g_windows[i].visible) { + g_windows[i].ws = g_ws_cur; + win_focus(&g_windows[i]); + slog("focus existing app window"); + return; + } + } + + int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H; + int wx = ICON_PAD + g_win_count * 30; + int wy = top + 8 + g_win_count * 30; + if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD; + if (wy + (int)app->default_h > bot - 20) wy = top + 8; desktop_window *win = win_create(app_id, app->display_name, wx, wy, (int)app->default_w, (int)app->default_h); if (!win) return; - /* 构建应用上下文 */ app_ctx ctx; - ctx.fb = &g_fb; - ctx.win = win; - ctx.client_x = win->x + BORDER_W; - ctx.client_y = win->y + TITLEBAR_H; - ctx.client_w = win->w - 2 * BORDER_W; - ctx.client_h = win->h - TITLEBAR_H - BORDER_W; - ctx.should_exit = 0; - ctx.block_read = g_block_read; - ctx.kernel_api = g_kernel_api; + fill_app_ctx(&ctx, win); win->app_state = app->on_create(&ctx); win->dirty = 1; - /* Window open animation - expanding rectangle */ + /* 窗口打开动画 — 展开矩形 */ for (int step = 0; step < 6; step++) { int scale = (step + 1) * 100 / 6; int aw = win->w * scale / 100; @@ -1326,9 +1519,8 @@ static void launch_app(int app_id) { int ay = win->y + (win->h - ah) / 2; cursor_restore_bg(); redraw_all(); - /* redraw_all 末尾已绘制光标,这里先擦除以保证保存的背景干净(不含光标像素) */ cursor_restore_bg(); - du_rect_outline(&g_fb, ax, ay, aw, ah, DS_DARK_ACCENT, DU_RADIUS_SM); + du_rect_outline(&g_fb, ax, ay, aw, ah, KS_ACCENT, DU_RADIUS_SM); cursor_save_bg(g_mouse_x, g_mouse_y); cursor_draw(g_mouse_x, g_mouse_y); anim_delay_poll_mouse(16); @@ -1338,46 +1530,1104 @@ static void launch_app(int app_id) { } /* ============================================================ - * 全屏重绘 + * FILES 面板(FAT32 真实读写) * ============================================================ */ -static void redraw_all(void) { - /* 渐变背景 */ - du_fill_bg_gradient(&g_fb, DP_ABYSS_900, DP_ABYSS_800); +static int files_list_cb(const char *name, u32 size, u8 attr, void *ud) { + (void)ud; + if (attr & 0x10) return 0; /* 跳过子目录 */ + if (g_file_count >= MAX_FILES) return 1; /* 停止 */ + fentry *f = &g_files[g_file_count]; + f->size = size; + kstrcpy(f->disp, name, 13); + /* disp (NAME.EXT) → name11 (NAME EXT) */ + for (int i = 0; i < 11; i++) f->name11[i] = ' '; + int i = 0, o = 0; + while (name[i] && name[i] != '.' && o < 8) f->name11[o++] = name[i++]; + while (name[i] && name[i] != '.') i++; + if (name[i] == '.') { + i++; + o = 8; + while (name[i] && o < 11) f->name11[o++] = name[i++]; + } + g_file_count++; + return 0; +} - /* 桌面图标 */ - draw_desktop_icons(); +static void files_refresh(void) { + g_file_count = 0; + g_file_sel = -1; + if (!g_block_read) { g_files_loaded = 0; return; } + if (f32_list_root(files_list_cb, 0) == 0) g_files_loaded = 1; + else g_files_loaded = 0; +} - /* 窗口(从底到顶) */ - for (int i = 0; i < g_win_count; i++) { - desktop_window *w = &g_windows[i]; - if (w->minimized || !w->visible) continue; +/* 双击打开文件 → 载入 DASHBOARD EDITOR */ +static void files_open(int idx) { + if (idx < 0 || idx >= g_file_count) return; + editor_state *ed = (editor_state *)ADDR_EDDASH; + fentry *f = &g_files[idx]; - /* 窗口装饰(先画框架作为窗口背景:主体、标题栏、边框) */ - draw_window_frame(w); + u8 *data = 0; u32 size = 0; + if (f32_read_root_file(f->name11, &data, &size) != 0) { + slog("file open failed"); + return; + } + ed->text_len = 0; + u32 cap = EDITOR_BUF_SIZE - 1; + if (size > cap) size = cap; + for (u32 i = 0; i < size; i++) { + char c = (char)data[i]; + if ((u8)c >= 32 || c == '\n' || c == '\t') + ed->text[ed->text_len++] = c; + } + for (int i = 0; i < 11; i++) ed->name11[i] = f->name11[i]; + kstrcpy(ed->disp, f->disp, 13); + ed->has_file = 1; + ed->modified = 0; + ed->scroll_y = 0; + ed->cursor_pos = 0; + editor_recalc_cursor(ed); + g_dash_focus = 2; + slog("file loaded into editor"); +} - /* 窗口客户区背景 */ - int cx = w->x + BORDER_W; - int cy = w->y + TITLEBAR_H; - int cw = w->w - 2 * BORDER_W; - int ch = w->h - TITLEBAR_H - BORDER_W; +/* EDITOR SAVE 回写磁盘 */ +static void editor_save(void) { + editor_state *ed = (editor_state *)ADDR_EDDASH; + if (!ed->has_file) return; + if (f32_write_root_file(ed->name11, (const u8 *)ed->text, (u32)ed->text_len) == 0) { + ed->modified = 0; + slog("file saved"); + } else { + slog("file save failed"); + } +} - /* 调用应用绘制(在框架之上绘制客户区内容) */ - if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_draw && w->app_state) { - app_ctx ctx; - ctx.fb = &g_fb; - ctx.win = w; - ctx.client_x = cx; ctx.client_y = cy; - ctx.client_w = cw; ctx.client_h = ch; - ctx.should_exit = 0; - ctx.block_read = g_block_read; - ctx.kernel_api = g_kernel_api; - g_apps[w->app_id].on_draw(w->app_state, &ctx); +/* ============================================================ + * 通用小绘制 + * ============================================================ */ + +/* 矩形内居中文字 */ +static void draw_centered(const char *s, int cx, int y, u32 fg, u32 bg) { + int l = kstrlen(s); + du_draw_string(&g_fb, s, cx - l * (int)DU_ASCII_STEP / 2, y, fg, bg, DU_ASCII_STEP); +} + +/* 虚线矩形(ide-host / im-slot / custom host) */ +static void draw_dashed_rect(int x, int y, int w, int h, u32 color) { + for (int i = x; i < x + w; i += 8) { + du_fill_rect(&g_fb, i, y, 4, 1, color); + du_fill_rect(&g_fb, i, y + h - 1, 4, 1, color); + } + for (int i = y; i < y + h; i += 8) { + du_fill_rect(&g_fb, x, i, 1, 4, color); + du_fill_rect(&g_fb, x + w - 1, i, 1, 4, color); + } +} + +/* ============================================================ + * Boot 屏(打字机) + * ============================================================ */ + +static const char *BOOT_LINES[] = { + "DESHAB SHELL v0.1.0", + "initializing UTSM sealed memory...", + "mounting FAT32 root filesystem............[ OK ]", + "loading window manager (SAS-R0 Ring0).....[ OK ]", + "registering PS/2 input drivers............[ OK ]", + "probing AHCI block device.................[ OK ]", + "resolving desktop shortcuts...............[ OK ]", + "desktop replaced by Winux-Kate shell......[ OK ]", + "shell ready.", + "DEAICUP STUDIO", +}; +#define BOOT_LINE_COUNT 10 + +static void busy_delay(u32 cycles) { + for (volatile u32 i = 0; i < cycles; i++) __asm__("pause"); +} + +static void boot_screen(void) { + int W = (int)g_fb_w, H = (int)g_fb_h; + du_fill_bg_solid(&g_fb, KS_BG_PRIMARY); + + int lw = 46 * (int)DU_ASCII_STEP; + int lx = (W - lw) / 2; + int ty = H / 2 - 180; + + /* boot-title */ + const char *title = "DESHAB"; + int tl = kstrlen(title); + for (int i = 0; i < tl; i++) { + char t[2] = { title[i], 0 }; + du_draw_string(&g_fb, t, W / 2 - tl * (int)DU_ASCII_STEP + i * (int)DU_ASCII_STEP * 2, + ty, KS_ACCENT, 0, DU_ASCII_STEP * 2); + busy_delay(8000000); + } + du_divider_h(&g_fb, W / 2 - 120, ty + 30, 240, KS_ACCENT); + + /* boot-subtitle(对应 Kate "由 Deaicup 工作室制作") */ + draw_centered("DEAICUP STUDIO", W / 2, ty + 44, KS_ACCENT2, 0); + busy_delay(10000000); + + /* boot-lines 打字机;进度条固定在全部行下方(对应 Kate .boot-bar) */ + int ly = ty + 76; + int bar_y = ly + BOOT_LINE_COUNT * ((int)DU_ASCII_LINE_H + 2) + 12; + int bw = 260; + for (int li = 0; li < BOOT_LINE_COUNT; li++) { + const char *s = BOOT_LINES[li]; + u32 fg = (li == 1 || li == BOOT_LINE_COUNT - 1) ? KS_TEXT_DIM : KS_ACCENT2; + du_draw_string(&g_fb, ">", lx, ly, KS_ACCENT, 0, DU_ASCII_STEP); + for (int ci = 0; s[ci]; ci++) { + char t[2] = { s[ci], 0 }; + du_draw_string(&g_fb, t, lx + (ci + 1) * (int)DU_ASCII_STEP, ly, + fg, 0, DU_ASCII_STEP); + busy_delay(500000); + } + ly += (int)DU_ASCII_LINE_H + 2; + /* boot-bar:渐变填充(accent → accent2) */ + int fill = bw * (li + 1) / BOOT_LINE_COUNT; + du_rect_outline(&g_fb, W / 2 - bw / 2, bar_y, bw, 6, KS_BORDER, 1); + if (fill > 2) du_fill_rect_gradient(&g_fb, W / 2 - bw / 2 + 1, bar_y + 1, + fill - 2, 4, KS_ACCENT, KS_ACCENT2); + } + + /* boot-hint + credit(对应 Kate "SYSTEM ONLINE" + "© 2026 Deaicup Studio") */ + draw_centered("SYSTEM ONLINE", W / 2, H - 64, KS_ACCENT2, 0); + draw_centered("(C) 2026 DEAICUP STUDIO", W / 2, H - 40, KS_TEXT_DIM, 0); + busy_delay(40000000); +} + +/* ============================================================ + * 顶栏(brand + WorkspaceSwitcher + PageSwitcher + PG 时钟) + * ============================================================ */ + +static void draw_topbar(void) { + int W = (int)g_fb_w; + du_fill_rect_gradient(&g_fb, 0, 0, W, KATE_TOPBAR_H, + 0xFF0A2840u, KS_BG_PRIMARY); + du_divider_h(&g_fb, 0, KATE_TOPBAR_H, W, KS_BORDER); + + int x = 12; + int ty = (KATE_TOPBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1; + + /* brand:方块点 + DESHAB + brand-credit(对应 Kate "由 Deaicup 工作室制作") */ + du_fill_rect(&g_fb, x, KATE_TOPBAR_H / 2 - 3, 6, 6, KS_ACCENT); + du_draw_string(&g_fb, "DESHAB", x + 12, ty, KS_ACCENT, 0, DU_ASCII_STEP + 2); + x += 12 + 6 * ((int)DU_ASCII_STEP + 2) + 10; + du_draw_string(&g_fb, "DEAICUP STUDIO", x, ty + 2, KS_ACCENT2, 0, DU_ASCII_STEP); + x += 14 * (int)DU_ASCII_STEP + 14; + + /* 分隔线 + 任务视图按钮(三横线图标) */ + du_divider_v(&g_fb, x - 6, 6, KATE_TOPBAR_H - 12, KS_BORDER); + g_tvbtn_rect[0] = x; g_tvbtn_rect[1] = 5; + g_tvbtn_rect[2] = 28; g_tvbtn_rect[3] = 24; + { + int hover = g_mouse_x >= x && g_mouse_x < x + 28 && + g_mouse_y >= 5 && g_mouse_y < 29; + if (hover) du_fill_rect(&g_fb, x, 5, 28, 24, KS_ACCENT_DIM); + du_rect_outline(&g_fb, x, 5, 28, 24, KS_BORDER, 1); + du_fill_rect(&g_fb, x + 6, 11, 16, 2, KS_ACCENT); + du_fill_rect(&g_fb, x + 6, 16, 16, 2, KS_ACCENT); + du_fill_rect(&g_fb, x + 6, 21, 16, 2, KS_ACCENT); + } + x += 34; + + /* 工作区按钮 "name|n" */ + for (int i = 0; i < g_ws_count; i++) { + char label[24]; + kstrcpy(label, g_ws[i].name, 18); + kstrcat(label, "|", 24); + int cnt = ws_win_count(i); + char cn[4]; + cn[0] = '0' + (char)(cnt % 10); cn[1] = 0; + kstrcat(label, cn, 24); + int bw = kstrlen(label) * (int)DU_ASCII_STEP + 16; + int active = (i == g_ws_cur); + int hover = g_mouse_x >= x && g_mouse_x < x + bw && + g_mouse_y >= 5 && g_mouse_y < 29; + if (active) { + du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT2); + du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT2); + } else if (hover) { + du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM); + } + du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT2 : KS_BORDER, 1); + du_draw_string(&g_fb, label, x + 8, ty, + active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); + g_ws_rect[i][0] = x; g_ws_rect[i][1] = 5; + g_ws_rect[i][2] = bw; g_ws_rect[i][3] = 24; + x += bw + 6; + } + + /* ws-add "+" */ + g_wsadd_rect[0] = x; g_wsadd_rect[1] = 5; + g_wsadd_rect[2] = 26; g_wsadd_rect[3] = 24; + { + int hover = g_mouse_x >= x && g_mouse_x < x + 26 && + g_mouse_y >= 5 && g_mouse_y < 29; + if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM); + du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1); + du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP); + } + x += 32; + + /* 分隔线 */ + du_divider_v(&g_fb, x - 4, 6, KATE_TOPBAR_H - 12, KS_BORDER); + x += 4; + + /* 页面切换 */ + static const char *pg_names[4] = { "1.DASHBOARD", "2.IDE", "3.IM", "4.DESKTOP" }; + for (int i = 0; i < 4; i++) { + int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 16; + int active = (g_page == i + 1); + int hover = g_mouse_x >= x && g_mouse_x < x + bw && + g_mouse_y >= 5 && g_mouse_y < 29; + if (active) { + du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT); + du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT); + } else if (hover) { + du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM); + } + du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1); + du_draw_string(&g_fb, pg_names[i], x + 8, ty, + active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); + g_pg_rect[i][0] = x; g_pg_rect[i][1] = 5; + g_pg_rect[i][2] = bw; g_pg_rect[i][3] = 24; + x += bw + 6; + } + /* 自定义页 */ + for (int i = 0; i < g_cpage_count; i++) { + char label[20]; + label[0] = '0' + (char)g_cpages[i].id; + label[1] = '.'; label[2] = 0; + kstrcat(label, g_cpages[i].name, 20); + int bw = kstrlen(label) * (int)DU_ASCII_STEP + 28; + int active = (g_page == g_cpages[i].id); + int hover = g_mouse_x >= x && g_mouse_x < x + bw && + g_mouse_y >= 5 && g_mouse_y < 29; + if (active) { + du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT); + } else if (hover) { + du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM); + } + du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1); + du_draw_string(&g_fb, label, x + 6, ty, + active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); + du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP); + g_pg_rect[4 + i][0] = x; g_pg_rect[4 + i][1] = 5; + g_pg_rect[4 + i][2] = bw; g_pg_rect[4 + i][3] = 24; + g_pg_rm_rect[4 + i][0] = x + bw - 20; g_pg_rm_rect[4 + i][1] = 5; + g_pg_rm_rect[4 + i][2] = 20; g_pg_rm_rect[4 + i][3] = 24; + x += bw + 6; + } + /* pg-add "+" */ + if (g_cpage_count < MAX_CPAGES) { + g_pgadd_rect[0] = x; g_pgadd_rect[1] = 5; + g_pgadd_rect[2] = 26; g_pgadd_rect[3] = 24; + int hover = g_mouse_x >= x && g_mouse_x < x + 26 && + g_mouse_y >= 5 && g_mouse_y < 29; + if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM); + du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1); + du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP); + } else { + g_pgadd_rect[2] = 0; + } + + /* 右侧:credit-top + PG 时钟(对应 Kate "© Deaicup Studio" + "PG n") */ + char pgclk[8]; + pgclk[0] = 'P'; pgclk[1] = 'G'; pgclk[2] = ' '; + pgclk[3] = '0' + (char)(g_page % 10); pgclk[4] = 0; + const char *credit = "(C) DEAICUP STUDIO"; + int cl = kstrlen(credit) + 1 + kstrlen(pgclk); + du_draw_string(&g_fb, credit, W - 12 - cl * (int)DU_ASCII_STEP, ty, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + du_draw_string(&g_fb, pgclk, W - 12 - kstrlen(pgclk) * (int)DU_ASCII_STEP, ty, + KS_ACCENT2, 0, DU_ASCII_STEP); +} + +/* ============================================================ + * 滑块(VOL / BRIGHT) + * ============================================================ */ + +static void draw_slider(int x, int y, int w, int val, int rect_out[4]) { + du_fill_rect(&g_fb, x, y + 5, w, 3, KS_BG_TERTIARY); + du_rect_outline(&g_fb, x, y + 4, w, 5, KS_BORDER, 1); + int fw = w * val / 100; + if (fw > 0) du_fill_rect(&g_fb, x, y + 5, fw, 3, KS_ACCENT); + int kx = x + fw - 3; + if (kx < x) kx = x; + if (kx > x + w - 6) kx = x + w - 6; + du_fill_rect(&g_fb, kx, y, 6, 13, KS_ACCENT); + du_rect_outline(&g_fb, kx, y, 6, 13, KS_ACCENT, 1); + rect_out[0] = x; rect_out[1] = y - 2; + rect_out[2] = w; rect_out[3] = 17; +} + +/* ============================================================ + * 状态栏(DASHBOARD 底部 32px) + * ============================================================ */ + +static void draw_statusbar_full(void) { + int W = (int)g_fb_w; + int y = (int)g_fb_h - KATE_STATUSBAR_H; + du_fill_rect(&g_fb, 0, y, W, KATE_STATUSBAR_H, 0xFF051828u); + du_divider_h(&g_fb, 0, y, W, KS_BORDER); + + int x = 12; + int ty = y + (KATE_STATUSBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1; + + /* 时钟(日期 · 秒级时间,accent2) */ + int yy, mo, dd, hh, mi, ss; + rtc_read(&yy, &mo, &dd, &hh, &mi, &ss); + char clk[32]; + int cp2 = 0; + clk[cp2++] = '2'; clk[cp2++] = '0'; + clk[cp2++] = '0' + (char)(yy / 10); clk[cp2++] = '0' + (char)(yy % 10); + clk[cp2++] = '-'; + clk[cp2++] = '0' + (char)(mo / 10); clk[cp2++] = '0' + (char)(mo % 10); + clk[cp2++] = '-'; + clk[cp2++] = '0' + (char)(dd / 10); clk[cp2++] = '0' + (char)(dd % 10); + clk[cp2++] = ' '; clk[cp2++] = '.'; clk[cp2++] = ' '; + char hms[9]; + fmt_hms(hms, hh * 3600 + mi * 60 + ss); + for (int i = 0; hms[i]; i++) clk[cp2++] = hms[i]; + clk[cp2] = 0; + du_draw_string(&g_fb, clk, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP); + x += cp2 * (int)DU_ASCII_STEP + 18; + + /* UP 计时 */ + { + int up = hh * 3600 + mi * 60 + ss - g_rtc_boot_sec; + if (up < 0) up += 86400; + char upb[16]; + kstrcpy(upb, "UP ", 16); + char uh[9]; + fmt_hms(uh, up); + kstrcat(upb, uh, 16); + du_draw_string(&g_fb, upb, x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); + x += kstrlen(upb) * (int)DU_ASCII_STEP + 18; + } + + /* VOL 滑块 */ + du_draw_string(&g_fb, "VOL", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); + x += 3 * (int)DU_ASCII_STEP + 6; + draw_slider(x, y + 9, 90, g_vol, g_vol_rect); + x += 90 + 8; + { + char vb[6]; + vb[0] = '0' + (char)(g_vol / 100 % 10); + vb[1] = '0' + (char)(g_vol / 10 % 10); + vb[2] = '0' + (char)(g_vol % 10); + vb[3] = '%'; vb[4] = 0; + const char *vp = (g_vol >= 100) ? vb : (g_vol >= 10 ? vb + 1 : vb + 2); + du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP); + x += 4 * (int)DU_ASCII_STEP + 14; + } + + /* BRIGHT 滑块 */ + du_draw_string(&g_fb, "BRIGHT", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); + x += 6 * (int)DU_ASCII_STEP + 6; + draw_slider(x, y + 9, 90, g_bright, g_bri_rect); + x += 90 + 8; + { + char vb[6]; + vb[0] = '0' + (char)(g_bright / 100 % 10); + vb[1] = '0' + (char)(g_bright / 10 % 10); + vb[2] = '0' + (char)(g_bright % 10); + vb[3] = '%'; vb[4] = 0; + const char *vp = (g_bright >= 100) ? vb : (g_bright >= 10 ? vb + 1 : vb + 2); + du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP); + x += 4 * (int)DU_ASCII_STEP + 14; + } + + /* BT / WIFI */ + du_draw_string(&g_fb, "BT OFF", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); + x += 6 * (int)DU_ASCII_STEP + 14; + du_draw_string(&g_fb, "WIFI --", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* 右侧 hint */ + const char *hint = "Ctrl+Tab PAGE . Ctrl+Shift+Tab CTX"; + du_draw_string(&g_fb, hint, W - 12 - kstrlen(hint) * (int)DU_ASCII_STEP, ty, + KS_TEXT_DIM, 0, DU_ASCII_STEP); +} + +/* ============================================================ + * 页面绘制 + * ============================================================ */ + +static void draw_desktop_icons(void) { + /* desktop-empty(Kate:无快捷方式提示) */ + if (g_icon_count == 0) { + du_draw_string(&g_fb, "NO SHORTCUTS ON DESKTOP", + KATE_ICON_PAD, KATE_TOPBAR_H + KATE_PAGE_PAD + KATE_ICON_PAD + 16, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + return; + } + for (int i = 0; i < g_icon_count; i++) { + desktop_icon *ic = &g_icons[i]; + du_kate_desktop_icon(&g_fb, ic->x, ic->y, ic->label, ic->selected); + int ix = ic->x + (KATE_ICON_W - KATE_ICON_IMG) / 2; + int iy = ic->y + 10; + draw_app_icon(&g_fb, ic->app_id, ix, iy); + } +} + +/* ---- 1·DASHBOARD ---- */ +static void dash_layout(void) { + int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_STATUSBAR_H; + int pad = 8, gap = 8; + int avail_w = (int)g_fb_w - 2 * pad; + int avail_h = bot - top - 2 * pad; + int left_w = avail_w * 42 / 100; + int right_x = pad + left_w + gap; + int right_w = (int)g_fb_w - pad - right_x; + int row_h = (avail_h - gap) / 2; + + g_dash_rect[0][0] = pad; g_dash_rect[0][1] = top + pad; + g_dash_rect[0][2] = left_w; g_dash_rect[0][3] = row_h; + g_dash_rect[1][0] = pad; g_dash_rect[1][1] = top + pad + row_h + gap; + g_dash_rect[1][2] = left_w; g_dash_rect[1][3] = row_h; + g_dash_rect[2][0] = right_x; g_dash_rect[2][1] = top + pad; + g_dash_rect[2][2] = right_w; g_dash_rect[2][3] = row_h; + g_dash_rect[3][0] = right_x; g_dash_rect[3][1] = top + pad + row_h + gap; + g_dash_rect[3][2] = right_w; g_dash_rect[3][3] = row_h; +} + +static void draw_dashboard(void) { + dash_layout(); + const char *headers[4] = { "TERM-01", "TERM-02", 0, "FILES" }; + char ed_hdr[40]; + editor_state *ed = (editor_state *)ADDR_EDDASH; + kstrcpy(ed_hdr, "EDITOR . ", 40); + kstrcat(ed_hdr, ed->has_file ? ed->disp : "untitled", 40); + headers[2] = ed_hdr; + + for (int i = 0; i < 4; i++) { + int x = g_dash_rect[i][0], y = g_dash_rect[i][1]; + int w = g_dash_rect[i][2], h = g_dash_rect[i][3]; + du_kate_panel(&g_fb, x, y, w, h, headers[i]); + if (g_dash_focus == i) { + du_rect_outline(&g_fb, x, y, w, h, KS_ACCENT, 2); } } - /* 任务栏 */ - draw_taskbar(); + /* EDITOR 头 SAVE 按钮(仅关联文件后可用) */ + { + int ex = g_dash_rect[2][0], ey = g_dash_rect[2][1], ew = g_dash_rect[2][2]; + int bw = 52, bh = 18; + int bx = ex + ew - bw - 6, by = ey + 3; + g_save_rect[0] = bx; g_save_rect[1] = by; + g_save_rect[2] = bw; g_save_rect[3] = bh; + u32 fg = ed->has_file ? KS_ACCENT : KS_TEXT_DIM; + int hover = ed->has_file && + g_mouse_x >= bx && g_mouse_x < bx + bw && + g_mouse_y >= by && g_mouse_y < by + bh; + if (hover) du_fill_rect(&g_fb, bx, by, bw, bh, KS_ACCENT_DIM); + du_rect_outline(&g_fb, bx, by, bw, bh, ed->has_file ? KS_BORDER : KS_BORDER_DIM, 1); + du_draw_string(&g_fb, "SAVE", bx + 5, by + 1, fg, 0, DU_ASCII_STEP); + } + + /* 面板内容 */ + bash_draw_to((bash_state *)ADDR_TERM1, + g_dash_rect[0][0] + 2, g_dash_rect[0][1] + KATE_PANEL_HEADER_H + 2, + g_dash_rect[0][2] - 4, g_dash_rect[0][3] - KATE_PANEL_HEADER_H - 4); + + bash_draw_to((bash_state *)ADDR_TERM2, + g_dash_rect[1][0] + 2, g_dash_rect[1][1] + KATE_PANEL_HEADER_H + 2, + g_dash_rect[1][2] - 4, g_dash_rect[1][3] - KATE_PANEL_HEADER_H - 4); + + editor_draw_to(ed, + g_dash_rect[2][0] + 2, g_dash_rect[2][1] + KATE_PANEL_HEADER_H + 2, + g_dash_rect[2][2] - 4, g_dash_rect[2][3] - KATE_PANEL_HEADER_H - 4); + + /* FILES 面板内容 */ + { + int fx = g_dash_rect[3][0] + 2, fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2; + int fw = g_dash_rect[3][2] - 4, fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4; + du_fill_rect(&g_fb, fx, fy, fw, fh, KS_BG_PRIMARY); + + char path_buf[32]; + kstrcpy(path_buf, "FAT32:/ROOT (", 32); + int pl = kstrlen(path_buf); + if (g_file_count >= 10) path_buf[pl++] = '0' + (char)(g_file_count / 10); + path_buf[pl++] = '0' + (char)(g_file_count % 10); + path_buf[pl++] = ')'; path_buf[pl] = 0; + du_draw_string(&g_fb, path_buf, fx + 8, fy + 3, KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP); + du_divider_h(&g_fb, fx, fy + 22, fw, KS_BORDER); + + int row_h = 22; + int max_rows = (fh - 24) / row_h; + for (int i = 0; i < g_file_count && i < max_rows; i++) { + int ry = fy + 24 + i * row_h; + if (i == g_file_sel) { + du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM); + } + int hover = (g_mouse_x >= fx && g_mouse_x < fx + fw && + g_mouse_y >= ry && g_mouse_y < ry + row_h); + if (hover && i != g_file_sel) { + du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM); + } + u32 fg = (i == g_file_sel) ? KS_ACCENT : KS_TEXT_PRIMARY; + du_draw_string(&g_fb, g_files[i].disp, fx + 8, ry + 2, fg, 0, DU_ASCII_STEP); + char sz[12]; + u32 kb = g_files[i].size / 1024; + int sp = 0; + if (kb >= 100) { sz[sp++] = '0' + (char)(kb / 100 % 10); } + if (kb >= 10) { sz[sp++] = '0' + (char)(kb / 10 % 10); } + sz[sp++] = '0' + (char)(kb % 10); + sz[sp++] = 'K'; sz[sp] = 0; + du_draw_string(&g_fb, sz, fx + fw - sp * (int)DU_ASCII_STEP - 8, ry + 2, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + } + if (!g_files_loaded) { + du_draw_string(&g_fb, "NO BLOCK DEVICE", fx + 8, fy + 28, + KS_DANGER, KS_BG_PRIMARY, DU_ASCII_STEP); + } else if (g_file_count == 0) { + du_draw_string(&g_fb, "(empty)", fx + 8, fy + 28, + KS_TEXT_DIM, KS_BG_PRIMARY, DU_ASCII_STEP); + } + } + + draw_statusbar_full(); +} + +/* ---- 2·IDE ---- */ +static int g_ide_attached = 0; + +static void draw_ide(void) { + int W = (int)g_fb_w; + int top = KATE_TOPBAR_H; + + /* ide-tabs(36px):dot + 实例标签 + "+ NEW IDE" + ide-hint(Kate 结构) */ + du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u); + du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER); + int x = 12; + int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1; + + /* .dot(8×8 accent2 发光点) */ + du_fill_rect(&g_fb, x, top + 14, 8, 8, KS_ACCENT2); + x += 16; + + /* 实例标签 EDITOR ×(Kate .ide-tab) */ + { + const char *label = "EDITOR"; + int bw = kstrlen(label) * (int)DU_ASCII_STEP + 32; + if (g_ide_attached) { + du_fill_rect(&g_fb, x, top + 5, bw, 26, KS_ACCENT); + du_kate_glow_border(&g_fb, x, top + 5, bw, 26, KS_ACCENT); + } + du_rect_outline(&g_fb, x, top + 5, bw, 26, + g_ide_attached ? KS_ACCENT : KS_BORDER, 1); + du_draw_string(&g_fb, label, x + 8, ty, + g_ide_attached ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); + du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP); + g_ide_tabx[0] = x + bw - 20; g_ide_tabx[1] = top + 5; + g_ide_tabx[2] = 20; g_ide_tabx[3] = 26; + x += bw + 8; + } + + /* "+ NEW IDE" 按钮(Kate .btn,透明底 + 边框 + accent 字) */ + { + const char *nb = "+ NEW IDE"; + int bw = kstrlen(nb) * (int)DU_ASCII_STEP + 20; + g_ide_new_rect[0] = x; g_ide_new_rect[1] = top + 7; + g_ide_new_rect[2] = bw; g_ide_new_rect[3] = 22; + int hover = g_mouse_x >= x && g_mouse_x < x + bw && + g_mouse_y >= top + 7 && g_mouse_y < top + 29; + if (hover) du_fill_rect(&g_fb, x, top + 7, bw, 22, KS_ACCENT_DIM); + du_rect_outline(&g_fb, x, top + 7, bw, 22, KS_BORDER, 1); + du_draw_string(&g_fb, nb, x + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP); + x += bw + 8; + } + + /* ide-hint */ + du_draw_string(&g_fb, "CTRL+SHIFT+TAB CTX . x DETACH . DBL-CLICK HOST ATTACH", + x + 8, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* page-credit(对应 Kate "由 Deaicup 工作室制作") */ + du_draw_string(&g_fb, "DEAICUP STUDIO", + W - 12 - 14 * (int)DU_ASCII_STEP, ty + 1, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* ide-host(虚线,margin 8px) */ + int hx = 8, hy = top + 36 + 8; + int hw = W - 16, hh = (int)g_fb_h - hy - 8; + g_ide_host[0] = hx; g_ide_host[1] = hy; + g_ide_host[2] = hw; g_ide_host[3] = hh; + draw_dashed_rect(hx, hy, hw, hh, KS_BORDER); + + if (g_ide_attached) { + editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4); + } else { + /* embed-empty(Kate:未启动 VSCode 提示) */ + draw_centered("EDITOR NOT ATTACHED . DOUBLE-CLICK OR + NEW IDE", + W / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0); + } +} + +/* ---- 3·IM ---- */ +static int g_im_view = 0; /* 0=split(WECHAT/QQ) 1=full(WECOM),对应 Kate imView */ + +/* Kate .missing-card:h2 + p + a.btn 下载按钮 */ +static void draw_missing_card(int sx, int sy, int sw, int sh, + const char *title, const char *desc, + const char *btn) { + int cw = 340, ch = 150; + int cx = sx + (sw - cw) / 2, cy = sy + (sh - ch) / 2; + du_kate_panel(&g_fb, cx, cy, cw, ch, 0); + draw_centered(title, sx + sw / 2, cy + 28, KS_ACCENT, 0); + draw_centered(desc, sx + sw / 2, cy + 56, KS_TEXT_DIM, 0); + /* .btn 下载按钮(视觉复刻,Deshab 无网络安装渠道) */ + int bw = kstrlen(btn) * (int)DU_ASCII_STEP + 20; + int bx = sx + sw / 2 - bw / 2, by = cy + 88; + int hover = g_mouse_x >= bx && g_mouse_x < bx + bw && + g_mouse_y >= by && g_mouse_y < by + 22; + if (hover) du_fill_rect(&g_fb, bx, by, bw, 22, KS_ACCENT_DIM); + du_rect_outline(&g_fb, bx, by, bw, 22, KS_BORDER, 1); + du_draw_string(&g_fb, btn, bx + 10, by + 3, KS_ACCENT, 0, DU_ASCII_STEP); +} + +static void draw_im(void) { + int W = (int)g_fb_w; + int top = KATE_TOPBAR_H; + + /* im-bar(36px):dot + 视图标签 + page-credit + 切换按钮(margin-left:auto) */ + du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u); + du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER); + int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1; + + /* .dot(8×8 accent2 发光点) */ + du_fill_rect(&g_fb, 12, top + 14, 8, 8, KS_ACCENT2); + + du_draw_string(&g_fb, + g_im_view ? "WECOM FULLSCREEN" : "WECHAT / QQ SPLIT", + 28, ty, KS_ACCENT, 0, DU_ASCII_STEP); + /* page-credit(对应 Kate "由 Deaicup 工作室制作") */ + du_draw_string(&g_fb, "DEAICUP STUDIO", + 28 + (g_im_view ? 16 : 17) * (int)DU_ASCII_STEP + 12, + ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* 切换按钮(对应 Kate im_toggle,Ctrl+Shift+Tab) */ + { + const char *tb = "CTRL+SHIFT+TAB SWITCH"; + int bw = kstrlen(tb) * (int)DU_ASCII_STEP + 20; + int bx = W - 12 - bw; + g_im_toggle_rect[0] = bx; g_im_toggle_rect[1] = top + 7; + g_im_toggle_rect[2] = bw; g_im_toggle_rect[3] = 22; + int hover = g_mouse_x >= bx && g_mouse_x < bx + bw && + g_mouse_y >= top + 7 && g_mouse_y < top + 29; + if (hover) du_fill_rect(&g_fb, bx, top + 7, bw, 22, KS_ACCENT_DIM); + du_rect_outline(&g_fb, bx, top + 7, bw, 22, KS_BORDER, 1); + du_draw_string(&g_fb, tb, bx + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP); + } + + if (g_im_view == 0) { + /* im-split 双槽(grid 1fr 1fr, gap/padding 8px) */ + int pad = 8, gap = 8; + int hy = top + 36 + pad; + int hh = (int)g_fb_h - hy - pad; + int sw = (W - 2 * pad - gap) / 2; + for (int i = 0; i < 2; i++) { + int sx = pad + i * (sw + gap); + draw_dashed_rect(sx, hy, sw, hh, KS_BORDER); + } + draw_missing_card(pad, hy, sw, hh, + "WECHAT NOT DETECTED", + "IM MODULE NOT AVAILABLE IN DESHAB", + "DOWNLOAD WECHAT"); + draw_missing_card(pad + sw + gap, hy, sw, hh, + "QQ NOT DETECTED", + "IM MODULE NOT AVAILABLE IN DESHAB", + "DOWNLOAD QQ"); + } else { + /* im-full 单槽(margin 8px) */ + int hx = 8, hy = top + 36 + 8; + int hw = W - 16, hh = (int)g_fb_h - hy - 8; + draw_dashed_rect(hx, hy, hw, hh, KS_BORDER); + draw_missing_card(hx, hy, hw, hh, + "WECOM NOT DETECTED", + "IM MODULE NOT AVAILABLE IN DESHAB", + "DOWNLOAD WECOM"); + } +} + +/* ---- 4·DESKTOP ---- */ +static void draw_desktop_page(void) { + /* 图标网格 */ + draw_desktop_icons(); + + /* 窗口(当前工作区,从底到顶) */ + for (int i = 0; i < g_win_count; i++) { + desktop_window *w = &g_windows[i]; + if (w->minimized || !w->visible) continue; + if (w->ws != g_ws_cur) continue; + + draw_window_frame(w); + + if (w->app_id >= 0 && w->app_id < g_app_count && + g_apps[w->app_id].on_draw && w->app_state) { + app_ctx ctx; + fill_app_ctx(&ctx, w); + g_apps[w->app_id].on_draw(w->app_state, &ctx); + } + + du_kate_scanlines(&g_fb, w->x + 1, w->y + KATE_TITLEBAR_H, + w->w - 2, w->h - KATE_TITLEBAR_H - 1); + } + + /* 任务栏(40px) */ + int W = (int)g_fb_w; + int tb_y = (int)g_fb_h - KATE_TASKBAR_H; + du_fill_rect_gradient(&g_fb, 0, tb_y, W, KATE_TASKBAR_H, + 0xFF0A2840u, KS_BG_PRIMARY); + du_divider_h(&g_fb, 0, tb_y, W, KS_BORDER); + + int x = 8; + int ty = tb_y + (KATE_TASKBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1; + + /* tb-item per window(当前工作区) */ + for (int i = 0; i < g_win_count; i++) { + desktop_window *w = &g_windows[i]; + if (!w->visible || w->ws != g_ws_cur) { + g_tb_rect[i][2] = 0; g_tb_close_rect[i][2] = 0; + continue; + } + char t[16]; + int tl = 0; + while (w->title[tl] && tl < 11) { t[tl] = w->title[tl]; tl++; } + if (w->title[tl]) { t[tl-1] = '.'; t[tl] = '.'; tl++; } + t[tl] = 0; + int bw = tl * (int)DU_ASCII_STEP + 34; + if (bw > 150) bw = 150; + int active = w->focused; + int hover = g_mouse_x >= x && g_mouse_x < x + bw && + g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34; + if (active) du_fill_rect(&g_fb, x, tb_y + 6, bw, 28, KS_ACCENT_DIM); + else if (hover) du_fill_rect(&g_fb, x, tb_y + 6, bw, 28, KS_ACCENT_DIM); + du_rect_outline(&g_fb, x, tb_y + 6, bw, 28, + active ? KS_ACCENT : KS_BORDER, 1); + du_draw_string(&g_fb, t, x + 6, ty, + active ? KS_ACCENT : KS_TEXT_PRIMARY, 0, DU_ASCII_STEP); + du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); + g_tb_rect[i][0] = x; g_tb_rect[i][1] = tb_y + 6; + g_tb_rect[i][2] = bw; g_tb_rect[i][3] = 28; + g_tb_close_rect[i][0] = x + bw - 20; g_tb_close_rect[i][1] = tb_y + 6; + g_tb_close_rect[i][2] = 20; g_tb_close_rect[i][3] = 28; + x += bw + 6; + if (x > W - 260) break; + } + + /* Winux-Kate 任务栏右组(margin-left:auto): + * [+ ADOPT 虚框] [tb-credit 署名] [EXIT 危险色] */ + const char *credit = "DEAICUP STUDIO"; + const char *quit = "EXIT"; + const char *adopt = "+ ADOPT"; + + int qw = 60; + int qx = W - qw - 8; + int credit_w = kstrlen(credit) * (int)DU_ASCII_STEP; + int cx = qx - 8 - credit_w; + int aw = kstrlen(adopt) * (int)DU_ASCII_STEP + 16; + int ax = cx - 8 - aw; + + /* tb-adopt:虚线 accent 边框 */ + g_tb_adopt_rect[0] = ax; g_tb_adopt_rect[1] = tb_y + 6; + g_tb_adopt_rect[2] = aw; g_tb_adopt_rect[3] = 28; + { + int hover = g_mouse_x >= ax && g_mouse_x < ax + aw && + g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34; + if (hover) du_fill_rect(&g_fb, ax, tb_y + 6, aw, 28, KS_ACCENT_DIM); + draw_dashed_rect(ax, tb_y + 6, aw, 28, KS_ACCENT); + du_draw_string(&g_fb, adopt, ax + 8, ty, KS_ACCENT, 0, DU_ASCII_STEP); + } + + /* tb-credit */ + du_draw_string(&g_fb, credit, cx, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* tb-quit */ + g_tb_quit_rect[0] = qx; g_tb_quit_rect[1] = tb_y + 6; + g_tb_quit_rect[2] = qw; g_tb_quit_rect[3] = 28; + { + int hover = g_mouse_x >= qx && g_mouse_x < qx + qw && + g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34; + if (hover) du_fill_rect(&g_fb, qx, tb_y + 6, qw, 28, 0x30FF4D6Du); + du_rect_outline(&g_fb, qx, tb_y + 6, qw, 28, KS_DANGER, 1); + du_draw_string(&g_fb, quit, qx + (qw - 4 * (int)DU_ASCII_STEP) / 2, ty, + KS_DANGER, 0, DU_ASCII_STEP); + } +} + +/* ---- 5+/CUSTOM ---- */ +static int g_cp_new_rect[4]; /* 自定义页 "+ NEW INSTANCE" 按钮 */ + +static void draw_custom_page(int cp_idx) { + int W = (int)g_fb_w; + int top = KATE_TOPBAR_H; + cpage *cp = &g_cpages[cp_idx]; + + /* ide-tabs 栏(36px):dot + 页名 + "+ NEW INSTANCE" + ide-hint(Kate 结构) */ + du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u); + du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER); + int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1; + + /* .dot + 页名 */ + du_fill_rect(&g_fb, 12, top + 14, 8, 8, KS_ACCENT2); + du_draw_string(&g_fb, cp->name, 28, ty, KS_ACCENT, 0, DU_ASCII_STEP); + int x = 28 + kstrlen(cp->name) * (int)DU_ASCII_STEP + 12; + + /* "+ NEW INSTANCE" 按钮(对应 Kate custom launch_custom_new) */ + { + const char *nb = "+ NEW INSTANCE"; + int bw = kstrlen(nb) * (int)DU_ASCII_STEP + 20; + g_cp_new_rect[0] = x; g_cp_new_rect[1] = top + 7; + g_cp_new_rect[2] = bw; g_cp_new_rect[3] = 22; + int hover = g_mouse_x >= x && g_mouse_x < x + bw && + g_mouse_y >= top + 7 && g_mouse_y < top + 29; + if (hover) du_fill_rect(&g_fb, x, top + 7, bw, 22, KS_ACCENT_DIM); + du_rect_outline(&g_fb, x, top + 7, bw, 22, KS_BORDER, 1); + du_draw_string(&g_fb, nb, x + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP); + x += bw + 8; + } + + /* ide-hint */ + du_draw_string(&g_fb, "DBL-CLICK HOST LAUNCH . EXTERNAL ELF FULLSCREEN", + x + 8, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* page-credit */ + du_draw_string(&g_fb, "DEAICUP STUDIO", + W - 12 - 14 * (int)DU_ASCII_STEP, ty + 1, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* ide-host(虚线,margin 8px) */ + int hx = 8, hy = top + 36 + 8; + int hw = W - 16, hh = (int)g_fb_h - hy - 8; + g_cp_host[0] = hx; g_cp_host[1] = hy; + g_cp_host[2] = hw; g_cp_host[3] = hh; + draw_dashed_rect(hx, hy, hw, hh, KS_BORDER); + + /* embed-empty(Kate:正在启动提示) */ + char msg[40]; + kstrcpy(msg, "DOUBLE-CLICK TO LAUNCH ", 40); + kstrcat(msg, cp->name, 40); + draw_centered(msg, hx + hw / 2, hy + hh / 2 - 12, KS_TEXT_DIM, 0); + + char sub[40]; + kstrcpy(sub, "ELF: ", 40); + { + char disp[13]; + f32_name_from_83(cp->elf11, disp); + kstrcat(sub, disp, 40); + } + draw_centered(sub, hx + hw / 2, hy + hh / 2 + 12, KS_ACCENT, 0); +} + +/* ============================================================ + * 任务视图 overlay + * ============================================================ */ + +static void draw_taskview(void) { + int W = (int)g_fb_w; + + /* 背景暗化(全屏 blend,模态 overlay 下文字可读性优先) */ + for (u64 ry = 0; ry < g_fb_h; ry++) { + u32 *line = (u32 *)((u8 *)g_fb.fb + ry * g_fb_pitch); + for (u64 rx = 0; rx < g_fb_w; rx++) { + line[rx] = du_blend(line[rx], KS_BG_PRIMARY, 220); + } + } + + /* tv-header */ + du_draw_string(&g_fb, "TASK VIEW", 60, 44, KS_ACCENT, 0, DU_ASCII_STEP + 4); + du_draw_string(&g_fb, "CLICK TO SWITCH . ESC CLOSE . x CLOSE WS", + 60 + 9 * ((int)DU_ASCII_STEP + 4) + 20, 48, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + du_divider_h(&g_fb, 60, 76, W - 120, KS_BORDER); + + /* tv-grid 卡片 */ + int cw = 260, ch = 170, gap = 20; + int cols = (W - 120 + gap) / (cw + gap); + if (cols < 1) cols = 1; + for (int i = 0; i <= g_ws_count; i++) { + int row = i / cols, col = i % cols; + int x = 60 + col * (cw + gap); + int y = 96 + row * (ch + gap); + g_tv_card[i][0] = x; g_tv_card[i][1] = y; + g_tv_card[i][2] = cw; g_tv_card[i][3] = ch; + + if (i == g_ws_count) { + /* 新建工作区卡片(虚线 +) */ + draw_dashed_rect(x, y, cw, ch, KS_ACCENT2); + du_draw_string(&g_fb, "+", x + cw / 2 - 6, y + ch / 2 - 24, + KS_ACCENT2, 0, DU_ASCII_STEP); + draw_centered("NEW WORKSPACE", x + cw / 2, y + ch / 2 + 8, + KS_ACCENT2, 0); + continue; + } + + int active = (i == g_ws_cur); + int hover = g_mouse_x >= x && g_mouse_x < x + cw && + g_mouse_y >= y && g_mouse_y < y + ch; + du_fill_rect(&g_fb, x, y, cw, ch, KS_BG_SECONDARY); + du_rect_outline(&g_fb, x, y, cw, ch, + active ? KS_ACCENT2 : (hover ? KS_ACCENT : KS_BORDER), 1); + if (active) du_kate_glow_border(&g_fb, x, y, cw, ch, KS_ACCENT2); + + /* tv-card-top */ + du_draw_string(&g_fb, g_ws[i].name, x + 12, y + 10, KS_ACCENT, 0, DU_ASCII_STEP); + du_divider_h(&g_fb, x, y + 34, cw, KS_BORDER); + /* × close */ + g_tv_close[i][0] = x + cw - 26; g_tv_close[i][1] = y + 4; + g_tv_close[i][2] = 22; g_tv_close[i][3] = 26; + du_draw_string(&g_fb, "x", x + cw - 20, y + 8, KS_TEXT_DIM, 0, DU_ASCII_STEP); + + /* tv-stat */ + char nbuf[4]; + int wc = ws_win_count(i); + nbuf[0] = '0' + (char)(wc % 10); nbuf[1] = 0; + draw_centered(nbuf, x + cw / 4, y + 70, KS_ACCENT2, 0); + draw_centered("WINS", x + cw / 4, y + 96, KS_TEXT_DIM, 0); + char pbuf[6]; + kstrcpy(pbuf, "PG", 6); + pbuf[2] = '0' + (char)(g_ws[i].page % 10); pbuf[3] = 0; + draw_centered(pbuf, x + cw * 3 / 4, y + 70, KS_ACCENT2, 0); + draw_centered("PAGE", x + cw * 3 / 4, y + 96, KS_TEXT_DIM, 0); + + /* badge CURRENT */ + if (active) { + int bx = x + cw - 84, by = y + 6; + du_fill_rect(&g_fb, bx, by, 56, 14, KS_ACCENT2); + du_draw_string(&g_fb, "CURRENT", bx + 2, by - 1, + KS_TEXT_INVERT, 0, DU_ASCII_STEP); + } + } +} + +/* ============================================================ + * 右键菜单(移到工作区) + * ============================================================ */ + +/* 构建"移到工作区"列表(排除当前工作区,与 Winux-Kate 一致),返回行数 */ +static int ctx_list_build(int *map) { + int n = 0; + for (int i = 0; i < g_ws_count; i++) { + if (i == g_ws_cur) continue; + map[n++] = i; + } + return n; +} + +static void draw_ctx_menu(void) { + if (!g_ctx_open) return; + int lmap[MAX_WS]; + int ln = ctx_list_build(lmap); + int rows = (ln ? ln : 1) + 1; /* 列表行(或空态行) + 新建行 */ + int mw = 200; + int mh = 24 + rows * 22 + 8; + int x = g_ctx_x, y = g_ctx_y; + if (x + mw > (int)g_fb_w) x = (int)g_fb_w - mw - 4; + if (y + mh > (int)g_fb_h) y = (int)g_fb_h - mh - 4; + g_ctx_rect[0] = x; g_ctx_rect[1] = y; + g_ctx_rect[2] = mw; g_ctx_rect[3] = mh; + + du_fill_rect(&g_fb, x, y, mw, mh, 0xFF050A18u); + du_rect_outline(&g_fb, x, y, mw, mh, KS_ACCENT, 1); + du_kate_glow_border(&g_fb, x, y, mw, mh, KS_ACCENT); + + du_draw_string(&g_fb, "MOVE TO WORKSPACE", x + 12, y + 5, + KS_ACCENT, 0, DU_ASCII_STEP); + du_divider_h(&g_fb, x, y + 22, mw, KS_BORDER); + + int iy = y + 26; + int row = 0; + if (ln == 0) { + /* 空态(不可点击) */ + du_draw_string(&g_fb, "NO OTHER WORKSPACE", x + 12, iy + 2, + KS_TEXT_DIM, 0, DU_ASCII_STEP); + g_ctx_item[0][2] = 0; /* 禁用命中 */ + g_ctx_item_ws[0] = -2; + iy += 22; + row = 1; + } + for (int r = 0; r < ln; r++) { + int wi = lmap[r]; + int hover = g_mouse_x >= x && g_mouse_x < x + mw && + g_mouse_y >= iy && g_mouse_y < iy + 22; + if (hover) du_fill_rect(&g_fb, x + 1, iy, mw - 2, 22, KS_ACCENT_DIM); + du_draw_string(&g_fb, g_ws[wi].name, x + 12, iy + 2, + hover ? KS_ACCENT : KS_TEXT_PRIMARY, 0, DU_ASCII_STEP); + char cn[4]; + cn[0] = '0' + (char)(ws_win_count(wi) % 10); cn[1] = 0; + du_draw_string(&g_fb, cn, x + mw - 24, iy + 2, KS_TEXT_DIM, 0, DU_ASCII_STEP); + g_ctx_item[row][0] = x; g_ctx_item[row][1] = iy; + g_ctx_item[row][2] = mw; g_ctx_item[row][3] = 22; + g_ctx_item_ws[row] = wi; + iy += 22; + row++; + } + /* + NEW WORKSPACE */ + du_divider_h(&g_fb, x, iy, mw, KS_BORDER); + int hover = g_mouse_x >= x && g_mouse_x < x + mw && + g_mouse_y >= iy && g_mouse_y < iy + 22; + if (hover) du_fill_rect(&g_fb, x + 1, iy, mw - 2, 22, KS_ACCENT_DIM); + du_draw_string(&g_fb, "+ NEW WORKSPACE", x + 12, iy + 2, + KS_ACCENT2, 0, DU_ASCII_STEP); + g_ctx_item[row][0] = x; g_ctx_item[row][1] = iy; + g_ctx_item[row][2] = mw; g_ctx_item[row][3] = 22; + g_ctx_item_ws[row] = -1; + /* 清除残余行命中 */ + for (int r = row + 1; r <= MAX_WS; r++) g_ctx_item[r][2] = 0; +} + +/* ============================================================ + * 全屏重绘 + * ============================================================ */ + +/* 全局亮度(BRIGHT 滑块真实调暗) */ +static void apply_brightness(void) { + if (g_bright >= 100) return; + u32 f = (u32)g_bright; + for (u64 y = 0; y < g_fb_h; y++) { + u32 *line = (u32 *)((u8 *)g_fb.fb + y * g_fb_pitch); + for (u64 x = 0; x < g_fb_w; x++) { + u32 c = line[x]; + u32 b = ((c >> 16) & 0xFF) * f / 100; + u32 g = ((c >> 8) & 0xFF) * f / 100; + u32 r = (c & 0xFF) * f / 100; + line[x] = 0xFF000000u | (b << 16) | (g << 8) | r; + } + } +} + +static void redraw_all(void) { + /* 深空黑渐变背景 */ + du_fill_bg_gradient(&g_fb, KS_BG_PRIMARY, KS_BG_TERTIARY); + + /* 顶栏 */ + draw_topbar(); + + /* 页面 */ + if (g_page == 1) draw_dashboard(); + else if (g_page == 2) draw_ide(); + else if (g_page == 3) draw_im(); + else if (g_page == 4) draw_desktop_page(); + else { + int idx = -1; + for (int i = 0; i < g_cpage_count; i++) + if (g_cpages[i].id == g_page) { idx = i; break; } + if (idx >= 0) draw_custom_page(idx); + else g_page = 1; + } + + /* 任务视图 overlay */ + if (g_taskview) draw_taskview(); + + /* 右键菜单(最顶) */ + if (g_ctx_open) draw_ctx_menu(); + + /* 亮度调整 */ + apply_brightness(); /* 鼠标光标 */ cursor_save_bg(g_mouse_x, g_mouse_y); @@ -1385,7 +2635,439 @@ static void redraw_all(void) { } /* ============================================================ - * 主循环 + * 工作区 / 页面管理 + * ============================================================ */ + +static void ws_switch(int id) { + if (id < 0 || id >= g_ws_count || id == g_ws_cur) { g_taskview = 0; return; } + g_ws[g_ws_cur].page = g_page; /* 记住离开时的页面 */ + g_ws_cur = id; + g_page = g_ws[id].page; + g_taskview = 0; +} + +static void ws_create(void) { + if (g_ws_count >= MAX_WS) return; + char *nm = g_ws[g_ws_count].name; + nm[0] = 'W'; nm[1] = 'S'; nm[2] = '-'; + nm[3] = '0' + (char)(g_ws_count + 1); nm[4] = 0; + g_ws[g_ws_count].page = 1; + g_ws_count++; +} + +/* 关闭工作区:窗口释放到关闭后的当前工作区(Kate release 语义) */ +static void ws_close(int id) { + if (g_ws_count <= 1 || id < 0 || id >= g_ws_count) return; + int cur_after = g_ws_cur; + if (g_ws_cur == id) cur_after = (id > 0) ? id - 1 : 0; + else if (g_ws_cur > id) cur_after = g_ws_cur - 1; + for (int i = 0; i < g_win_count; i++) { + if (g_windows[i].ws == id) g_windows[i].ws = cur_after; + else if (g_windows[i].ws > id) g_windows[i].ws--; + } + for (int i = id; i < g_ws_count - 1; i++) g_ws[i] = g_ws[i + 1]; + g_ws_count--; + g_ws_cur = cur_after; + g_taskview = 0; +} + +static void page_switch(int p) { + if (p < 1 || p > 4 + g_cpage_count) return; + g_page = p; +} + +/* 自定义页:为尚未挂载的外部 .elf 应用新建页面 */ +static void cpage_add(void) { + if (g_cpage_count >= MAX_CPAGES) return; + for (int a = 0; a < g_app_count; a++) { + if (!g_apps[a].elf_name) continue; + int dup = 0; + for (int i = 0; i < g_cpage_count; i++) + if (f32_neq11(g_cpages[i].elf11, g_apps[a].elf_name)) { dup = 1; break; } + if (dup) continue; + cpage *cp = &g_cpages[g_cpage_count]; + int n = kstrlen(g_apps[a].display_name); + if (n > 11) n = 11; + for (int i = 0; i < n; i++) cp->name[i] = g_apps[a].display_name[i]; + cp->name[n] = 0; + for (int i = 0; i < 11; i++) cp->elf11[i] = g_apps[a].elf_name[i]; + cp->id = 5 + g_cpage_count; + g_cpage_count++; + g_page = cp->id; + return; + } +} + +static void cpage_remove(int idx) { + if (idx < 0 || idx >= g_cpage_count) return; + int removed_page = g_cpages[idx].id; + for (int i = idx; i < g_cpage_count - 1; i++) g_cpages[i] = g_cpages[i + 1]; + g_cpage_count--; + for (int i = 0; i < g_cpage_count; i++) g_cpages[i].id = 5 + i; + if (g_page == removed_page) g_page = 1; + else if (g_page > removed_page) g_page--; +} + +/* ============================================================ + * 事件处理 + * ============================================================ */ + +static int rect_hit(const int r[4], int mx, int my) { + return r[2] > 0 && mx >= r[0] && mx < r[0] + r[2] && + my >= r[1] && my < r[1] + r[3]; +} + +static void slider_set(const int rect[4], int *val, int mx) { + int v = (mx - rect[0]) * 100 / rect[2]; + if (v < 0) v = 0; + if (v > 100) v = 100; + *val = v; +} + +/* 双击判定(TSC 半秒 + 8px 容差),不更新状态 */ +static int dbl_check(int mx, int my) { + if (!g_tsc_per_sec) return 0; + u64 now = rdtsc(); + int dx = mx - g_last_click_x, dy = my - g_last_click_y; + return (now - g_last_click_tsc) < g_tsc_per_sec / 2 && + dx > -8 && dx < 8 && dy > -8 && dy < 8; +} + +/* ---- 1·DASHBOARD 点击 ---- */ +static int dash_click(int mx, int my, int dbl) { + editor_state *ed = (editor_state *)ADDR_EDDASH; + + /* SAVE 按钮 */ + if (ed->has_file && rect_hit(g_save_rect, mx, my)) { editor_save(); return 1; } + + /* 面板聚焦 */ + for (int i = 0; i < 4; i++) { + if (!rect_hit(g_dash_rect[i], mx, my)) continue; + g_dash_focus = i; + if (i == 3) { + /* FILES 行命中 / 双击打开 */ + int fx = g_dash_rect[3][0] + 2; + int fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2; + int fw = g_dash_rect[3][2] - 4; + int fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4; + if (mx >= fx && mx < fx + fw && my >= fy + 24) { + int max_rows = (fh - 24) / 22; + int row = (my - fy - 24) / 22; + if (row >= 0 && row < max_rows && row < g_file_count) { + if (dbl && row == g_file_sel) files_open(row); + else g_file_sel = row; + } + } + } + return 1; + } + + /* 状态栏滑块 */ + if (rect_hit(g_vol_rect, mx, my)) { + g_drag_slider = 1; + slider_set(g_vol_rect, &g_vol, mx); + return 1; + } + if (rect_hit(g_bri_rect, mx, my)) { + g_drag_slider = 2; + slider_set(g_bri_rect, &g_bright, mx); + return 1; + } + return 0; +} + +/* ---- 2·IDE 点击 ---- */ +static int ide_click(int mx, int my, int dbl) { + if (g_ide_attached && rect_hit(g_ide_tabx, mx, my)) { g_ide_attached = 0; return 1; } + if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) { g_ide_attached = 1; return 1; } + if (!g_ide_attached && dbl && rect_hit(g_ide_host, mx, my)) { + g_ide_attached = 1; + return 1; + } + return 0; +} + +/* ---- 4·DESKTOP 点击 ---- */ +static int desktop_click(int mx, int my, int dbl) { + int tb_y = (int)g_fb_h - KATE_TASKBAR_H; + + /* 任务栏 */ + if (my >= tb_y) { + if (rect_hit(g_tb_quit_rect, mx, my)) { g_quit = 1; return 1; } + if (rect_hit(g_tb_adopt_rect, mx, my)) { + /* 收纳窗口:全部工作区窗口收归当前工作区并还原最小化 */ + for (int i = 0; i < g_win_count; i++) { + g_windows[i].ws = g_ws_cur; + g_windows[i].minimized = 0; + } + return 1; + } + for (int i = 0; i < g_win_count; i++) { + if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue; + if (rect_hit(g_tb_close_rect[i], mx, my)) { + win_destroy(&g_windows[i]); + } else { + win_focus(&g_windows[i]); + } + return 1; + } + return 0; + } + + /* 窗口 */ + desktop_window *w = win_find_at(mx, my); + if (w) { + int wid = w->id; + win_focus(w); + w = win_find(wid); /* focus 重排后重新定位 */ + if (!w) return 1; + if (win_hit_close_btn(w, mx, my)) { win_destroy(w); return 1; } + if (win_hit_titlebar(w, mx, my)) { + g_dragging = 1; + g_drag_win = w->id; + g_drag_off_x = mx - w->x; + g_drag_off_y = my - w->y; + return 1; + } + /* 客户区事件 → 应用 */ + if (w->app_id >= 0 && w->app_id < g_app_count && + g_apps[w->app_id].on_event && w->app_state) { + desktop_event dev; + dev.type = EV_MOUSE_DOWN; + dev.mx = mx; dev.my = my; + dev.button = 0; dev.scancode = 0; + dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt; + app_ctx actx; + fill_app_ctx(&actx, w); + g_apps[w->app_id].on_event(w->app_state, &actx, &dev); + } + return 1; + } + + /* 图标(单击选中 / 双击启动) */ + for (int i = 0; i < g_icon_count; i++) { + int ix = g_icons[i].x, iy = g_icons[i].y; + if (mx >= ix && mx < ix + ICON_W && my >= iy && my < iy + ICON_H) { + if (dbl && g_icons[i].selected) { + launch_app(g_icons[i].app_id); + } else { + for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0; + g_icons[i].selected = 1; + } + return 1; + } + } + + /* 空白:取消选择 */ + for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0; + return 1; +} + +/* ---- 左键按下 ---- */ +static int on_left_press(void) { + int mx = g_mouse_x, my = g_mouse_y; + int dbl = dbl_check(mx, my); + g_last_click_tsc = rdtsc(); + g_last_click_x = mx; g_last_click_y = my; + + /* 任务视图 overlay(模态) */ + if (g_taskview) { + for (int i = 0; i <= g_ws_count; i++) { + if (!rect_hit(g_tv_card[i], mx, my)) continue; + if (i == g_ws_count) ws_create(); + else if (rect_hit(g_tv_close[i], mx, my)) ws_close(i); + else ws_switch(i); + return 1; + } + g_taskview = 0; /* 背景点击关闭 */ + return 1; + } + + /* 右键菜单(模态) */ + if (g_ctx_open) { + if (rect_hit(g_ctx_rect, mx, my)) { + for (int i = 0; i <= MAX_WS; i++) { + if (!rect_hit(g_ctx_item[i], mx, my)) continue; + int wi = g_ctx_item_ws[i]; + if (wi == -1) { + /* + NEW WORKSPACE:新建并迁移 */ + ws_create(); + desktop_window *w = win_find(g_ctx_win); + if (w && g_ws_count > 0) w->ws = g_ws_count - 1; + } else if (wi >= 0) { + desktop_window *w = win_find(g_ctx_win); + if (w) w->ws = wi; + } + break; + } + } + g_ctx_open = 0; + return 1; + } + + /* 顶栏 */ + if (my < KATE_TOPBAR_H) { + if (rect_hit(g_tvbtn_rect, mx, my)) { g_taskview = 1; return 1; } + for (int i = 0; i < g_ws_count; i++) + if (rect_hit(g_ws_rect[i], mx, my)) { ws_switch(i); return 1; } + if (rect_hit(g_wsadd_rect, mx, my)) { ws_create(); return 1; } + for (int i = 0; i < 4 + g_cpage_count; i++) { + if (i >= 4 && rect_hit(g_pg_rm_rect[i], mx, my)) { + cpage_remove(i - 4); + return 1; + } + if (rect_hit(g_pg_rect[i], mx, my)) { page_switch(i + 1); return 1; } + } + if (rect_hit(g_pgadd_rect, mx, my)) { cpage_add(); return 1; } + return 0; + } + + /* 页面内容 */ + if (g_page == 1) return dash_click(mx, my, dbl); + if (g_page == 2) return ide_click(mx, my, dbl); + if (g_page == 3) { + /* IM 切换视图按钮(对应 Kate im_toggle) */ + if (rect_hit(g_im_toggle_rect, mx, my)) { g_im_view ^= 1; return 1; } + return 0; + } + if (g_page == 4) return desktop_click(mx, my, dbl); + if (g_page >= 5) { + int launch = 0; + if (rect_hit(g_cp_new_rect, mx, my)) launch = 1; + if (dbl && rect_hit(g_cp_host, mx, my)) launch = 1; + if (launch) { + for (int i = 0; i < g_cpage_count; i++) { + if (g_cpages[i].id != g_page) continue; + launch_external_elf(g_cpages[i].elf11); + break; + } + return 1; + } + return 0; + } + return 0; +} + +/* ---- 右键按下:窗口/任务栏项 → 移到工作区菜单 ---- */ +static int on_right_press(void) { + int mx = g_mouse_x, my = g_mouse_y; + + if (g_taskview) { g_taskview = 0; return 1; } + if (g_ctx_open) { g_ctx_open = 0; return 1; } + if (g_page != 4) return 0; + + int tb_y = (int)g_fb_h - KATE_TASKBAR_H; + if (my >= tb_y) { + for (int i = 0; i < g_win_count; i++) { + if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue; + int lmap[MAX_WS]; + int ln = ctx_list_build(lmap); + int mh = 24 + ((ln ? ln : 1) + 1) * 22 + 8; + g_ctx_win = g_windows[i].id; + g_ctx_x = mx; + g_ctx_y = my - mh; /* 菜单向上展开 */ + if (g_ctx_y < KATE_TOPBAR_H) g_ctx_y = my; + g_ctx_open = 1; + return 1; + } + return 0; + } + + desktop_window *w = win_find_at(mx, my); + if (w) { + g_ctx_win = w->id; + g_ctx_x = mx; g_ctx_y = my; + g_ctx_open = 1; + return 1; + } + return 0; +} + +/* ---- 键盘 ---- */ +static int handle_key(u8 sc) { + if (sc == 0xE0) { g_e0 = 1; return 0; } + if (sc == 0x2A || sc == 0x36) { g_shift = 1; return 0; } + if (sc == 0xAA || sc == 0xB6) { g_shift = 0; return 0; } + if (sc == 0x1D) { g_ctrl = 1; g_e0 = 0; return 0; } + if (sc == 0x9D) { g_ctrl = 0; return 0; } + if (sc == 0x38) { g_alt = 1; g_e0 = 0; return 0; } + if (sc == 0xB8) { g_alt = 0; return 0; } + if (sc & 0x80) { g_e0 = 0; return 0; } /* 其他释放码忽略 */ + + /* Esc:关闭 overlay */ + if (sc == 0x01) { + if (g_taskview) { g_taskview = 0; return 1; } + if (g_ctx_open) { g_ctx_open = 0; return 1; } + return 0; + } + if (g_taskview || g_ctx_open) return 0; /* 模态期间吞掉按键 */ + + /* Ctrl+Tab 切页 / Ctrl+Shift+Tab 上下文菜单(IM 页为切换视图,对应 Kate imView) */ + if (g_ctrl && sc == 0x0F) { + if (g_shift) { + if (g_page == 3) { g_im_view ^= 1; return 1; } + /* Ctrl+Shift+Tab:对当前聚焦窗口打开"移到工作区"菜单 */ + if (g_page == 4 && win_find(g_focused_win)) { + g_ctx_win = g_focused_win; + g_ctx_x = g_mouse_x; g_ctx_y = g_mouse_y; + g_ctx_open = 1; + return 1; + } + return 0; + } + int maxp = 4 + g_cpage_count; + int p = g_page + 1; + if (p > maxp) p = 1; + page_switch(p); + return 1; + } + if (g_ctrl && sc == 0x1F) { editor_save(); return 1; } + + /* 页面路由 */ + if (g_page == 1) { + if (g_dash_focus <= 1) { + bash_key((bash_state *)(g_dash_focus == 0 ? ADDR_TERM1 : ADDR_TERM2), + sc, g_shift); + return 1; + } + if (g_dash_focus == 2) { + editor_key((editor_state *)ADDR_EDDASH, sc, g_shift); + return 1; + } + /* FILES 面板导航 */ + if (sc == 0x48) { if (g_file_sel > 0) g_file_sel--; return 1; } + if (sc == 0x50) { if (g_file_sel < g_file_count - 1) g_file_sel++; return 1; } + if (sc == 0x1C) { files_open(g_file_sel); return 1; } + return 0; + } + if (g_page == 2) { + if (g_ide_attached) { + editor_key((editor_state *)ADDR_EDDASH, sc, g_shift); + return 1; + } + return 0; + } + if (g_page == 4) { + desktop_window *fw = win_find(g_focused_win); + if (fw && fw->ws == g_ws_cur && fw->app_id >= 0 && fw->app_id < g_app_count && + g_apps[fw->app_id].on_event && fw->app_state) { + desktop_event dev; + dev.type = EV_KEY_DOWN; + dev.mx = g_mouse_x; dev.my = g_mouse_y; + dev.button = 0; + dev.scancode = sc; + dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt; + app_ctx actx; + fill_app_ctx(&actx, fw); + g_apps[fw->app_id].on_event(fw->app_state, &actx, &dev); + return 1; + } + } + return 0; +} + +/* ============================================================ + * 主入口 * ============================================================ */ __attribute__((visibility("default"))) @@ -1395,167 +3077,134 @@ void dsk_entry(const dsk_boot_context *ctx) { if (!ctx || ctx->magic != 0x44534B31424F4F54ULL) { slog("bad context"); - for(;;) __asm__("hlt"); + for (;;) __asm__("hlt"); } + g_boot_ctx = ctx; g_fb_addr = ctx->framebuffer_address; g_fb_w = ctx->framebuffer_width; g_fb_h = ctx->framebuffer_height; g_fb_pitch = ctx->framebuffer_pitch; g_kernel_api = (const void *)ctx->dkm_kernel_api; - g_block_read = (desktop_block_read_fn)(*(u64 *)(ctx->dkm_kernel_api + 0xA8)); + + /* block API:kernel_api + 0xA8 → block_api 结构,read @ +0x10, write @ +0x18 */ + { + u64 blk = *(u64 *)(ctx->dkm_kernel_api + 0xA8); + g_block_read = blk ? (desktop_block_read_fn)*(u64 *)(blk + 16) : 0; + g_block_write = blk ? (desktop_block_write_fn)*(u64 *)(blk + 24) : 0; + } du_context_init(&g_fb, g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch); + f32_init(g_block_read, g_block_write); + + tsc_calibrate(); + g_rtc_boot_sec = rtc_day_sec(); + g_last_sec = g_rtc_boot_sec; - /* 初始化 PS/2 鼠标 */ ps2_mouse_init(); - /* 注册应用 */ register_apps(); setup_desktop_icons(); - /* 初始绘制 */ + /* 工作区 0 */ + kstrcpy(g_ws[0].name, "WS-1", 16); + g_ws[0].page = 1; + g_ws_count = 1; + g_ws_cur = 0; + + /* DASHBOARD 面板共享状态 */ + { + bash_state *t1 = (bash_state *)ADDR_TERM1; + bash_reset(t1, 640, 280); + bash_puts(t1, "Deshab Bash v0.1 - TERM-01\nType help for commands\n\n", KS_TEXT_PRIMARY); + + bash_state *t2 = (bash_state *)ADDR_TERM2; + bash_reset(t2, 640, 280); + bash_puts(t2, "Deshab Bash v0.1 - TERM-02\nType help for commands\n\n", KS_TEXT_PRIMARY); + + editor_reset((editor_state *)ADDR_EDDASH, + "Welcome to Deshab Editor\n" + "\n" + "- Double-click a file in FILES panel to open it here\n" + "- SAVE button / Ctrl+S writes back to FAT32 disk\n" + "- IDE page hosts this same editor full-screen\n"); + } + + files_refresh(); + + boot_screen(); + g_booted = 1; + redraw_all(); slog("desktop ready"); /* 主事件循环 */ - for (;;) { + while (!g_quit) { int need_redraw = 0; - g_mouse_has_pkt = 0; + int moved = 0; - /* 轮询 PS/2 鼠标 */ - if (ps2_mouse_poll()) { - /* 拖拽处理 - 需要全屏重绘 */ - if (g_dragging && g_drag_win >= 0) { - desktop_window *w = win_find(g_drag_win); - if (w) { - w->x = g_mouse_x - g_drag_off_x; - w->y = g_mouse_y - g_drag_off_y; - } + /* 鼠标:排空所有数据包 */ + while (ps2_mouse_poll()) moved = 1; + + if (g_left_pressed) { + g_left_pressed = 0; + if (on_left_press()) need_redraw = 1; + } + if (g_right_pressed) { + g_right_pressed = 0; + if (on_right_press()) need_redraw = 1; + } + if (g_left_released) { + g_left_released = 0; + if (g_dragging || g_drag_slider) { + g_dragging = 0; + g_drag_win = -1; + g_drag_slider = 0; need_redraw = 1; } - - /* 鼠标点击事件 - 需要全屏重绘 */ - if (g_mouse_btn) { - /* 检查任务栏点击 */ - int tb_y = (int)g_fb_h - TASKBAR_H; - if (g_mouse_y >= tb_y) { - /* 快速启动图标 */ - int qx = 64; - for (int i = 0; i < g_app_count && i < 4; i++) { - int icon_x = qx + i * 40; - if (g_mouse_x >= icon_x && g_mouse_x < icon_x + 40) { - launch_app(i); - need_redraw = 1; - break; - } - } - } else { - /* 窗口命中测试 */ - desktop_window *hit = win_find_at(g_mouse_x, g_mouse_y); - if (hit) { - win_bring_to_front(hit); - /* 取消其他焦点 */ - for (int i = 0; i < g_win_count; i++) - g_windows[i].focused = (g_windows[i].id == hit->id); - g_focused_win = hit->id; - - /* 检查关闭按钮 */ - if (win_hit_close_btn(hit, g_mouse_x, g_mouse_y)) { - win_destroy(hit); - need_redraw = 1; - } - /* 检查标题栏拖拽 */ - else if (win_hit_titlebar(hit, g_mouse_x, g_mouse_y)) { - g_dragging = 1; - g_drag_win = hit->id; - g_drag_off_x = g_mouse_x - hit->x; - g_drag_off_y = g_mouse_y - hit->y; - } - /* 传递鼠标事件给应用 */ - else if (hit->app_id >= 0 && g_apps[hit->app_id].on_event && hit->app_state) { - desktop_event dev; - dev.type = EV_MOUSE_DOWN; - dev.mx = g_mouse_x; dev.my = g_mouse_y; - dev.button = 0; - app_ctx actx; - actx.fb = &g_fb; actx.win = hit; - actx.client_x = hit->x + BORDER_W; - actx.client_y = hit->y + TITLEBAR_H; - actx.client_w = hit->w - 2*BORDER_W; - actx.client_h = hit->h - TITLEBAR_H - BORDER_W; - actx.should_exit = 0; - actx.block_read = g_block_read; - actx.kernel_api = g_kernel_api; - g_apps[hit->app_id].on_event(hit->app_state, &actx, &dev); - } - } else { - /* 桌面图标点击 */ - for (int i = 0; i < g_icon_count; i++) { - int ix = g_icons[i].x, iy = g_icons[i].y; - if (g_mouse_x >= ix && g_mouse_x < ix + 32 && - g_mouse_y >= iy && g_mouse_y < iy + 32) { - launch_app(g_icons[i].app_id); - need_redraw = 1; - break; - } - } - } - } - } else { - if (g_dragging) { - g_dragging = 0; - g_drag_win = -1; - } - } } - /* 轮询 PS/2 键盘 */ - { + /* 窗口拖拽 */ + if (moved && g_dragging && g_drag_win >= 0) { + desktop_window *w = win_find(g_drag_win); + if (w) { + w->x = g_mouse_x - g_drag_off_x; + w->y = g_mouse_y - g_drag_off_y; + if (w->y < KATE_TOPBAR_H) w->y = KATE_TOPBAR_H; + if (w->x < 0 - w->w + 40) w->x = 0 - w->w + 40; + if (w->x > (int)g_fb_w - 40) w->x = (int)g_fb_w - 40; + if (w->y > (int)g_fb_h - KATE_TASKBAR_H - 20) + w->y = (int)g_fb_h - KATE_TASKBAR_H - 20; + } + need_redraw = 1; + } + + /* 滑块拖拽 */ + if (moved && g_drag_slider == 1) { slider_set(g_vol_rect, &g_vol, g_mouse_x); need_redraw = 1; } + if (moved && g_drag_slider == 2) { slider_set(g_bri_rect, &g_bright, g_mouse_x); need_redraw = 1; } + + /* 键盘:排空 */ + for (;;) { u8 st = inb(0x64); - if ((st & 1) && !(st & 0x20)) { - u8 data = inb(0x60); - u8 sc = data; - if (sc == 0xE0) { g_e0 = 1; goto next; } - if (sc == 0x2A || sc == 0x36) { g_shift = 1; goto next; } - if (sc == 0xAA || sc == 0xB6) { g_shift = 0; goto next; } - if (sc & 0x80) { g_e0 = 0; goto next; } + if (!(st & 1) || (st & 0x20)) break; + u8 sc = inb(0x60); + if (handle_key(sc)) need_redraw = 1; + } - /* 发送键盘事件到焦点窗口 */ - if (g_focused_win >= 0) { - desktop_window *fw = win_find(g_focused_win); - if (fw && fw->app_id >= 0 && g_apps[fw->app_id].on_event && fw->app_state) { - desktop_event dev; - dev.type = EV_KEY_DOWN; - dev.mx = g_mouse_x; dev.my = g_mouse_y; - dev.button = 0; - dev.scancode = sc; - dev.shift = g_shift; - dev.ctrl = g_ctrl; - dev.alt = g_alt; - app_ctx actx; - actx.fb = &g_fb; actx.win = fw; - actx.client_x = fw->x + BORDER_W; - actx.client_y = fw->y + TITLEBAR_H; - actx.client_w = fw->w - 2*BORDER_W; - actx.client_h = fw->h - TITLEBAR_H - BORDER_W; - actx.should_exit = 0; - actx.block_read = g_block_read; - actx.kernel_api = g_kernel_api; - g_apps[fw->app_id].on_event(fw->app_state, &actx, &dev); - need_redraw = 1; - } - } - g_e0 = 0; + /* RTC 秒 tick:DASHBOARD 状态栏时钟/UP 计时刷新 */ + { + int sec = rtc_day_sec(); + if (sec != g_last_sec) { + g_last_sec = sec; + if (g_page == 1 && !g_taskview) need_redraw = 1; } } -next: if (need_redraw) { cursor_restore_bg(); redraw_all(); - } else if (g_mouse_has_pkt) { - /* 鼠标移动但无需全屏重绘:只更新光标位置 */ + } else if (moved) { + /* 仅光标移动:局部更新 */ cursor_restore_bg(); cursor_save_bg(g_mouse_x, g_mouse_y); cursor_draw(g_mouse_x, g_mouse_y); @@ -1563,4 +3212,7 @@ next: __asm__("pause"); } + + slog("exit"); + /* 返回到 DSK */ } diff --git a/CODE/dsk/linker.ld b/CODE/dsk/linker.ld index 473e5fc..ffdf62d 100644 --- a/CODE/dsk/linker.ld +++ b/CODE/dsk/linker.ld @@ -17,8 +17,10 @@ SECTIONS *(.data*) *(.data.rel.ro*) *(COMMON) - *(.bss*) } :data .rela.dyn : ALIGN(8) { *(.rela.dyn*) } :data .dynamic : ALIGN(8) { *(.dynamic*) } :data :dynamic + .bss : ALIGN(4K) { + *(.bss*) + } :data } diff --git a/CODE/dsk/main.c b/CODE/dsk/main.c index 7c6b497..c8aa172 100644 --- a/CODE/dsk/main.c +++ b/CODE/dsk/main.c @@ -66,7 +66,7 @@ static block_write_fn g_block_write; static u64 g_tsc_per_ms = 0; static u8 g_disk[131072]; static u8 g_cluster[4096]; -static u8 g_fdata[262144]; +static u8 g_fdata[1048576]; static int fat32_read_sectors(u32 lba, u32 count, u8 *out) { return g_block_read ? g_block_read(0,lba,count,out) : -1; } static int fat32_write_sectors(u32 lba, u32 count, const u8 *buf) { return g_block_write ? g_block_write(0,lba,count,buf) : -1; } @@ -529,7 +529,7 @@ static int dsk_load_elf(u8 *data, u32 size, void **entry_out) { if (!lc || min_vaddr == ~0ULL) return -4; u64 isize = max_vaddr - min_vaddr; isize = (isize + 0xFFF) & ~0xFFFULL; - /* 加载缓冲:需容纳 shell.elf(~536KB,含 FAT32 读写缓冲 BSS)。1MB 留余量。 */ + /* 加载缓冲:需容纳 shell.elf(~862KB memsz,含 FAT32 读写缓冲 BSS)。1MB 留余量。 */ static u8 ibuf[1048576]; u8 *image = ibuf; if (isize > 1048576) return -5; /* 镜像过大,防止越界 */ diff --git a/CODE/dsk/main.o b/CODE/dsk/main.o index 622fb3e..267f9d9 100644 Binary files a/CODE/dsk/main.o and b/CODE/dsk/main.o differ diff --git a/CODE/linux/README.md b/CODE/linux/README.md new file mode 100644 index 0000000..d08eb49 --- /dev/null +++ b/CODE/linux/README.md @@ -0,0 +1,114 @@ +# Linux 6.6 LTS for UTSM Dual-Kernel Architecture + +## Overview + +This directory contains the Linux 6.6 LTS configuration and build scripts for the +UTSM + Linux dual-kernel architecture. Linux runs as a VMX non-root guest under +the UTSM hypervisor (VMX root). + +## Prerequisites + +- **WSL** (Windows Subsystem for Linux) with Ubuntu 22.04+ installed + ```powersShell + wsl --install -d Ubuntu-22.04 + ``` +- Build dependencies (inside WSL): + ```bash + sudo apt update + sudo apt install -y build-essential bc flex bison libelf-dev libssl-dev cpio gzip git + ``` +- ~3GB free disk space + +## Building Linux + +### 1. From WSL + +```bash +# Navigate to the project (adjust path for your WSL mount) +cd /mnt/d/Code/Deshab/CODE/linux + +# Make the build script executable +chmod +x build.sh + +# Run the build (clones Linux 6.6 LTS, configures, builds) +./build.sh +``` + +The build will: +1. Clone Linux 6.6 LTS source to `CODE/linux/src/` +2. Apply UTSM patches from `CODE/linux/patches/` (if any) +3. Configure with `configs/utsm_x86_64_defconfig` +4. Build `bzImage` +5. Copy to `SYSTEM/boot/linux-bzImage` +6. Build initramfs if `CODE/linux/initramfs/` exists + +### 2. Enable in Limine + +After building, uncomment the Linux module entries in `SYSTEM/limine/limine.conf`: + +``` + module_path: boot():/boot/linux-bzImage + module_cmdline: linux:bzImage + module_path: boot():/boot/linux-initrd.img + module_cmdline: linux:initrd +``` + +### 3. Rebuild the disk image + +```PowerShell +cd d:\Code\Deshab +.\build.bat +``` + +### 4. Test with QEMU + +**Note:** VMX requires hardware virtualization support. QEMU's TCG (software +emulation) does NOT support VMX instructions. To test the VMX path: + +- **Real hardware**: Boot on an Intel machine with VT-x enabled in BIOS +- **QEMU with WHPX**: `qemu-system-x86_64 -accel whpx -cpu max` (may not expose + nested VMX to the guest) +- **QEMU with KVM** (Linux host only): `qemu-system-x86_64 -enable-kvm -cpu host` + +Without VMX, UTSM gracefully falls back to the normal DSK boot flow. + +## Configuration Details + +The `utsm_x86_64_defconfig` enables: +- 64-bit kernel with SMP support +- Serial console (8250/16550 at 0x3F8, 115200 baud) +- virtio: console, block, net, fs (for UTSM host backends) +- ext4, FAT, tmpfs, proc, sysfs filesystems +- No module loading (all drivers built-in for Phase 1) +- No EFI stub (UTSM uses custom VMX boot protocol) +- Debug info and printk timestamps + +## File Structure + +``` +CODE/linux/ +├── configs/ +│ └── utsm_x86_64_defconfig # Kernel configuration +├── patches/ # UTSM-specific kernel patches +│ ├── utsm-hcall.patch # /dev/utsm hypercall driver (Phase 1.3) +│ ├── utsm-ipc.patch # Shared memory IPC driver (Phase 1.3) +│ └── utsm-virtio.patch # virtio enhancements (Phase 1.4+) +├── initramfs/ # Minimal initramfs contents (optional) +├── build.sh # Build script (run in WSL/Linux) +└── README.md # This file +``` + +## Boot Flow + +``` +Limine (UEFI) + → utsm.elf (VMX root) + → vmm_init() + vmm_self_test() + → linux_loader_init() (parse bzImage, set up boot_params, EPT) + → linux_launch() (VMCS config + vmlaunch) + → Linux 6.6 startup_64 (VMX non-root) + → Serial console output + → virtio-console ↔ UTSM + → virtio-fs mount /utsm + → User space (init/shell) +``` diff --git a/CODE/linux/build.sh b/CODE/linux/build.sh new file mode 100644 index 0000000..4dd375b --- /dev/null +++ b/CODE/linux/build.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# +# Build Linux 6.6 LTS bzImage for UTSM dual-kernel guest. +# +# Prerequisites: +# - Linux build environment (WSL, native Linux, or Docker) +# - gcc, make, bc, flex, bison, libelf-dev, libssl-dev +# - ~3GB free disk space +# +# Usage: +# cd CODE/linux +# ./build.sh +# +# Output: +# - SYSTEM/boot/linux-bzImage (kernel image) +# - SYSTEM/boot/linux-initrd.img (minimal initramfs, if initramfs/ exists) + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +LINUX_SRC="${LINUX_SRC:-$SCRIPT_DIR/src}" +OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot" +CONFIG_FILE="$SCRIPT_DIR/configs/utsm_x86_64_defconfig" +INITRAMFS_DIR="$SCRIPT_DIR/initramfs" + +echo "[build] Linux kernel build for UTSM" +echo "[build] Project root: $PROJECT_ROOT" +echo "[build] Linux source: $LINUX_SRC" + +# 1. Clone Linux 6.6 LTS if not present +if [ ! -d "$LINUX_SRC" ]; then + echo "[build] Cloning Linux 6.6 LTS..." + git clone --depth 1 --branch v6.6 \ + https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git \ + "$LINUX_SRC" +fi + +cd "$LINUX_SRC" + +# 2. Apply UTSM patches if any +PATCH_DIR="$SCRIPT_DIR/patches" +if [ -d "$PATCH_DIR" ] && [ "$(ls -A $PATCH_DIR 2>/dev/null)" ]; then + echo "[build] Applying UTSM patches..." + + # 2a. Apply .patch files (if any) + for patch in "$PATCH_DIR"/*.patch; do + if [ -f "$patch" ]; then + echo "[build] applying $(basename $patch)" + git apply --check "$patch" 2>/dev/null && git apply "$patch" || \ + echo "[build] WARNING: $patch already applied or failed" + fi + done + + # 2b. Install UTSM driver source files into drivers/utsm/ + UTSM_DRV_DIR="$LINUX_SRC/drivers/utsm" + echo "[build] installing UTSM driver into drivers/utsm/" + mkdir -p "$UTSM_DRV_DIR/include" + + # Copy driver source, Kconfig, and Makefile + cp "$PATCH_DIR/utsm_hcall.c" "$UTSM_DRV_DIR/utsm_hcall.c" + cp "$PATCH_DIR/utsm_Kconfig" "$UTSM_DRV_DIR/Kconfig" + cp "$PATCH_DIR/utsm_Makefile" "$UTSM_DRV_DIR/Makefile" + + # Copy shared IPC protocol header (included by both UTSM and Linux) + cp "$PROJECT_ROOT/CODE/utsm-ipc/ipc_proto.h" "$UTSM_DRV_DIR/include/ipc_proto.h" + + # Add UTSM to drivers/Kconfig (if not already present) + if ! grep -q "drivers/utsm/Kconfig" "$LINUX_SRC/drivers/Kconfig"; then + echo '[build] adding source "drivers/utsm/Kconfig" to drivers/Kconfig' + echo 'source "drivers/utsm/Kconfig"' >> "$LINUX_SRC/drivers/Kconfig" + fi + + # Add UTSM to drivers/Makefile (if not already present) + if ! grep -q "CONFIG_UTSM_HCALL" "$LINUX_SRC/drivers/Makefile"; then + echo '[build] adding obj-$(CONFIG_UTSM_HCALL) += utsm/ to drivers/Makefile' + echo 'obj-$(CONFIG_UTSM_HCALL) += utsm/' >> "$LINUX_SRC/drivers/Makefile" + fi + + echo "[build] UTSM driver installed:" + echo "[build] $UTSM_DRV_DIR/utsm_hcall.c" + echo "[build] $UTSM_DRV_DIR/Kconfig" + echo "[build] $UTSM_DRV_DIR/Makefile" + echo "[build] $UTSM_DRV_DIR/include/ipc_proto.h" +fi + +# 3. Configure +echo "[build] Configuring kernel..." +cp "$CONFIG_FILE" .config +make olddefconfig + +# 4. Build bzImage +echo "[build] Building bzImage (this may take 10-30 minutes)..." +make -j"$(nproc)" bzImage + +# 5. Copy bzImage to SYSTEM/boot/ +mkdir -p "$OUTPUT_DIR" +cp arch/x86/boot/bzImage "$OUTPUT_DIR/linux-bzImage" +echo "[build] bzImage: $OUTPUT_DIR/linux-bzImage ($(du -h $OUTPUT_DIR/linux-bzImage | cut -f1))" + +# 6. Build initramfs if initramfs/ directory exists +if [ -d "$INITRAMFS_DIR" ]; then + echo "[build] Building initramfs..." + # Create a minimal initramfs with busybox or a simple init + INITRAMFS_TMP="$SCRIPT_DIR/initramfs.tmp" + rm -rf "$INITRAMFS_TMP" + mkdir -p "$INITRAMFS_TMP" + + # Copy initramfs contents + cp -a "$INITRAMFS_DIR"/* "$INITRAMFS_TMP"/ + + # Create init script if not present + if [ ! -f "$INITRAMFS_TMP/init" ]; then + cat > "$INITRAMFS_TMP/init" <<'INITEOF' +#!/bin/sh +mount -t proc none /proc +mount -t sysfs none /sys +mount -t devtmpfs none /dev +echo "[utsm-linux] Boot complete" +echo "[utsm-linux] Console ready on /dev/hvc0 (virtio-console)" +exec /bin/sh +INITEOF + chmod +x "$INITRAMFS_TMP/init" + fi + + # Pack initramfs + cd "$INITRAMFS_TMP" + find . | cpio -H newc -o | gzip -9 > "$OUTPUT_DIR/linux-initrd.img" + echo "[build] initrd: $OUTPUT_DIR/linux-initrd.img ($(du -h $OUTPUT_DIR/linux-initrd.img | cut -f1))" + cd "$LINUX_SRC" +fi + +echo "[build] Done. Copy linux-bzImage (and linux-initrd.img) to your FAT32 image." +echo "[build] Limine will load them as boot modules for UTSM." diff --git a/CODE/linux/configs/utsm_x86_64_defconfig b/CODE/linux/configs/utsm_x86_64_defconfig new file mode 100644 index 0000000..c98aa68 --- /dev/null +++ b/CODE/linux/configs/utsm_x86_64_defconfig @@ -0,0 +1,231 @@ +# +# Linux 6.6 LTS minimal config for UTSM dual-kernel guest +# +# Build: make utsm_x86_64_defconfig && make -j$(nproc) bzImage +# +# Key design choices: +# - 64-bit only (CONFIG_64BIT=y) +# - Serial console (8250/16550 at 0x3F8) +# - virtio: console, block, net, fs (for UTSM host backends) +# - EFI stub disabled (UTSM uses custom VMX boot, not EFI) +# - Built-in drivers (no module loading needed for Phase 1) +# - No SMP (single CPU for Phase 1 simplicity) +# - printk to serial for debugging + +CONFIG_64BIT=y +CONFIG_X86_64=y +CONFIG_X86=y +CONFIG_INSTRUCTION_DECODER=y +CONFIG_OUTPUT_FORMAT="elf64-x86-64" +CONFIG_ARCH_MMAP_RND_BITS_MIN=28 +CONFIG_ARCH_MMAP_RND_BITS_MAX=32 +CONFIG_GENERIC_ISA_DMA=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_ARCH_MMAP_RND_BITS=28 +CONFIG_SMP=y +CONFIG_NR_CPUS=4 +CONFIG_X86_64_SMP=y +CONFIG_SCHED_MC=y +CONFIG_SCHED_SMT=y + +# Processor type +CONFIG_MK8=y +CONFIG_X86_CPU=y +CONFIG_X86_L1_CACHE_SHIFT=6 +CONFIG_X86_TSC=y +CONFIG_X86_CMPXCHG64=y +CONFIG_X86_CMOV=y +CONFIG_X86_MINIMUM_CPU_FAMILY=64 +CONFIG_PROCESSOR_SELECT=y +CONFIG_CPU_SUP_INTEL=y +CONFIG_CPU_SUP_AMD=y +CONFIG_CPU_SUP_CENTAUR=y +CONFIG_HPET_TIMER=y +CONFIG_HPET_EMULATE_RTC=y +CONFIG_DMI=y + +# Memory +CONFIG_PHYS_ADDR_T_64BIT=y +CONFIG_ARCH_PHYS_ADDR_T_64BIT=y +CONFIG_ARCH_DMA_ADDR_T_64BIT=y +CONFIG_DIRECT_GBPAGES=y +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_ARCH_SPARSEMEM_DEFAULT=y +CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 +CONFIG_X86_PMEM_LEGACY_DEVICE=y +CONFIG_X86_PMEM_LEGACY=y +CONFIG_X86_CHECK_BIOS_CORRUPTION=y +CONFIG_X86_BOOTPARAM_MEMORY_CORRUPTION_CHECK=y +CONFIG_X86_RESERVE_LOW=64 +CONFIG_MTRR=y +CONFIG_MTRR_SANITIZER=y +CONFIG_MTRR_SANITIZER_ENABLE_DEFAULT=0 +CONFIG_X86_PAT=y +CONFIG_ARCH_USES_PG_UNCACHED=y +CONFIG_ARCH_RANDOM=y +CONFIG_X86_SMAP=y +CONFIG_X86_INTEL_UMIP=y +CONFIG_X86_UMIP=y + +# Power management (minimal) +CONFIG_PM=y +CONFIG_PM_CLK=y + +# Bus options +CONFIG_PCI=y +CONFIG_PCI_DIRECT=y +CONFIG_PCI_MMCONFIG=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_MSI=y +CONFIG_PCI_MSI_IRQ_DOMAIN=y +CONFIG_PCI_LABEL=y +CONFIG_ISA_DMA_API=y +CONFIG_AMD_NB=y + +#Executable file formats +CONFIG_BINFMT_ELF=y +CONFIG_BINFMT_SCRIPT=y +CONFIG_BINFMT_MISC=y + +# Networking +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_SYN_COOKIES=y +CONFIG_TCP_CONG_CUBIC=y + +# Device Drivers +# Serial console (critical: UTSM captures serial output) +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_8250_NR_UARTS=4 +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_EARLY_PRINTK=y +CONFIG_EARLY_PRINTK_SERIAL=y + +# PCI host bridge +CONFIG_PCI_HOST_GENERIC=y + +# virtio (critical: UTSM provides virtio backends) +CONFIG_VIRTIO=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_PCI_LEGACY=y +CONFIG_VIRTIO_BALLOON=y +CONFIG_VIRTIO_INPUT=y +CONFIG_VIRTIO_MMIO=y +CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y + +# virtio console (Phase 1.4: UTSM↔Linux console) +CONFIG_VIRTIO_CONSOLE=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_VIRTIO=y + +# UTSM dual-kernel hypercall and IPC driver (Phase 1.3) +CONFIG_UTSM_HCALL=y + +# virtio block (Phase 2: block device) +CONFIG_VIRTIO_BLK=y +CONFIG_VIRTIO_BLK_SCSI=y + +# virtio net (Phase 2: network) +CONFIG_VIRTIO_NET=y + +# virtio-fs (Phase 1.5: shared filesystem) +CONFIG_VIRTIO_FS=y +CONFIG_FUSE_FS=y + +# Filesystems +CONFIG_EXT4_FS=y +CONFIG_EXT4_USE_FOR_EXT2=y +CONFIG_FAT_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_TMPFS=y +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_SYSFS=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y + +# Block devices +CONFIG_BLK_DEV=y +CONFIG_BLK_DEV_LOOP=y + +# Input (minimal) +CONFIG_INPUT=y +CONFIG_INPUT_KEYBOARD=y + +# RTC +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" + +# PNP/ACPI (minimal for QEMU) +CONFIG_ACPI=y +CONFIG_ACPI_AC=y +CONFIG_ACPI_BUTTON=y +CONFIG_ACPI_FAN=y +CONFIG_ACPI_PROCESSOR=y +CONFIG_ACPI_THERMAL=y + +# File systems +CONFIG_FHANDLE=y + +# Kernel hacking +CONFIG_PRINTK=y +CONFIG_PRINTK_TIME=y +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7 +CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y +CONFIG_FRAME_WARN=2048 +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_KERNEL=y +CONFIG_DEBUG_RODATA=y +CONFIG_DEBUG_SET_MODULE_RONX=y +CONFIG_DEBUG_WX=y + +# Security (minimal) +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y + +# Crypto (minimal) +CONFIG_CRYPTO=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_SHA256=y + +# Library routines +CONFIG_CRC16=y +CONFIG_CRC32=y +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_XZ_DEC=y +CONFIG_XZ_DEC_X86=y + +# Initramfs +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" + +# No module support (build everything in for Phase 1) +# CONFIG_MODULES is not set + +# No EFI stub (UTSM uses custom VMX boot) +# CONFIG_EFI_STUB is not set +# CONFIG_EFI is not set + +# Boot protocol +CONFIG_EARLY_PRINTK_SERIAL=y diff --git a/CODE/linux/patches/utsm_Kconfig b/CODE/linux/patches/utsm_Kconfig new file mode 100644 index 0000000..a317728 --- /dev/null +++ b/CODE/linux/patches/utsm_Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# UTSM dual-kernel hypercall driver configuration +# + +config UTSM_HCALL + tristate "UTSM dual-kernel hypercall and IPC driver" + default y + help + This driver enables communication between the Linux guest and + the UTSM monitor kernel in the Deshab dual-kernel architecture. + + When loaded, it: + - Probes for the UTSM monitor via a VMCALL PING hypercall + - Maps the IPC shared memory ring buffer + - Registers /dev/utsm for userspace IPC message passing + + If running on bare metal or a non-UTSM hypervisor, the driver + loads in passive mode (no /dev/utsm registered). + + If unsure, say Y for UTSM guest builds, N otherwise. + +config UTSM_EARLY_CONSOLE + bool "Redirect kernel printk to UTSM serial console" + depends on UTSM_HCALL + default n + help + Redirects Linux kernel console output to the UTSM monitor's + serial port via UTSM_HCALL_CONSOLE_WRITE hypercalls. Useful + for early boot debugging when the guest's own serial is not + configured. + + If unsure, say N. diff --git a/CODE/linux/patches/utsm_Makefile b/CODE/linux/patches/utsm_Makefile new file mode 100644 index 0000000..e362e74 --- /dev/null +++ b/CODE/linux/patches/utsm_Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# UTSM dual-kernel driver Makefile +# + +obj-$(CONFIG_UTSM_HCALL) += utsm_hcall.o + +# Include the shared IPC protocol header from CODE/utsm-ipc/ +# This path is set up by build.sh when copying files into the source tree +ccflags-y += -I$(src)/include diff --git a/CODE/linux/patches/utsm_hcall.c b/CODE/linux/patches/utsm_hcall.c new file mode 100644 index 0000000..f61b653 --- /dev/null +++ b/CODE/linux/patches/utsm_hcall.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UTSM hypercall driver — Linux guest side. + * + * Provides: + * 1. VMCALL hypercall interface to communicate with the UTSM monitor kernel + * 2. /dev/utsm character device for userspace IPC message passing + * 3. Shared memory ring buffer mapping (queried via UTSM_HCALL_SHM_INFO) + * + * The driver is platform-agnostic: it probes for UTSM by issuing a PING + * hypercall at init. If UTSM is not present (e.g. running on bare metal + * or a different hypervisor), the driver exits gracefully. + * + * Build: configured via CONFIG_UTSM_HCALL (built-in or module). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Shared IPC protocol header (included via build.sh -I flag) */ +#include + +#define UTSM_DEV_NAME "utsm" + +/* ===== Hypercall ABI ===== + * + * Linux calls VMCALL with: + * RAX = UTSM_HCALL_MAGIC | op + * RDI = arg0, RSI = arg1, RDX = arg2 + * + * UTSM handles the VMCALL VM-Exit and returns: + * RAX = return value + * + * Since we run in the guest, the VMCALL instruction triggers a VM-Exit + * to UTSM. The inline assembly must use the same register conventions. + */ + +static inline long utsm_hcall(u64 op, u64 a0, u64 a1, u64 a2) +{ + long ret; + register u64 rax asm("rax") = UTSM_HCALL_MAGIC | op; + register u64 rdi asm("rdi") = a0; + register u64 rsi asm("rsi") = a1; + register u64 rdx asm("rdx") = a2; + + asm volatile("vmcall" + : "+r"(rax) + : "r"(rdi), "r"(rsi), "r"(rdx) + : "memory"); + + return (long)rax; +} + +static inline long utsm_hcall_ping(void) +{ + return utsm_hcall(UTSM_HCALL_PING, 0, 0, 0); +} + +static inline long utsm_hcall_hello(void) +{ + return utsm_hcall(UTSM_HCALL_HELLO, 0, 0, 0); +} + +static inline long utsm_hcall_shm_info(u64 *gpa_out, u64 *size_out) +{ + u64 gpa = 0, size = 0; + long ret; + + /* Pass stack addresses (GVA=GPA due to identity mapping in early boot). + * UTSM translates GPA→HPA via EPT walk and writes the values there. */ + ret = utsm_hcall(UTSM_HCALL_SHM_INFO, (u64)&gpa, (u64)&size, 0); + if (ret == 0) { + *gpa_out = gpa; + *size_out = size; + } + return ret; +} + +static inline long utsm_hcall_console_write(const char *buf, u64 len) +{ + return utsm_hcall(UTSM_HCALL_CONSOLE_WRITE, (u64)buf, len, 0); +} + +/* ===== Shared memory state ===== */ + +static struct utsm_ipc_shm *g_shm; /* kernel virtual address of SHM */ +static u64 g_shm_gpa; /* guest physical address of SHM */ +static u64 g_shm_size; /* size of SHM region */ +static int g_utsm_present; /* 1 = UTSM detected */ +static int g_shm_mapped; + +/* ===== /dev/utsm character device ===== */ + +static ssize_t utsm_dev_write(struct file *f, const char __user *buf, + size_t len, loff_t *off) +{ + struct utsm_ipc_msg msg; + size_t copy_len; + ssize_t ret; + + if (!g_shm_mapped) + return -ENODEV; + + if (len == 0) + return 0; + + /* Cap data to inline message size */ + copy_len = min_t(size_t, len, UTSM_IPC_MSG_DATA_SIZE); + + memset(&msg, 0, sizeof(msg)); + msg.type = UTSM_MSG_DATA; + msg.data_len = (u32)copy_len; + + if (copy_from_user(msg.data, buf, copy_len)) { + pr_err("[utsm] copy_from_user failed\n"); + return -EFAULT; + } + + /* Enqueue into linux_to_utsm ring (Linux produces, UTSM consumes) */ + if (utsm_ipc_ring_push(&g_shm->linux_to_utsm, &msg) != 0) { + pr_warn("[utsm] linux_to_utsm ring full\n"); + return -EAGAIN; + } + + ret = (ssize_t)copy_len; + + /* Also relay the data to the UTSM serial console via hypercall. + * This gives immediate visibility into Linux userspace output. */ + if (copy_len > 0) { + char kbuf[UTSM_IPC_MSG_DATA_SIZE + 1]; + memcpy(kbuf, msg.data, copy_len); + kbuf[copy_len] = '\0'; + utsm_hcall_console_write(kbuf, copy_len); + } + + return ret; +} + +static ssize_t utsm_dev_read(struct file *f, char __user *buf, + size_t len, loff_t *off) +{ + struct utsm_ipc_msg msg; + + if (!g_shm_mapped) + return -ENODEV; + + /* Dequeue from utsm_to_linux ring (UTSM produces, Linux consumes) */ + if (utsm_ipc_ring_pop(&g_shm->utsm_to_linux, &msg) != 0) { + /* No message available */ + return -EAGAIN; /* Non-blocking; userspace can poll */ + } + + if (msg.data_len == 0) + return 0; + + if (len < msg.data_len) + return -EINVAL; + + if (copy_to_user(buf, msg.data, msg.data_len)) + return -EFAULT; + + return (ssize_t)msg.data_len; +} + +static const struct file_operations utsm_fops = { + .owner = THIS_MODULE, + .write = utsm_dev_write, + .read = utsm_dev_read, +}; + +static struct miscdevice utsm_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = UTSM_DEV_NAME, + .fops = &utsm_fops, + .mode = 0666, +}; + +/* ===== Early console redirect (optional) ===== + * + * Redirect kernel printk output to UTSM serial console via hypercall. + * This is useful for debugging when the guest's own serial is not set up. + * Currently disabled; can be enabled via CONFIG_UTSM_EARLY_CONSOLE. + */ + +#ifdef CONFIG_UTSM_EARLY_CONSOLE +static void utsm_console_write(struct console *con, const char *s, unsigned int n) +{ + if (g_utsm_present) { + char buf[256]; + unsigned int chunk; + + while (n > 0) { + chunk = min_t(unsigned int, n, sizeof(buf) - 1); + memcpy(buf, s, chunk); + buf[chunk] = '\0'; + utsm_hcall_console_write(buf, chunk); + s += chunk; + n -= chunk; + } + } +} + +static struct console utsm_console = { + .name = "utsmcon", + .write = utsm_console_write, + .flags = CON_PRINTBUFFER | CON_ENABLED, + .index = -1, +}; +#endif + +/* ===== Initialization ===== */ + +/* Forward declaration: used by init before /dev/utsm is registered */ +static inline int ipc_shm_send_to_utsm(u32 type, const void *data, u32 len); + +static int __init utsm_hcall_init(void) +{ + long ping_ret; + u64 shm_gpa = 0, shm_size = 0; + int ret; + + pr_info("[utsm] probing for UTSM monitor...\n"); + + /* 1. Ping UTSM to verify it's present */ + ping_ret = utsm_hcall_ping(); + if (ping_ret != 0x504F4E47) { /* "PONG" */ + pr_info("[utsm] UTSM not detected (ping returned 0x%lx)\n", ping_ret); + pr_info("[utsm] Driver loaded in passive mode (no /dev/utsm)\n"); + return 0; /* Not an error: we might be running on bare metal */ + } + + g_utsm_present = 1; + pr_info("[utsm] UTSM monitor detected (PONG=0x%lx)\n", ping_ret); + + /* 2. Say hello */ + utsm_hcall_hello(); + + /* 3. Query shared memory info */ + ret = utsm_hcall_shm_info(&shm_gpa, &shm_size); + if (ret != 0) { + pr_err("[utsm] SHM_INFO hypercall failed: %ld\n", ret); + goto err_no_shm; + } + + pr_info("[utsm] shared memory: gpa=0x%llx size=%llu KB\n", + shm_gpa, shm_size / 1024); + + if (shm_gpa == 0 || shm_size == 0) { + pr_err("[utsm] invalid SHM info\n"); + goto err_no_shm; + } + + /* 4. Map the shared memory region. + * The GPA is a physical address from Linux's perspective. + * Use ioremap_cache for cacheable memory (the SHM region is + * configured as WB in EPT). */ + g_shm = (struct utsm_ipc_shm *)ioremap_cache(shm_gpa, shm_size); + if (!g_shm) { + pr_err("[utsm] failed to ioremap SHM at 0x%llx\n", shm_gpa); + goto err_ioremap; + } + + g_shm_gpa = shm_gpa; + g_shm_size = shm_size; + g_shm_mapped = 1; + + /* 5. Verify SHM header */ + if (g_shm->header.magic != UTSM_IPC_MAGIC) { + pr_err("[utsm] SHM magic mismatch: 0x%llx\n", g_shm->header.magic); + goto err_bad_magic; + } + + pr_info("[utsm] SHM header: magic=OK version=%u utsm_ready=%u\n", + g_shm->header.version, g_shm->header.utsm_ready); + + /* 6. Set linux_ready flag */ + g_shm->header.linux_ready = 1; + mb(); /* Ensure visible to UTSM */ + + /* 7. Register /dev/utsm */ + ret = misc_register(&utsm_miscdev); + if (ret) { + pr_err("[utsm] misc_register failed: %d\n", ret); + goto err_misc; + } + +#ifdef CONFIG_UTSM_EARLY_CONSOLE + register_console(&utsm_console); +#endif + + pr_info("[utsm] /dev/%s registered (IPC ready)\n", UTSM_DEV_NAME); + + /* 8. Send a HELLO message via the ring buffer */ + { + const char *hello_msg = "Linux booted OK"; + ipc_shm_send_to_utsm(UTSM_MSG_DATA, hello_msg, 15); + } + + return 0; + +err_misc: + g_shm->header.linux_ready = 0; +err_bad_magic: + iounmap(g_shm); + g_shm = NULL; + g_shm_mapped = 0; +err_ioremap: +err_no_shm: + g_utsm_present = 0; + return -ENODEV; +} + +/* Helper to send a message (used by init before /dev/utsm is available) */ +static inline int ipc_shm_send_to_utsm(u32 type, const void *data, u32 len) +{ + struct utsm_ipc_msg msg; + + if (!g_shm_mapped || !g_shm) + return -1; + + if (len > UTSM_IPC_MSG_DATA_SIZE) + len = UTSM_IPC_MSG_DATA_SIZE; + + memset(&msg, 0, sizeof(msg)); + msg.type = type; + msg.data_len = len; + if (data && len > 0) + memcpy(msg.data, data, len); + + return utsm_ipc_ring_push(&g_shm->linux_to_utsm, &msg); +} + +static void __exit utsm_hcall_exit(void) +{ + if (g_shm_mapped) { + /* Clear linux_ready flag */ + g_shm->header.linux_ready = 0; + mb(); + + /* Send shutdown notification */ + ipc_shm_send_to_utsm(UTSM_MSG_SHUTDOWN, NULL, 0); + + misc_deregister(&utsm_miscdev); + +#ifdef CONFIG_UTSM_EARLY_CONSOLE + unregister_console(&utsm_console); +#endif + + iounmap(g_shm); + g_shm = NULL; + g_shm_mapped = 0; + } + + pr_info("[utsm] driver unloaded\n"); +} + +module_init(utsm_hcall_init); +module_exit(utsm_hcall_exit); + +MODULE_AUTHOR("Deshab Project"); +MODULE_DESCRIPTION("UTSM dual-kernel hypercall and IPC driver"); +MODULE_LICENSE("GPL"); diff --git a/CODE/login/MAKEFILE b/CODE/login/MAKEFILE index fc329ac..339efb9 100644 --- a/CODE/login/MAKEFILE +++ b/CODE/login/MAKEFILE @@ -3,6 +3,7 @@ LD := ld.lld ROOT := ../.. OUT := $(ROOT)/SYSTEM/system/user/use/login.elf +PATCHER := $(ROOT)/.build_tmp/patch_login_hash.ps1 CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I../UTSM/include -I../firstInit -O2 LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld @@ -14,9 +15,12 @@ OBJECTS := $(C_SOURCES:.c=.o) all: $(OUT) +# 构建后用 patch_login_hash.ps1 计算 .text SHA256 并写入 .hashseg $(OUT): $(OBJECTS) linker.ld @if not exist "$(ROOT)\SYSTEM\system\user\use" mkdir "$(ROOT)\SYSTEM\system\user\use" $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + @echo [login] patching .text SHA256 into .hashseg... + powershell -NoProfile -ExecutionPolicy Bypass -File $(PATCHER) $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ diff --git a/CODE/login/linker.ld b/CODE/login/linker.ld index 473e5fc..627093f 100644 --- a/CODE/login/linker.ld +++ b/CODE/login/linker.ld @@ -11,12 +11,28 @@ PHDRS SECTIONS { . = 0x1000; + /* 防篡改:导出 .text 段范围供运行时自校验 */ + __text_start = .; .text : ALIGN(4K) { *(.text*) } :text + __text_end = .; + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + + /* 防篡改:独立的 .hashseg 段存放预期 SHA256(32 字节)。 + * 由构建后补丁脚本 patch_login_hash.exe 写入。 + * 初始填充 0x00,运行时若发现全 0 则跳过校验(开发期便利)。 */ + .hashseg : ALIGN(16) { + __expected_hash = .; + . = . + 32; + __expected_hash_end = .; + } :rodata + .data : ALIGN(4K) { *(.data*) *(.data.rel.ro*) *(COMMON) + } :data + .bss : ALIGN(4K) { *(.bss*) } :data .rela.dyn : ALIGN(8) { *(.rela.dyn*) } :data diff --git a/CODE/login/main.c b/CODE/login/main.c index 4c73aee..ab49451 100644 --- a/CODE/login/main.c +++ b/CODE/login/main.c @@ -4,12 +4,22 @@ * extracts username + password hash, prompts for password, * verifies via SHA256 comparison, sets ctx->reserved[2]=1 on success. * + * 防篡改:对 .text 段做 SHA256 自校验,预期哈希由构建后补丁脚本 + * 写入 .hashseg 段(__expected_hash)。运行时若发现哈希不匹配, + * 拒绝继续并触发串口告警。开发期哈希为全 0 时跳过校验。 + * * Returns to DSK in all cases (success, skip via Esc, or fatal error). * DSK loads desktop.elf after this returns. */ #include "../UTSM/include/utsm/dsk.h" +/* 防篡改:linker 导出的 .text 段范围与预期哈希存储位置 */ +extern const u8 __text_start[]; +extern const u8 __text_end[]; +extern const u8 __expected_hash[]; +extern const u8 __expected_hash_end[]; + typedef signed char i8; typedef unsigned char u8; typedef unsigned short u16; @@ -253,6 +263,69 @@ static void sha256(const char *msg, u8 out[32]) { for(int i=0;i<8;i++){out[i*4]=(u8)(h[i]>>24);out[i*4+1]=(u8)(h[i]>>16);out[i*4+2]=(u8)(h[i]>>8);out[i*4+3]=(u8)h[i];} } +/* ---- 防篡改:对任意字节缓冲计算 SHA256(分块处理,支持 >55 字节) ---- */ +static void sha256_buf(const u8 *msg, u64 len, u8 out[32]) { + u32 h[8]={0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19}; + u8 block[64]; + u64 processed = 0; + while (processed + 64 <= len) { + for (int i = 0; i < 64; i++) block[i] = msg[processed + i]; + u32 w[64]; + for(int i=0;i<16;i++) w[i]=((u32)block[i*4]<<24)|((u32)block[i*4+1]<<16)|((u32)block[i*4+2]<<8)|block[i*4+3]; + for(int i=16;i<64;i++){u32 s0=ROR(w[i-15],7)^ROR(w[i-15],18)^(w[i-15]>>3);u32 s1=ROR(w[i-2],17)^ROR(w[i-2],19)^(w[i-2]>>10);w[i]=w[i-16]+s0+w[i-7]+s1;} + u32 a=h[0],b=h[1],c=h[2],d=h[3],e=h[4],f=h[5],g=h[6],hh=h[7]; + for(int i=0;i<64;i++){u32 S1=ROR(e,6)^ROR(e,11)^ROR(e,25);u32 ch=(e&f)^((~e)&g);u32 t1=hh+S1+ch+K256[i]+w[i];u32 S0=ROR(a,2)^ROR(a,13)^ROR(a,22);u32 maj=(a&b)^(a&c)^(b&c);u32 t2=S0+maj;hh=g;g=f;f=e;e=d+t1;d=c;c=b;b=a;a=t1+t2;} + h[0]+=a;h[1]+=b;h[2]+=c;h[3]+=d;h[4]+=e;h[5]+=f;h[6]+=g;h[7]+=hh; + processed += 64; + } + /* 最终块:剩余数据 + 0x80 + 长度(64 位大端) */ + u32 rem = (u32)(len - processed); + for (u32 i = 0; i < 64; i++) block[i] = 0; + for (u32 i = 0; i < rem; i++) block[i] = msg[processed + i]; + block[rem] = 0x80; + u64 bit = len * 8; + for (int i = 0; i < 8; i++) block[63 - i] = (u8)(bit >> (i * 8)); + u32 w[64]; + for(int i=0;i<16;i++) w[i]=((u32)block[i*4]<<24)|((u32)block[i*4+1]<<16)|((u32)block[i*4+2]<<8)|block[i*4+3]; + for(int i=16;i<64;i++){u32 s0=ROR(w[i-15],7)^ROR(w[i-15],18)^(w[i-15]>>3);u32 s1=ROR(w[i-2],17)^ROR(w[i-2],19)^(w[i-2]>>10);w[i]=w[i-16]+s0+w[i-7]+s1;} + u32 a=h[0],b=h[1],c=h[2],d=h[3],e=h[4],f=h[5],g=h[6],hh=h[7]; + for(int i=0;i<64;i++){u32 S1=ROR(e,6)^ROR(e,11)^ROR(e,25);u32 ch=(e&f)^((~e)&g);u32 t1=hh+S1+ch+K256[i]+w[i];u32 S0=ROR(a,2)^ROR(a,13)^ROR(a,22);u32 maj=(a&b)^(a&c)^(b&c);u32 t2=S0+maj;hh=g;g=f;f=e;e=d+t1;d=c;c=b;b=a;a=t1+t2;} + h[0]+=a;h[1]+=b;h[2]+=c;h[3]+=d;h[4]+=e;h[5]+=f;h[6]+=g;h[7]+=hh; + for(int i=0;i<8;i++){out[i*4]=(u8)(h[i]>>24);out[i*4+1]=(u8)(h[i]>>16);out[i*4+2]=(u8)(h[i]>>8);out[i*4+3]=(u8)h[i];} +} + +/* ---- 防篡改:运行时 .text 段完整性校验 ---- + * 计算 __text_start ~ __text_end 的 SHA256,与 __expected_hash 对比。 + * 返回 1=校验通过(或开发期哈希为空跳过),0=校验失败(被篡改) */ +static int login_verify_integrity(void) { + const u8 *start = __text_start; + const u8 *end = __text_end; + const u8 *expected = __expected_hash; + u64 len = (u64)(end - start); + if (len == 0) return 0; + + /* 开发期便利:预期哈希全 0 时跳过校验 */ + int all_zero = 1; + for (int i = 0; i < 32; i++) if (expected[i] != 0) { all_zero = 0; break; } + if (all_zero) { + logl("[login] integrity: expected hash empty (dev mode), skip"); + return 1; + } + + u8 actual[32]; + sha256_buf(start, len, actual); + + /* 常量时间比较 */ + u8 diff = 0; + for (int i = 0; i < 32; i++) diff |= actual[i] ^ expected[i]; + if (diff == 0) { + logl("[login] integrity: OK"); + return 1; + } + logl("[login] integrity: FAIL — code tampered!"); + return 0; +} + /* ---- conf parsing ---- * USER.CONF format (built by FirstInit's build_user_conf, but stored unencrypted * on disk for login.elf to parse): @@ -404,6 +477,14 @@ __attribute__((visibility("default"))) void dsk_entry(const dsk_boot_context *ctx) { __asm__ volatile("cli"); /* prevent IRQ1 (ps2kbd) from racing with our polling */ logl("[login] boot"); + + /* 防篡改:第一时间校验 .text 段完整性。失败则拒绝继续,立即返回。 */ + if (!login_verify_integrity()) { + logl("[login] integrity check failed, refusing to continue"); + if (ctx) ((dsk_boot_context *)ctx)->reserved[2] = 0; + return; + } + logl("[login] calibrating TSC"); tsc_calibrate_fi(); diff --git a/CODE/tools/browser/main.c b/CODE/tools/browser/main.c index 26844ad..b9934b1 100644 --- a/CODE/tools/browser/main.c +++ b/CODE/tools/browser/main.c @@ -1,22 +1,170 @@ -/* Deshab Browser — 全屏浏览器 (Stub) - * 地址栏 + 导航按钮 + 内容渲染区域。 - * 当前显示 stub 提示,Esc 退出。 +/* Deshab Browser — 全屏浏览器 + * 接入 net_stack.h 共享协议栈,实现 HTTP GET 与简单内容显示。 + * 地址栏输入 URL(如 http://10.0.2.2:8000/ 或 10.0.2.2), + * Enter 发起请求,内容区显示 HTTP 响应(分离 header/body,移除 HTML 标签)。 + * Esc 退出。 */ #include "../../firstInit/ascii_bitmaps.c" #include "../desktop_app.h" +#include "../net_stack.h" #define CHAR_STEP 12 #define CHAR_H 18 +#define URL_BUF_SIZE 128 +#define CONTENT_BUF_SIZE 32768 +#define DISPLAY_LINES 200 + +/* 前向声明(do_fetch 引用 redraw_all_imm 和 br_ns_init) */ +static void redraw_all_imm(void); +static int br_ns_init(void); static da_app_context g_ac; static da_cursor g_cursor; static da_mouse g_mouse; -static void redraw_all(void) { - da_fill_bg(&g_ac, DA_BG_PRIMARY); +/* 地址栏 */ +static char url_buf[URL_BUF_SIZE] = "http://10.0.2.2:8000/"; +static int url_len = 22; +static int url_focused = 1; /* 启动时地址栏聚焦 */ - /* 标题栏 */ +/* 内容缓冲 */ +static u8 content_buf[CONTENT_BUF_SIZE]; +static u32 content_len = 0; +static int content_scroll = 0; +static int fetching = 0; /* 1=正在请求 */ +static char status_msg[64] = "Ready"; + +/* ---- 状态消息 ---- */ +static void br_set_status(const char *s) { + int i = 0; + while (*s && i < 63) status_msg[i++] = *s++; + status_msg[i] = 0; +} + +/* ---- HTTP 请求(复制 shell cmd_curl 核心逻辑) ---- */ +static void do_fetch(void) { + if (url_len == 0) { br_set_status("Empty URL"); return; } + + br_set_status("Initializing net..."); + redraw_all_imm(); + + if (br_ns_init() != 0) { + br_set_status("Net init failed"); + return; + } + + /* 解析 URL: [http://]host[:port][/path] */ + const char *p = url_buf; + if (p[0]=='h'&&p[1]=='t'&&p[2]=='t'&&p[3]=='p'&&p[4]==':'&&p[5]=='/'&&p[6]=='/') p += 7; + + char host[64]; int hl = 0; + while (*p && *p != ':' && *p != '/' && hl < 63) host[hl++] = *p++; + host[hl] = 0; + if (!hl) { br_set_status("Bad host"); return; } + + u16 port = 80; + if (*p == ':') { + p++; + u32 pv = 0; int digits = 0; + while (*p >= '0' && *p <= '9') { pv = pv * 10 + (u32)(*p - '0'); p++; digits++; } + if (digits && pv <= 65535) port = (u16)pv; + } + char path[128]; int pl = 0; + if (*p == '/') { + while (*p && *p != ' ' && pl < 127) path[pl++] = *p++; + } + if (!pl) { path[0] = '/'; pl = 1; } + path[pl] = 0; + + /* DNS 解析 */ + br_set_status("Resolving..."); + redraw_all_imm(); + u32 ip; + if (ns_dns_resolve(host, &ip) != 0) { + br_set_status("DNS failed"); + return; + } + + /* TCP 连接 */ + br_set_status("Connecting..."); + redraw_all_imm(); + if (ns_tcp_connect(ip, port, 3000) != 0) { + br_set_status("TCP connect failed"); + return; + } + + /* 构造 HTTP/1.0 请求 */ + static char req[512]; + int r = 0; + const char *m = "GET "; while (*m) req[r++] = *m++; + for (int i = 0; i < pl; i++) req[r++] = path[i]; + m = " HTTP/1.0\r\nHost: "; while (*m) req[r++] = *m++; + for (int i = 0; i < hl; i++) req[r++] = host[i]; + m = "\r\nConnection: close\r\n\r\n"; while (*m) req[r++] = *m++; + req[r] = 0; + + br_set_status("Sending request..."); + redraw_all_imm(); + if (ns_tcp_send((const u8 *)req, (u32)r) != 0) { + br_set_status("Send failed"); + ns_tcp_close(); + return; + } + + br_set_status("Receiving..."); + redraw_all_imm(); + int n = ns_tcp_recv(0, 0, 5000); + ns_tcp_close(); + + if (n < 0) { br_set_status("Connection reset"); return; } + if (n == 0) { br_set_status("No response (timeout)"); return; } + + /* 复制响应到 content_buf */ + u32 total = ns_tcp_rx_len; + u32 copy = (total < CONTENT_BUF_SIZE) ? total : CONTENT_BUF_SIZE; + for (u32 i = 0; i < copy; i++) content_buf[i] = ns_tcp_rx[i]; + content_len = copy; + content_scroll = 0; + + char sm[32]; int sp = 0; + const char *ok = "OK "; while (*ok && sp < 30) sm[sp++] = *ok++; + char nb[12]; ns_u32_dec(nb, copy); + for (int i = 0; nb[i] && sp < 30; i++) sm[sp++] = nb[i]; + sm[sp] = 0; + br_set_status(sm); +} + +/* ---- 极简 HTML 文本提取:移除 <...> 标签,保留纯文本 ---- */ +static int html_to_text(const u8 *in, u32 in_len, u8 *out, u32 out_cap) { + u32 oi = 0; + int in_tag = 0; + int prev_space = 0; + for (u32 i = 0; i < in_len && oi < out_cap - 1; i++) { + u8 c = in[i]; + if (c == '<') { in_tag = 1; continue; } + if (c == '>') { in_tag = 0; continue; } + if (in_tag) continue; + /* 跳过 HTTP header(找到 \r\n\r\n 后才开始) */ + /* 将   等实体简化为空格 */ + if (c == '\r') continue; + if (c == '\t') c = ' '; + /* 连续空格压缩 */ + if (c == ' ') { + if (prev_space) continue; + prev_space = 1; + } else { + prev_space = 0; + } + out[oi++] = c; + } + out[oi] = 0; + return (int)oi; +} + +/* ---- 立即重绘(无鼠标光标保存/恢复,用于 fetching 过程) ---- */ +static void redraw_all_imm(void) { + da_fill_bg(&g_ac, DA_BG_PRIMARY); da_draw_titlebar(&g_ac, "Browser", (i64)g_ac.fb_w); /* 地址栏 */ @@ -24,69 +172,100 @@ static void redraw_all(void) { int bar_h = 28; int bar_x = 12; int bar_w = (int)g_ac.fb_w - 24; - da_fill_rounded_rect(&g_ac, bar_x, bar_y, bar_w, bar_h, DA_BG_TERTIARY, 6); - da_rect_outline(&g_ac, bar_x, bar_y, bar_w, bar_h, DA_BORDER, 6); + u32 bar_bg = url_focused ? DA_BG_TERTIARY : DA_BG_SECONDARY; + da_fill_rounded_rect(&g_ac, bar_x, bar_y, bar_w, bar_h, bar_bg, 6); + da_rect_outline(&g_ac, bar_x, bar_y, bar_w, bar_h, url_focused ? DA_ACCENT : DA_BORDER, 6); - /* 导航按钮 */ - int btn_w = 32; - da_fill_rounded_rect(&g_ac, bar_x + 4, bar_y + 4, btn_w, bar_h - 8, DA_BG_SECONDARY, 4); - da_draw_string(&g_ac, "<", bar_x + 14, bar_y + 5, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); - da_fill_rounded_rect(&g_ac, bar_x + 4 + btn_w + 4, bar_y + 4, btn_w, bar_h - 8, DA_BG_SECONDARY, 4); - da_draw_string(&g_ac, ">", bar_x + 4 + btn_w + 4 + 12, bar_y + 5, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); - da_fill_rounded_rect(&g_ac, bar_x + 4 + (btn_w + 4) * 2, bar_y + 4, btn_w, bar_h - 8, DA_BG_SECONDARY, 4); - da_draw_string(&g_ac, "R", bar_x + 4 + (btn_w + 4) * 2 + 12, bar_y + 5, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); + /* URL 文本 + 光标 */ + int url_x = bar_x + 12; + da_draw_string(&g_ac, url_buf, url_x, bar_y + 5, DA_TEXT_PRIMARY, bar_bg, CHAR_STEP); + if (url_focused) { + int cx = url_x + url_len * CHAR_STEP; + da_fill_rect(&g_ac, cx, bar_y + 5, 2, CHAR_H, DA_ACCENT_LIGHT); + } - /* URL 文本 */ - int url_x = bar_x + (btn_w + 4) * 3 + 8; - da_draw_string(&g_ac, "deshab://home", url_x, bar_y + 5, DA_TEXT_DIM, DA_BG_TERTIARY, CHAR_STEP); + /* 提示 */ + int hint_y = bar_y + bar_h + 4; + const char *hint = "Enter: fetch Esc: exit Up/Down: scroll"; + da_draw_string(&g_ac, hint, 12, hint_y, DA_TEXT_DIM, DA_BG_PRIMARY, CHAR_STEP); - /* 内容区域 */ - int content_y = bar_y + bar_h + 12; - int content_x = 16; - int content_w = (int)g_ac.fb_w - 32; + /* 内容区 */ + int content_y = hint_y + CHAR_H + 4; + int content_x = 12; + int content_w = (int)g_ac.fb_w - 24; + int content_h = (int)g_ac.fb_h - content_y - DA_STATUSBAR_H - 8; + da_fill_rounded_rect(&g_ac, content_x, content_y, content_w, content_h, DA_BG_SECONDARY, 4); + da_rect_outline(&g_ac, content_x, content_y, content_w, content_h, DA_BORDER, 4); - /* Stub 页面 */ - da_fill_rounded_rect(&g_ac, content_x, content_y, content_w, 200, DA_BG_SECONDARY, 8); - da_rect_outline(&g_ac, content_x, content_y, content_w, 200, DA_BORDER, 8); + /* 内容显示 */ + if (content_len > 0) { + /* 提取 body(跳过 HTTP header) */ + u32 body_off = 0; + for (u32 i = 0; i + 3 < content_len; i++) { + if (content_buf[i]=='\r' && content_buf[i+1]=='\n' && + content_buf[i+2]=='\r' && content_buf[i+3]=='\n') { + body_off = i + 4; + break; + } + } + /* HTML 转纯文本 */ + static u8 plain[CONTENT_BUF_SIZE]; + int plain_len = html_to_text(content_buf + body_off, content_len - body_off, plain, CONTENT_BUF_SIZE); - /* 居中图标 */ - int icon_cx = content_x + content_w / 2 - 32; - int icon_cy = content_y + 24; - da_fill_rounded_rect(&g_ac, icon_cx, icon_cy, 64, 48, DA_BG_TERTIARY, 8); - /* 地球图标简化:圆圈 */ - int cx = icon_cx + 32, cy = icon_cy + 24, r = 16; - for (int dy = -r; dy <= r; dy++) - for (int dx = -r; dx <= r; dx++) - if (dx*dx+dy*dy <= r*r && dx*dx+dy*dy > (r-1)*(r-1)) - da_pixel(&g_ac, cx+dx, cy+dy, DA_ACCENT); - /* 横线 */ - for (int dx = -r; dx <= r; dx++) - if (dx*dx + 0 <= r*r) - da_pixel(&g_ac, cx+dx, cy, DA_ACCENT); - /* 竖线 */ - for (int dy = -r; dy <= r; dy++) - if (0 + dy*dy <= r*r) - da_pixel(&g_ac, cx, cy+dy, DA_ACCENT); - - /* 提示文字 */ - da_draw_string(&g_ac, "Deshab Browser", - content_x + content_w / 2 - 7 * CHAR_STEP, icon_cy + 60, - DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); - da_draw_string(&g_ac, "HTTP + HTML rendering required", - content_x + content_w / 2 - 15 * CHAR_STEP, icon_cy + 82, - DA_TEXT_DIM, DA_BG_SECONDARY, CHAR_STEP); - da_draw_string(&g_ac, "Press Esc to exit", - content_x + content_w / 2 - 9 * CHAR_STEP, icon_cy + 104, - DA_TEXT_DIM, DA_BG_SECONDARY, CHAR_STEP); + /* 按行显示 */ + int visible_cols = (content_w - 16) / CHAR_STEP; + int visible_rows = (content_h - 8) / CHAR_H; + int y = content_y + 4; + int pos = 0; + int line = 0; + /* 跳过 scroll 行 */ + int skip = content_scroll; + while (pos < plain_len && skip > 0) { + if (plain[pos] == '\n') skip--; + pos++; + } + while (pos < plain_len && line < visible_rows) { + int ls = pos; + while (pos < plain_len && plain[pos] != '\n' && pos - ls < visible_cols) pos++; + int llen = pos - ls; + for (int i = 0; i < llen; i++) { + char c = (char)plain[ls + i]; + if (c < ' ' || c > '~') c = '.'; + char buf[2]; buf[0] = c; buf[1] = 0; + da_draw_string(&g_ac, buf, content_x + 8 + i * CHAR_STEP, y, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); + } + y += CHAR_H; + line++; + if (pos < plain_len && plain[pos] == '\n') pos++; + } + } else if (!fetching) { + const char *empty = "No content. Enter URL and press Enter to fetch."; + da_draw_string(&g_ac, empty, content_x + 16, content_y + 16, DA_TEXT_DIM, DA_BG_SECONDARY, CHAR_STEP); + } /* 状态栏 */ - da_draw_statusbar(&g_ac, "Browser | Stub - No network stack", (i64)g_ac.fb_w, (i64)g_ac.fb_h); + char status[96]; int p = 0; + const char *s1 = "Browser | "; while (*s1 && p < 95) status[p++] = *s1++; + for (int i = 0; status_msg[i] && p < 95; i++) status[p++] = status_msg[i]; + status[p] = 0; + da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); +} - /* 鼠标光标 */ +static void redraw_all(void) { + redraw_all_imm(); da_cursor_save(&g_ac, &g_cursor); da_cursor_draw(&g_ac, &g_cursor, DA_CURSOR_COLOR); } +/* ---- net_stack.h 的 ns_init 需要 dkm_kernel_api ---- */ +/* 重写 ns_init 以从 da_app_context 获取 kernel_api */ +static int br_ns_init(void) { + if (ns_ready) return 0; + u64 dkm_kernel_api = (u64)g_ac.kernel_api; + if (!dkm_kernel_api) return -1; + return ns_init(dkm_kernel_api); +} + __attribute__((visibility("default"))) void dsk_entry(const da_boot_context *ctx) { __asm__ volatile("cli"); @@ -100,25 +279,85 @@ void dsk_entry(const da_boot_context *ctx) { da_init(&g_ac, ctx); da_cursor_init(&g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h); da_mouse_init(); + + /* 初始化网络协议栈 */ + br_ns_init(); + + br_set_status("Ready"); redraw_all(); + int e0 = 0, shift = 0; for (;;) { int need_redraw = 0; /* 鼠标轮询 */ - if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h)) + if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h)) { need_redraw = 1; + /* 点击地址栏聚焦 */ + int bar_y = DA_TITLEBAR_H + 6; + int bar_h = 28; + if (g_cursor.btn && + g_cursor.my >= bar_y && g_cursor.my < bar_y + bar_h && + g_cursor.mx >= 12 && g_cursor.mx < (int)g_ac.fb_w - 12) { + url_focused = 1; + need_redraw = 1; + } + } /* 键盘轮询 */ u8 st = inb(0x64); if ((st & 1) && !(st & 0x20)) { u8 data = inb(0x60); - if (!(data & 0x80) && data == 0x01) { + u8 sc = data; + if (sc == 0xE0) { e0 = 1; goto skip; } + if (sc == 0x2A || sc == 0x36) { shift = 1; goto skip; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; goto skip; } + if (sc & 0x80) { e0 = 0; goto skip; } + + if (sc == 0x01) { /* Esc */ da_slog("browser", "exit"); return; } - } + /* 地址栏输入模式 */ + if (url_focused) { + if (sc == 0x1C) { /* Enter - 发起请求 */ + url_focused = 0; + fetching = 1; + br_set_status("Fetching..."); + redraw_all(); + do_fetch(); + fetching = 0; + need_redraw = 1; + goto skip; + } + if (sc == 0x0E) { /* Backspace */ + if (url_len > 0) { url_len--; url_buf[url_len] = 0; } + need_redraw = 1; + goto skip; + } + char c = da_scan_to_ascii(sc, shift); + if (c && c >= 32 && c <= 126 && url_len < URL_BUF_SIZE - 1) { + /* 小写转大写便于 URL 输入(除协议头) */ + url_buf[url_len++] = c; + url_buf[url_len] = 0; + need_redraw = 1; + } + goto skip; + } + + /* 内容浏览模式 */ + if (e0) { + if (sc == 0x48) { if (content_scroll > 0) content_scroll--; need_redraw = 1; } + else if (sc == 0x50) { content_scroll++; need_redraw = 1; } + e0 = 0; + goto skip; + } + if (sc == 0x49) { content_scroll -= 10; if (content_scroll < 0) content_scroll = 0; need_redraw = 1; } + else if (sc == 0x51) { content_scroll += 10; need_redraw = 1; } + else if (sc == 0x1C) { url_focused = 1; need_redraw = 1; } /* Enter 回到地址栏 */ + } + skip: if (need_redraw) { da_cursor_restore(&g_ac, &g_cursor); redraw_all(); diff --git a/CODE/tools/desktop_app.h b/CODE/tools/desktop_app.h index 82cada8..5a4ac60 100644 --- a/CODE/tools/desktop_app.h +++ b/CODE/tools/desktop_app.h @@ -40,8 +40,9 @@ typedef struct { u64 kernel_stack_top; u64 reserved[8]; } da_boot_context; -/* block_read 函数类型 */ -typedef int (*da_block_read_fn)(void *ctx, u64 lba, u32 count, void *buffer); +/* block 设备函数类型(与 shell/fat32_io.h 一致:u32 index 固定 0) */ +typedef int (*da_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer); +typedef int (*da_block_write_fn)(u32 index, u64 lba, u32 count, const void *buffer); /* 应用上下文 — 从 dsk_boot_context 提取的常用数据 */ typedef struct { @@ -49,22 +50,27 @@ typedef struct { u64 fb_w; /* 宽度 */ u64 fb_h; /* 高度 */ u64 fb_pitch; /* 行宽(字节) */ - da_block_read_fn block_read; + da_block_read_fn block_read; + da_block_write_fn block_write; const void *kernel_api; } da_app_context; -/* 从 dsk_boot_context 初始化 da_app_context */ +/* 从 dsk_boot_context 初始化 da_app_context。 + * block API 从 kernel_api + 0xA8 获取:read @ +0x10,write @ +0x18。 */ static inline void da_init(da_app_context *ac, const da_boot_context *ctx) { ac->fb = (u32 *)ctx->framebuffer_address; ac->fb_w = ctx->framebuffer_width; ac->fb_h = ctx->framebuffer_height; ac->fb_pitch = ctx->framebuffer_pitch; ac->kernel_api = (const void *)ctx->dkm_kernel_api; - /* block_read 从 kernel_api + 0xA8 获取 */ + ac->block_read = 0; + ac->block_write = 0; u64 api = ctx->dkm_kernel_api; - ac->block_read = (da_block_read_fn)*(u64 *)(api + 0xA8); - if (ac->block_read) - ac->block_read = (da_block_read_fn)*(u64 *)((u64)ac->block_read + 16); + u64 blk_api = *(u64 *)(api + 0xA8); + if (blk_api) { + ac->block_read = (da_block_read_fn)*(u64 *)(blk_api + 0x10); + ac->block_write = (da_block_write_fn)*(u64 *)(blk_api + 0x18); + } } /* ========== 帧缓冲绘制 ========== */ @@ -285,10 +291,12 @@ static inline int da_mouse_poll(da_mouse *m, da_cursor *c, i64 fb_w, i64 fb_h) { if (m->idx < 3) return 0; m->idx = 0; if (!(m->buf[0] & 0x08)) return 0; + /* PS/2 鼠标包:buf[1]/buf[2] 已是 9 位补码的低 8 位, + * (signed char) 强转即可正确符号扩展,无需再依据 buf[0] 的 sign bit 反转。 + * X 直接相加;Y 需翻转一次(PS/2 Y 向上为正,屏幕 Y 向下为正)。 + * 与 desktop/main.c:ps2_mouse_poll 保持一致。 */ int dx = (int)(signed char)m->buf[1]; int dy = (int)(signed char)m->buf[2]; - if (m->buf[0] & 0x10) dx = -dx; - if (!(m->buf[0] & 0x20)) dy = -dy; else dy = -dy; dy = -dy; c->mx += dx; c->my += dy; if (c->mx < 0) c->mx = 0; diff --git a/CODE/tools/editor/main.c b/CODE/tools/editor/main.c index fdb863a..866852c 100644 --- a/CODE/tools/editor/main.c +++ b/CODE/tools/editor/main.c @@ -1,14 +1,18 @@ /* Deshab Editor — 全屏文本编辑器 * PS/2 键盘输入,行号 + 光标 + 垂直滚动。 - * Ctrl+O 打开文件,Ctrl+S 保存(暂不支持),Esc 退出。 + * Ctrl+O 打开文件(输入 8.3 名,从 FAT32 根目录读取) + * Ctrl+S 保存文件(输入 8.3 名,写入 FAT32 根目录) + * Esc 退出。 */ #include "../../firstInit/ascii_bitmaps.c" #include "../desktop_app.h" +#include "../fat32_io.h" #define CHAR_STEP 12 #define CHAR_H 18 #define EDITOR_BUF_SIZE 65536 +#define INPUT_BUF_SIZE 16 static da_app_context g_ac; @@ -22,6 +26,11 @@ static int modified = 0; static char filename[64] = "untitled"; static int ctrl_down = 0; +/* 输入模式:Ctrl+O/Ctrl+S 时进入,在状态栏输入文件名 */ +static int input_mode = 0; /* 0=编辑, 1=open, 2=save */ +static char input_buf[INPUT_BUF_SIZE]; +static int input_len = 0; + static int line_start(int line) { int l = 0; for (int i = 0; i < text_len; i++) { @@ -47,6 +56,63 @@ static void init_default_text(void) { recalc_cursor(); } +/* ---- 文件打开/保存 ---- */ +static void do_open(void) { + if (input_len == 0) return; + char name11[11]; + if (f32_name_to_83(input_buf, name11) != 0) { + da_slog("editor", "open: bad name"); + return; + } + u8 *data = 0; u32 size = 0; + if (f32_read_root_file(name11, &data, &size) != 0) { + da_slog("editor", "open: read failed"); + return; + } + if (size > EDITOR_BUF_SIZE) size = EDITOR_BUF_SIZE; + text_len = (int)size; + for (int i = 0; i < text_len; i++) text_buf[i] = (char)data[i]; + cursor_pos = 0; + modified = 0; + /* 更新 filename 显示 */ + int p = 0; + for (int i = 0; i < input_len && p < 60; i++) filename[p++] = input_buf[i]; + filename[p] = 0; + recalc_cursor(); + da_slog("editor", "open: ok"); +} + +static void do_save(void) { + if (input_len == 0) return; + char name11[11]; + if (f32_name_to_83(input_buf, name11) != 0) { + da_slog("editor", "save: bad name"); + return; + } + if (f32_write_root_file(name11, (const u8 *)text_buf, (u32)text_len) != 0) { + da_slog("editor", "save: write failed"); + return; + } + modified = 0; + int p = 0; + for (int i = 0; i < input_len && p < 60; i++) filename[p++] = input_buf[i]; + filename[p] = 0; + da_slog("editor", "save: ok"); +} + +static void enter_input_mode(int mode) { + input_mode = mode; + input_len = 0; + input_buf[0] = 0; +} + +static void commit_input(void) { + int mode = input_mode; + input_mode = 0; + if (mode == 1) do_open(); + else if (mode == 2) do_save(); +} + static void redraw_all(void) { da_fill_bg(&g_ac, DA_BG_SECONDARY); char title[80]; @@ -74,16 +140,13 @@ static void redraw_all(void) { for (; pos <= text_len && line < scroll_y + visible_rows; ) { if (line >= scroll_y) { int screen_y = top_y + (line - scroll_y) * CHAR_H; - /* 行号 */ char ln[8]; ln[0] = '0' + ((line+1)/100)%10; ln[1] = '0' + ((line+1)/10)%10; ln[2] = '0' + (line+1)%10; ln[3] = 0; da_draw_string(&g_ac, ln, margin_x, screen_y, DA_TEXT_DIM, DA_BG_SECONDARY, CHAR_STEP); - /* 分隔线 */ da_fill_rect(&g_ac, margin_x + line_num_w - 2, screen_y, 1, CHAR_H, DA_BORDER); - /* 文本 */ int col = 0; while (pos < text_len && text_buf[pos] != '\n' && col < visible_cols) { da_draw_char(&g_ac, text_buf[pos], @@ -97,25 +160,35 @@ static void redraw_all(void) { if (pos < text_len && text_buf[pos] == '\n') pos++; line++; if (pos >= text_len) { - /* 空行也画光标 */ if (line > cursor_line) break; } } - /* 光标 */ - if (cursor_line >= scroll_y && cursor_line < scroll_y + visible_rows) { + /* 光标(编辑模式) */ + if (!input_mode && cursor_line >= scroll_y && cursor_line < scroll_y + visible_rows) { int cy = top_y + (cursor_line - scroll_y) * CHAR_H; int cx = text_x + cursor_col * CHAR_STEP; da_fill_rect(&g_ac, cx, cy, 2, CHAR_H, DA_ACCENT_LIGHT); } /* 状态栏 */ - char status[64]; p = 0; - const char *s1 = "L:"; while (*s1) status[p++] = *s1++; - status[p++] = '0' + (cursor_line+1)/10%10; status[p++] = '0' + (cursor_line+1)%10; - const char *s2 = " C:"; while (*s2) status[p++] = *s2++; - status[p++] = '0' + (cursor_col+1)/10%10; status[p++] = '0' + (cursor_col+1)%10; - if (modified) { const char *m = " *"; while (*m) status[p++] = *m++; } + char status[96]; p = 0; + if (input_mode) { + const char *prompt = (input_mode == 1) ? "Open: " : "Save: "; + while (*prompt) status[p++] = *prompt++; + for (int i = 0; i < input_len; i++) status[p++] = input_buf[i]; + status[p++] = '_'; + const char *hint = " Enter:confirm Esc:cancel"; + while (*hint && p < 95) status[p++] = *hint++; + } else { + const char *s1 = "L:"; while (*s1) status[p++] = *s1++; + status[p++] = '0' + (cursor_line+1)/10%10; status[p++] = '0' + (cursor_line+1)%10; + const char *s2 = " C:"; while (*s2) status[p++] = *s2++; + status[p++] = '0' + (cursor_col+1)/10%10; status[p++] = '0' + (cursor_col+1)%10; + if (modified) { const char *m = " *"; while (*m) status[p++] = *m++; } + const char *s3 = " Ctrl+O:Open Ctrl+S:Save Esc:Exit"; + while (*s3 && p < 95) status[p++] = *s3++; + } status[p] = 0; da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); } @@ -131,6 +204,7 @@ void dsk_entry(const da_boot_context *ctx) { } da_init(&g_ac, ctx); + f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write); init_default_text(); redraw_all(); @@ -148,6 +222,36 @@ void dsk_entry(const da_boot_context *ctx) { if (sc == 0x9D) { ctrl_down = 0; continue; } if (sc & 0x80) { e0 = 0; continue; } + /* Esc:输入模式取消 / 编辑模式退出 */ + if (sc == 0x01 && !e0) { + if (input_mode) { input_mode = 0; redraw_all(); continue; } + da_slog("editor", "exit"); + return; + } + + /* ---- 输入模式:输入文件名 ---- */ + if (input_mode) { + if (sc == 0x1C) { /* Enter - 确认 */ + commit_input(); + redraw_all(); + continue; + } + if (sc == 0x0E) { /* Backspace */ + if (input_len > 0) input_len--; + input_buf[input_len] = 0; + redraw_all(); + continue; + } + char c = da_scan_to_ascii(sc, shift); + if (c && c >= 32 && c <= 126 && input_len < INPUT_BUF_SIZE - 1) { + input_buf[input_len++] = c; + input_buf[input_len] = 0; + redraw_all(); + } + continue; + } + + /* ---- 编辑模式 ---- */ /* Esc 退出 */ if (sc == 0x01 && !e0) { da_slog("editor", "exit"); return; } @@ -212,16 +316,16 @@ void dsk_entry(const da_boot_context *ctx) { modified = 1; } } else { - /* Ctrl+O: 打开文件(提示暂不支持) */ + /* Ctrl+O: 打开文件 */ if (ctrl_down && (sc == 0x18)) { - /* Ctrl+O - stub */ - da_slog("editor", "Ctrl+O - open not yet"); + enter_input_mode(1); + redraw_all(); continue; } - /* Ctrl+S: 保存(提示暂不支持) */ + /* Ctrl+S: 保存文件 */ if (ctrl_down && (sc == 0x1F)) { - da_slog("editor", "Ctrl+S - save not yet"); - modified = 0; + enter_input_mode(2); + redraw_all(); continue; } char c = da_scan_to_ascii(sc, shift); diff --git a/CODE/tools/fat32_io.h b/CODE/tools/fat32_io.h new file mode 100644 index 0000000..653a3c4 --- /dev/null +++ b/CODE/tools/fat32_io.h @@ -0,0 +1,557 @@ +/* fat32_io.h — Deshab 用户态共享 FAT32 读写库(static inline,无库) + * + * 前置条件:包含者已定义 u8/u16/u32/u64/i64 基础类型。 + * 通过 f32_init() 注入 block_read/block_write 函数指针(来自 dkm_kernel_api + 0xA8)。 + * + * 能力:FAT32 根目录文件的读/写/删/列举,支持 FAT 链跟随、多簇文件。 + * 限制:仅根目录(不支持子目录,Phase 2.2 fileman 将扩展);单文件最大 256KB。 + * + * 用法: + * f32_init(block_read, block_write) — 初始化(block_write 可为 NULL,仅读场景) + * f32_name_to_83("file.txt", name11) — "file.txt" → 11 字符 8.3 名 + * f32_read_root_file(name11, &data, &size) — 读根目录文件到 f32_data 缓冲 + * f32_write_root_file(name11, data, size) — 写/替换根目录文件 + * f32_delete_root_file(name11) — 删除根目录文件 + * f32_list_root(emit_cb, user_data) — 列举根目录 + * + * 缓冲区:每个包含本头的 .elf 独立持有 256KB g_disk + 4KB g_cluster + 256KB g_fdata(共 ~516KB BSS)。 + * 与 net_stack.h 同模式,接受 static 缓冲的代价以换取无库依赖。 + */ + +#ifndef DESHAB_FAT32_IO_H +#define DESHAB_FAT32_IO_H + +/* ---- block 设备函数类型(与 shell/DSK 一致:index 参数固定 0) ---- */ +typedef int (*f32_block_read_fn)(u32 index, u64 lba, u32 count, void *buf); +typedef int (*f32_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf); + +static f32_block_read_fn f32_blk_read = 0; +static f32_block_write_fn f32_blk_write = 0; + +/* ---- FAT32 BPB / 目录项结构(packed,与 DSK/shell 一致) ---- */ +typedef struct __attribute__((packed)) { + u8 jmp[3]; char oem[8]; u16 bps; u8 spc; u16 rsvd; u8 fc; u16 root_ent; + u16 ts16; u8 media; u16 spf16; u16 spt; u16 heads; u32 hidden; u32 ts32; + u32 spf; u16 flags; u16 ver; u32 root_clus; u16 fsi; u16 bkboot; + u8 res[12]; u8 drv; u8 ntfl; u8 sig; u32 ser; char lbl[11]; char typ[8]; + u8 code[420]; u16 boot_sig; +} f32_bpb; + +typedef struct __attribute__((packed)) { + char name[11]; u8 attr; u8 ntr; u8 ctenth; + u16 ctime; u16 cdate; u16 adate; u16 chigh; + u16 wtime; u16 wdate; u16 clow; u32 fsize; +} f32_dirent; + +/* ---- 内部小工具 ---- */ +static inline u32 f32_r32(const u8 *p) { return (u32)p[0]|((u32)p[1]<<8)|((u32)p[2]<<16)|((u32)p[3]<<24); } +static inline u16 f32_r16(const u8 *p) { return (u16)p[0]|((u16)p[1]<<8); } +static inline int f32_neq11(const char *a, const char *b) { + for (int i = 0; i < 11; i++) if (a[i] != b[i]) return 0; + return 1; +} + +/* ---- 批量读写缓冲区(与 DSK/shell 对齐:256 扇区覆盖 BPB+FAT+根目录+小文件) ---- */ +static u8 f32_disk[131072]; /* 256 扇区 BPB+FAT+根目录缓存 */ +static u8 f32_cluster[4096]; /* 单簇缓冲 */ +static u8 f32_data[262144]; /* 文件数据缓冲(最大 256KB) */ +static int f32_disk_loaded = 0; /* f32_disk 是否已加载 BPB+FAT */ + +/* 初始化:注入 block_read/block_write 函数指针 */ +static inline void f32_init(f32_block_read_fn rd, f32_block_write_fn wr) { + f32_blk_read = rd; + f32_blk_write = wr; + f32_disk_loaded = 0; +} + +static inline int f32_read_sectors(u32 lba, u32 count, u8 *out) { + return f32_blk_read ? f32_blk_read(0, lba, count, out) : -1; +} +static inline int f32_write_sectors(u32 lba, u32 count, const u8 *buf) { + return f32_blk_write ? f32_blk_write(0, lba, count, buf) : -1; +} + +/* 确保 f32_disk 已加载 BPB+FAT+根目录(256 扇区) */ +static inline int f32_disk_load(void) { + if (f32_disk_loaded) return 0; + if (f32_read_sectors(0, 256, f32_disk) != 0) return -1; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + if (bpb->boot_sig != 0xAA55 || bpb->bps != 512) return -2; + if (bpb->spf == 0 || bpb->root_clus < 2) return -3; + f32_disk_loaded = 1; + return 0; +} + +/* ---- 8.3 名字转换 ---- */ + +/* "file.txt" / "NAME" → 11 字符 8.3 名(大写,空格填充) + * 返回 0 成功,-1 失败(过长或非法字符) */ +static inline int f32_name_to_83(const char *in, char out[11]) { + for (int i = 0; i < 11; i++) out[i] = ' '; + int inlen = 0; + while (in[inlen] && inlen < 13) inlen++; + if (inlen == 0 || inlen > 12) return -1; + int dot = -1; + for (int i = 0; i < inlen; i++) if (in[i] == '.') dot = i; + int base_end = (dot >= 0) ? dot : inlen; + if (base_end == 0 || base_end > 8) return -1; + for (int j = 0; j < base_end; j++) { + char c = in[j]; + if (c >= 'a' && c <= 'z') c -= 32; + if (c == '.' || c == ' ' || c == '/' || c == '\\') return -1; + out[j] = c; + } + if (dot >= 0) { + int extlen = inlen - dot - 1; + if (extlen > 3) return -1; + for (int j = 0; j < extlen; j++) { + char c = in[dot + 1 + j]; + if (c >= 'a' && c <= 'z') c -= 32; + if (c == '.' || c == ' ' || c == '/' || c == '\\') return -1; + out[8 + j] = c; + } + } + return 0; +} + +/* 11 字符 8.3 名 → 可显示字符串(如 "README.TXT") */ +static inline void f32_name_from_83(const char in[11], char out[13]) { + int p = 0; + for (int j = 0; j < 8; j++) { + if (in[j] == ' ') break; + out[p++] = in[j]; + } + if (in[8] != ' ') { + out[p++] = '.'; + for (int j = 8; j < 11; j++) { + if (in[j] == ' ') break; + out[p++] = in[j]; + } + } + out[p] = 0; +} + +/* ---- 根目录文件查找 ---- */ + +/* 在根目录簇链中查找 11 字符名,输出首簇号与文件大小 */ +static inline int f32_find_in_root(const u8 *clus, u32 clus_sectors, const char *target, + u32 *out_clus, u32 *out_size, u32 *out_idx) { + const f32_dirent *dir = (const f32_dirent *)clus; + for (u32 e = 0; e * 32 < clus_sectors * 512; e++) { + if (dir[e].name[0] == 0) break; + if ((u8)dir[e].name[0] == 0xE5) continue; + if (dir[e].attr == 0x0F) continue; + if (dir[e].attr & 0x08) continue; + if (f32_neq11(dir[e].name, target)) { + *out_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16); + *out_size = dir[e].fsize; + if (out_idx) *out_idx = e; + return 0; + } + } + return -1; +} + +/* 读取根目录下指定 8.3 名文件到 f32_data。 + * 返回 0 成功,*out_data 指向 f32_data,*out_size 为字节数;非 0 失败。 */ +static inline int f32_read_root_file(const char *name11, u8 **out_data, u32 *out_size) { + if (f32_disk_load() != 0) return -1; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf; + u32 fat_byte_off = bpb->rsvd * 512; + u32 spc = bpb->spc; + + u32 clus = bpb->root_clus; + u32 found_clus = 0, found_size = 0; + int found = 0; + while (clus >= 2 && clus < 0x0FFFFFF8 && !found) { + u32 lba = data_lba + (clus - 2) * spc; + const u8 *cb; + if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512) + cb = f32_disk + (u64)lba * 512; + else { + if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4; + cb = f32_cluster; + } + if (f32_find_in_root(cb, spc, name11, &found_clus, &found_size, 0) == 0) { found = 1; break; } + u32 fo = fat_byte_off + clus * 4; + if (fo + 4 > sizeof(f32_disk)) break; + clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } + if (!found) return -5; + if (found_size > sizeof(f32_data)) return -6; + + u8 *dst = f32_data; u32 remaining = found_size; u32 fc = found_clus; + while (fc >= 2 && fc < 0x0FFFFFF8 && remaining > 0) { + u32 fc_lba = data_lba + (fc - 2) * spc; + u32 fc_bytes = spc * 512; + if (fc_bytes > remaining) fc_bytes = remaining; + const u8 *fb; + if ((u64)fc_lba * 512 + (u64)spc * 512 <= 256ULL * 512) + fb = f32_disk + (u64)fc_lba * 512; + else { + if (f32_read_sectors(fc_lba, 8, f32_cluster) != 0) return -7; + fb = f32_cluster; + } + for (u32 b = 0; b < fc_bytes; b++) dst[b] = fb[b]; + dst += fc_bytes; remaining -= fc_bytes; + u32 fo = fat_byte_off + fc * 4; + if (fo + 4 > sizeof(f32_disk)) break; + fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } + *out_data = f32_data; *out_size = found_size; + return 0; +} + +/* 写入根目录下指定 8.3 名文件(存在则替换,不存在则新建)。 + * 依赖 f32_disk 已加载 BPB+FAT。返回 0 成功。 */ +static inline int f32_write_root_file(const char *name11, const u8 *data, u32 size) { + if (f32_disk_load() != 0) return -1; + if (!f32_blk_write) return -10; + /* 写入前重新加载 BPB+FAT,保证与盘上一致 */ + if (f32_read_sectors(0, 256, f32_disk) != 0) return -2; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + u32 spc = bpb->spc; + u32 fat_lba = bpb->rsvd; + u32 fat_sectors = bpb->spf; + u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf; + u32 fat_byte_off = bpb->rsvd * 512; + u32 root_clus = bpb->root_clus; + u32 cluster_bytes = spc * 512; + + u32 root_lba = data_lba + (root_clus - 2) * spc; + u8 *root_buf = f32_disk + (u64)root_lba * 512; + u32 max_entries = cluster_bytes / 32; + f32_dirent *dir = (f32_dirent *)root_buf; + int free_entry = -1; + u32 existing_clus = 0; + int found = 0; + u32 existing_idx = 0; + for (u32 e = 0; e < max_entries; e++) { + if (dir[e].name[0] == 0) { if (free_entry < 0) free_entry = (int)e; break; } + if ((u8)dir[e].name[0] == 0xE5) { if (free_entry < 0) free_entry = (int)e; continue; } + if (dir[e].attr == 0x0F) continue; + if (dir[e].attr & 0x08) continue; + if (f32_neq11(dir[e].name, name11)) { + existing_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16); + found = 1; existing_idx = e; + if (free_entry < 0) free_entry = (int)e; + break; + } + } + + u32 bytes_needed = size > 0 ? size : 1; + u32 clusters_needed = (bytes_needed + cluster_bytes - 1) / cluster_bytes; + u32 first_clus = 0; + u32 prev_clus = 0; + + if (found && existing_clus >= 2) { + first_clus = existing_clus; + u32 cur = existing_clus; + u32 count = 0; + while (cur >= 2 && cur < 0x0FFFFFF8 && count < clusters_needed) { + prev_clus = cur; + u32 fo = fat_byte_off + cur * 4; + if (fo + 4 > sizeof(f32_disk)) break; + cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + count++; + } + while (count < clusters_needed) { + u32 newc = 0; + for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) { + u32 fo = fat_byte_off + c * 4; + if (fo + 4 > sizeof(f32_disk)) break; + if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; } + } + if (newc == 0) return -3; + u32 pfo = fat_byte_off + prev_clus * 4; + if (pfo + 4 <= sizeof(f32_disk)) { + f32_disk[pfo] = (u8)(newc & 0xFF); + f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF); + f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF); + f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F); + } + prev_clus = newc; + count++; + } + if (prev_clus >= 2) { + u32 fo = fat_byte_off + prev_clus * 4; + if (fo + 4 <= sizeof(f32_disk)) { + f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F; + } + } + /* 释放多余旧簇 */ + u32 next = 0; + u32 fo = fat_byte_off + prev_clus * 4; + if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + while (next >= 2 && next < 0x0FFFFFF8) { + u32 nfo = fat_byte_off + next * 4; + u32 nn = 0; + if (nfo + 4 <= sizeof(f32_disk)) nn = f32_r32(f32_disk + nfo) & 0x0FFFFFFF; + f32_disk[nfo] = 0; f32_disk[nfo+1] = 0; f32_disk[nfo+2] = 0; f32_disk[nfo+3] = 0; + next = nn; + } + } else { + if (free_entry < 0) return -4; + for (u32 i = 0; i < clusters_needed; i++) { + u32 newc = 0; + for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) { + u32 fo = fat_byte_off + c * 4; + if (fo + 4 > sizeof(f32_disk)) break; + if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; } + } + if (newc == 0) return -3; + if (i == 0) first_clus = newc; + if (prev_clus >= 2) { + u32 pfo = fat_byte_off + prev_clus * 4; + f32_disk[pfo] = (u8)(newc & 0xFF); + f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF); + f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF); + f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F); + } + prev_clus = newc; + } + if (prev_clus >= 2) { + u32 fo = fat_byte_off + prev_clus * 4; + f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F; + } + } + + /* 写数据到簇 */ + u32 remaining = size; + const u8 *src = data; + u32 cur = first_clus; + u32 ci = 0; + while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) { + u32 clba = data_lba + (cur - 2) * spc; + u32 chunk = remaining < cluster_bytes ? remaining : cluster_bytes; + if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) { + u8 *dst = f32_disk + (u64)clba * 512; + for (u32 b = 0; b < chunk; b++) dst[b] = src[b]; + for (u32 b = chunk; b < cluster_bytes; b++) dst[b] = 0; + } else { + if (f32_read_sectors(clba, spc, f32_cluster) != 0) return -5; + for (u32 b = 0; b < chunk; b++) f32_cluster[b] = src[b]; + for (u32 b = chunk; b < cluster_bytes; b++) f32_cluster[b] = 0; + if (f32_write_sectors(clba, spc, f32_cluster) != 0) return -6; + } + src += chunk; remaining -= chunk; ci++; + u32 fo = fat_byte_off + cur * 4; + if (fo + 4 > sizeof(f32_disk)) break; + cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } + + /* 更新目录项 */ + u32 entry_idx = found ? existing_idx : (u32)free_entry; + f32_dirent *e = &dir[entry_idx]; + if (!found) { + for (int i = 0; i < 11; i++) e->name[i] = name11[i]; + e->attr = 0x20; e->ntr = 0; e->ctenth = 0; e->ctime = 0; e->cdate = 0; e->adate = 0; + } + e->chigh = (u16)((first_clus >> 16) & 0xFFFF); + e->clow = (u16)(first_clus & 0xFFFF); + e->fsize = size; + + /* 回写 FAT(两份)+ 根目录 + 缓冲区内的数据簇 */ + for (u32 f = 0; f < bpb->fc; f++) { + u32 flba = fat_lba + f * fat_sectors; + if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -7; + } + u32 root_dir_lba = data_lba + (root_clus - 2) * spc; + if ((u64)root_dir_lba * 512 + cluster_bytes <= 256ULL * 512) { + if (f32_write_sectors(root_dir_lba, spc, f32_disk + (u64)root_dir_lba * 512) != 0) return -8; + } + cur = first_clus; ci = 0; + while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) { + u32 clba = data_lba + (cur - 2) * spc; + if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) { + if (f32_write_sectors(clba, spc, f32_disk + (u64)clba * 512) != 0) return -9; + } + u32 fo = fat_byte_off + cur * 4; + if (fo + 4 > sizeof(f32_disk)) break; + cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + ci++; + } + f32_disk_loaded = 0; /* 下次操作重新加载,避免使用脏缓存 */ + return 0; +} + +/* 删除根目录下指定 8.3 名文件:清空簇链 + 标记目录项为 0xE5。 + * 返回 0 成功,-1 未找到,其他负值失败。 */ +static inline int f32_delete_root_file(const char *name11) { + if (f32_disk_load() != 0) return -2; + if (!f32_blk_write) return -10; + if (f32_read_sectors(0, 256, f32_disk) != 0) return -3; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + u32 spc = bpb->spc; + u32 fat_lba = bpb->rsvd; + u32 fat_sectors = bpb->spf; + u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf; + u32 fat_byte_off = bpb->rsvd * 512; + u32 root_clus = bpb->root_clus; + + u32 root_lba = data_lba + (root_clus - 2) * spc; + u8 *root_buf = f32_disk + (u64)root_lba * 512; + u32 max_entries = (spc * 512) / 32; + f32_dirent *dir = (f32_dirent *)root_buf; + u32 found_idx = 0xFFFFFFFFu; + for (u32 e = 0; e < max_entries; e++) { + if (dir[e].name[0] == 0) break; + if ((u8)dir[e].name[0] == 0xE5) continue; + if (dir[e].attr == 0x0F) continue; + if (dir[e].attr & 0x08) continue; + if (f32_neq11(dir[e].name, name11)) { found_idx = e; break; } + } + if (found_idx == 0xFFFFFFFFu) return -1; + + /* 释放簇链 */ + u32 cur = (u32)f32_r16((const u8*)&dir[found_idx].clow) | ((u32)f32_r16((const u8*)&dir[found_idx].chigh) << 16); + while (cur >= 2 && cur < 0x0FFFFFF8) { + u32 fo = fat_byte_off + cur * 4; + u32 next = 0; + if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + f32_disk[fo] = 0; f32_disk[fo+1] = 0; f32_disk[fo+2] = 0; f32_disk[fo+3] = 0; + cur = next; + } + /* 标记目录项为已删除 */ + dir[found_idx].name[0] = (char)0xE5; + + /* 回写 FAT + 根目录 */ + for (u32 f = 0; f < bpb->fc; f++) { + u32 flba = fat_lba + f * fat_sectors; + if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -4; + } + if (f32_write_sectors(root_lba, spc, f32_disk + (u64)root_lba * 512) != 0) return -5; + f32_disk_loaded = 0; + return 0; +} + +/* 列举根目录条目回调签名:返回 0 继续,非 0 停止 */ +typedef int (*f32_list_cb)(const char *name, u32 size, u8 attr, void *user_data); + +/* 列出根目录所有文件/目录,调用回调输出。 + * 回调返回非 0 立即停止。返回 0 成功(含回调提前停止),负值失败。 */ +static inline int f32_list_root(f32_list_cb emit, void *user_data) { + if (f32_disk_load() != 0) return -1; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf; + u32 fat_byte_off = bpb->rsvd * 512; + u32 spc = bpb->spc; + u32 clus = bpb->root_clus; + while (clus >= 2 && clus < 0x0FFFFFF8) { + u32 lba = data_lba + (clus - 2) * spc; + const u8 *cb; + if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512) + cb = f32_disk + (u64)lba * 512; + else { + if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4; + cb = f32_cluster; + } + const f32_dirent *dir = (const f32_dirent *)cb; + for (u32 e = 0; e * 32 < spc * 512; e++) { + if (dir[e].name[0] == 0) goto done; + if ((u8)dir[e].name[0] == 0xE5) continue; + if (dir[e].attr == 0x0F) continue; + if (dir[e].attr & 0x08) continue; + char disp[13]; + f32_name_from_83(dir[e].name, disp); + if (emit(disp, dir[e].fsize, dir[e].attr, user_data) != 0) goto done; + } + u32 fo = fat_byte_off + clus * 4; + if (fo + 4 > sizeof(f32_disk)) break; + clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } +done: + return 0; +} + +/* ---- 子目录遍历支持(Phase 2.2 fileman 扩展) ---- */ + +/* 目录条目(供 fileman 等工具使用) */ +typedef struct { + char name[13]; /* 8.3 显示名 */ + u32 clus; /* 首簇号 */ + u32 size; /* 文件大小(目录为 0) */ + u8 attr; + u8 is_dir; +} f32_entry; + +typedef int (*f32_list_entry_cb)(const f32_entry *e, void *user_data); + +/* 列举指定目录簇的内容。 + * dir_clus = 0 表示根目录(自动从 BPB 取 root_clus)。 + * 回调返回非 0 立即停止。返回 0 成功,负值失败。 */ +static inline int f32_list_dir(u32 dir_clus, f32_list_entry_cb emit, void *user_data) { + if (f32_disk_load() != 0) return -1; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf; + u32 fat_byte_off = bpb->rsvd * 512; + u32 spc = bpb->spc; + u32 clus = (dir_clus == 0) ? bpb->root_clus : dir_clus; + while (clus >= 2 && clus < 0x0FFFFFF8) { + u32 lba = data_lba + (clus - 2) * spc; + const u8 *cb; + if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512) + cb = f32_disk + (u64)lba * 512; + else { + if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4; + cb = f32_cluster; + } + const f32_dirent *dir = (const f32_dirent *)cb; + for (u32 e = 0; e * 32 < spc * 512; e++) { + if (dir[e].name[0] == 0) goto done; + if ((u8)dir[e].name[0] == 0xE5) continue; + if (dir[e].attr == 0x0F) continue; + if (dir[e].attr & 0x08) continue; + /* 跳过 "." 和 ".." 条目 */ + if (dir[e].name[0] == '.' && (dir[e].name[1] == ' ' || dir[e].name[1] == '.')) + continue; + f32_entry fe; + f32_name_from_83(dir[e].name, fe.name); + fe.clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16); + fe.size = dir[e].fsize; + fe.attr = dir[e].attr; + fe.is_dir = (dir[e].attr & 0x10) ? 1 : 0; + if (emit(&fe, user_data) != 0) goto done; + } + u32 fo = fat_byte_off + clus * 4; + if (fo + 4 > sizeof(f32_disk)) break; + clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } +done: + return 0; +} + +/* 按首簇号 + 大小读取文件内容到 out_buf。 + * out_buf 至少 size 字节容量。返回 0 成功,负值失败。 */ +static inline int f32_read_file_by_clus(u32 clus, u32 size, u8 *out_buf, u32 buf_cap) { + if (f32_disk_load() != 0) return -1; + if (size > buf_cap) size = buf_cap; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf; + u32 fat_byte_off = bpb->rsvd * 512; + u32 spc = bpb->spc; + u32 remaining = size; + u8 *dst = out_buf; + u32 fc = clus; + while (fc >= 2 && fc < 0x0FFFFFF8 && remaining > 0) { + u32 fc_lba = data_lba + (fc - 2) * spc; + u32 fc_bytes = spc * 512; + if (fc_bytes > remaining) fc_bytes = remaining; + const u8 *fb; + if ((u64)fc_lba * 512 + (u64)spc * 512 <= 256ULL * 512) + fb = f32_disk + (u64)fc_lba * 512; + else { + if (f32_read_sectors(fc_lba, 8, f32_cluster) != 0) return -7; + fb = f32_cluster; + } + for (u32 b = 0; b < fc_bytes; b++) dst[b] = fb[b]; + dst += fc_bytes; remaining -= fc_bytes; + u32 fo = fat_byte_off + fc * 4; + if (fo + 4 > sizeof(f32_disk)) break; + fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } + return 0; +} + +#endif /* DESHAB_FAT32_IO_H */ diff --git a/CODE/tools/fileman/main.c b/CODE/tools/fileman/main.c index 41b69be..ca8ff4e 100644 --- a/CODE/tools/fileman/main.c +++ b/CODE/tools/fileman/main.c @@ -1,80 +1,107 @@ /* Deshab File Manager — 全屏文件管理器 - * 文件列表视图,鼠标点击选择,键盘导航。 - * FAT32 根目录遍历,Esc 退出。 + * 接入 fat32_io.h 共享头,支持: + * - 子目录遍历(路径栈,Enter 进入 / Backspace 返回) + * - FAT 链跟随(多簇文件/目录) + * - 文件查看(Enter 打开文本文件,只读浏览,Esc 返回列表) + * - 鼠标点击选择 + 键盘导航 + * Esc 退出返回 DSK/desktop。 */ #include "../../firstInit/ascii_bitmaps.c" #include "../desktop_app.h" +#include "../fat32_io.h" #define CHAR_STEP 12 #define CHAR_H 18 #define MAX_FILES 256 +#define MAX_DEPTH 16 +#define VIEW_BUF 24576 /* 文件查看器缓冲 24KB */ static da_app_context g_ac; static da_cursor g_cursor; static da_mouse g_mouse; typedef struct { - char name[13]; /* 8.3 */ - int is_dir; + char name[13]; + u32 clus; u32 size; -} file_entry; + u8 is_dir; +} fm_entry; -static file_entry files[MAX_FILES]; +static fm_entry files[MAX_FILES]; static int file_count = 0; static int selected = 0; static int scroll_off = 0; -static void load_root_dir(void) { - file_count = 0; - if (!g_ac.block_read) return; +/* 路径栈:每层记录目录首簇(0=根)与显示名 */ +static u32 path_clus[MAX_DEPTH]; +static char path_name[MAX_DEPTH][13]; +static int path_depth = 0; /* 0 = 根目录 */ - u8 bpb[512]; - if (g_ac.block_read(0, 0, 1, bpb) != 0) return; - if (bpb[510] != 0x55 || bpb[511] != 0xAA) return; +/* 文件查看器状态 */ +static int view_mode = 0; +static u8 view_buf[VIEW_BUF]; +static u32 view_size = 0; +static int view_scroll = 0; - u32 root_cluster = *(u32 *)(bpb + 44); - u8 sectors_per_cluster = bpb[13]; - u32 fat_start = *(u16 *)(bpb + 14); - u32 data_start = fat_start + (*(u16 *)(bpb + 22)) * 2; - - u8 dir_buf[8192]; - u32 lba = data_start + (root_cluster - 2) * sectors_per_cluster; - if (g_ac.block_read(0, lba, sectors_per_cluster, dir_buf) != 0) return; - - for (int i = 0; i < (int)(sectors_per_cluster * 512) && file_count < MAX_FILES; i += 32) { - u8 first = dir_buf[i]; - if (first == 0x00) break; - if (first == 0xE5) continue; - if (dir_buf[i+11] & 0x08) continue; - if (dir_buf[i+11] & 0x10) continue; /* 暂跳过目录 */ - - file_entry *f = &files[file_count]; - f->is_dir = (dir_buf[i+11] & 0x10) ? 1 : 0; - f->size = *(u32 *)(dir_buf + i + 28); - int p = 0; - for (int j = 0; j < 8 && dir_buf[i+j] != ' '; j++) - f->name[p++] = (char)dir_buf[i+j]; - if (dir_buf[i+8] != ' ') { - f->name[p++] = '.'; - for (int j = 8; j < 11 && dir_buf[i+j] != ' '; j++) - f->name[p++] = (char)dir_buf[i+j]; - } - f->name[p] = 0; - file_count++; - } +/* ---- 目录列举回调 ---- */ +static int fm_collect_cb(const f32_entry *e, void *user_data) { + (void)user_data; + if (file_count >= MAX_FILES) return 1; + fm_entry *f = &files[file_count]; + for (int i = 0; i < 13; i++) f->name[i] = e->name[i]; + f->clus = e->clus; + f->size = e->size; + f->is_dir = e->is_dir; + file_count++; + return 0; } -static void redraw_all(void) { +static void load_current_dir(void) { + file_count = 0; + selected = 0; + scroll_off = 0; + u32 dir_clus = (path_depth == 0) ? 0 : path_clus[path_depth - 1]; + f32_list_dir(dir_clus, fm_collect_cb, 0); +} + +/* ---- 路径显示 ---- */ +static void build_path_str(char *out, int cap) { + int p = 0; + out[p++] = '/'; + for (int i = 0; i < path_depth && p < cap - 13; i++) { + const char *n = path_name[i]; + while (*n && p < cap - 2) out[p++] = *n++; + out[p++] = '/'; + } + out[p] = 0; +} + +/* ---- 数字转字符串 ---- */ +static void fm_u32_dec(char *buf, u32 v) { + char tmp[12]; int n = 0; + if (!v) { buf[0] = '0'; buf[1] = 0; return; } + while (v && n < 11) { tmp[n++] = '0' + (v % 10); v /= 10; } + for (int i = 0; i < n; i++) buf[i] = tmp[n - 1 - i]; + buf[n] = 0; +} + +/* ---- 列表视图绘制 ---- */ +static void redraw_list(void) { da_fill_bg(&g_ac, DA_BG_PRIMARY); da_draw_titlebar(&g_ac, "Files", (i64)g_ac.fb_w); + /* 当前路径 */ + char path_str[200]; + build_path_str(path_str, sizeof(path_str)); + da_draw_string(&g_ac, path_str, 12, DA_TITLEBAR_H + 4, DA_ACCENT, DA_BG_PRIMARY, CHAR_STEP); + int margin = 12; - int top_y = DA_TITLEBAR_H + 4; + int top_y = DA_TITLEBAR_H + 4 + CHAR_H + 4; int row_h = CHAR_H + 6; int visible_rows = ((int)g_ac.fb_h - top_y - DA_STATUSBAR_H - 8) / row_h; int col_name_x = margin + 32; - int col_size_x = (int)g_ac.fb_w - 120; + int col_size_x = (int)g_ac.fb_w - 160; int col_type_x = (int)g_ac.fb_w - 60; /* 列标题 */ @@ -83,9 +110,10 @@ static void redraw_all(void) { da_draw_string(&g_ac, "Type", col_type_x, top_y, DA_TEXT_DIM, DA_BG_PRIMARY, CHAR_STEP); da_fill_rect(&g_ac, margin, top_y + CHAR_H, (i64)g_ac.fb_w - margin * 2, 1, DA_BORDER); - /* 文件列表 */ + /* 滚动范围调整 */ if (scroll_off < 0) scroll_off = 0; - if (scroll_off > file_count - visible_rows && file_count > visible_rows) scroll_off = file_count - visible_rows; + if (file_count > visible_rows && scroll_off > file_count - visible_rows) + scroll_off = file_count - visible_rows; if (scroll_off < 0) scroll_off = 0; for (int i = 0; i < visible_rows; i++) { @@ -93,28 +121,23 @@ static void redraw_all(void) { if (idx >= file_count) break; int y = top_y + CHAR_H + 6 + i * row_h; - /* 选中高亮 */ if (idx == selected) { da_fill_rect(&g_ac, margin, y - 2, (i64)g_ac.fb_w - margin * 2, row_h, DA_BG_TERTIARY); da_rect_outline(&g_ac, margin, y - 2, (i64)g_ac.fb_w - margin * 2, row_h, DA_ACCENT, 4); } - /* 图标 */ u32 icon_color = files[idx].is_dir ? DA_WARNING : DA_ACCENT; da_fill_rounded_rect(&g_ac, margin + 4, y, 24, 16, icon_color, 3); - /* 文件名 */ u32 name_color = (idx == selected) ? DA_ACCENT_LIGHT : DA_TEXT_PRIMARY; da_draw_string(&g_ac, files[idx].name, col_name_x, y, name_color, (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); - /* 大小 */ if (!files[idx].is_dir) { - char sz[16]; int p = 0; - u32 s = files[idx].size; - if (s == 0) { sz[p++] = '0'; } - else { char tmp[12]; int tl = 0; while(s>0){tmp[tl++]='0'+(s%10);s/=10;} for(int j=tl-1;j>=0;j--)sz[p++]=tmp[j]; } - sz[p++] = ' '; sz[p++] = 'B'; sz[p] = 0; + char sz[16]; + fm_u32_dec(sz, files[idx].size); + int sl = 0; while (sz[sl]) sl++; + sz[sl++] = ' '; sz[sl++] = 'B'; sz[sl] = 0; da_draw_string(&g_ac, sz, col_size_x, y, DA_TEXT_DIM, (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); } else { @@ -122,7 +145,6 @@ static void redraw_all(void) { (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); } - /* 类型 */ const char *type = files[idx].is_dir ? "DIR" : "FILE"; da_draw_string(&g_ac, type, col_type_x, y, DA_TEXT_DIM, (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); @@ -134,18 +156,129 @@ static void redraw_all(void) { } /* 状态栏 */ - char status[64]; int p = 0; - const char *p1 = "/ "; while (*p1) status[p++] = *p1++; - status[p++] = '0' + file_count / 10; status[p++] = '0' + file_count % 10; - const char *p2 = " items"; while (*p2) status[p++] = *p2++; + char status[80]; int p = 0; + char nc[12]; fm_u32_dec(nc, (u32)file_count); + const char *s1 = "items: "; while (*s1) status[p++] = *s1++; + for (int i = 0; nc[i]; i++) status[p++] = nc[i]; + const char *s2 = " | Enter:open Backspace:up Esc:exit"; + while (*s2) status[p++] = *s2++; status[p] = 0; da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); - /* 鼠标光标 */ da_cursor_save(&g_ac, &g_cursor); da_cursor_draw(&g_ac, &g_cursor, DA_CURSOR_COLOR); } +/* ---- 文件查看器绘制 ---- */ +static void redraw_view(void) { + da_fill_bg(&g_ac, DA_BG_PRIMARY); + + /* 标题栏显示文件名 */ + char title[32]; int p = 0; + const char *t = "View: "; + while (*t && p < 30) title[p++] = *t++; + if (selected >= 0 && selected < file_count) { + const char *fn = files[selected].name; + while (*fn && p < 30) title[p++] = *fn++; + } + title[p] = 0; + da_draw_titlebar(&g_ac, title, (i64)g_ac.fb_w); + + int margin = 12; + int top_y = DA_TITLEBAR_H + 4; + int row_h = CHAR_H + 2; + int visible_rows = ((int)g_ac.fb_h - top_y - DA_STATUSBAR_H - 8) / row_h; + + /* 按行显示文本内容 */ + int y = top_y; + u32 pos = 0; + int line = 0; + /* 跳过 view_scroll 行 */ + int skip = view_scroll; + while (pos < view_size && skip > 0) { + if (view_buf[pos] == '\n') { skip--; } + pos++; + } + while (pos < view_size && line < visible_rows) { + /* 提取一行 */ + int line_start = pos; + while (pos < view_size && view_buf[pos] != '\n' && pos - line_start < 80) pos++; + int line_len = pos - line_start; + /* 绘制字符 */ + for (int i = 0; i < line_len && i < 80; i++) { + char c = (char)view_buf[line_start + i]; + if (c == '\r') continue; + if (c < ' ' || c > '~') c = '.'; + char buf[2]; buf[0] = c; buf[1] = 0; + da_draw_string(&g_ac, buf, margin + i * CHAR_STEP, y, DA_TEXT_PRIMARY, DA_BG_PRIMARY, CHAR_STEP); + } + y += row_h; + line++; + if (pos < view_size && view_buf[pos] == '\n') pos++; + /* 处理 \r\n */ + if (pos < view_size && view_buf[pos] == '\r') pos++; + } + + /* 状态栏 */ + char status[80]; p = 0; + char sz[12]; fm_u32_dec(sz, view_size); + const char *s1 = "size: "; while (*s1) status[p++] = *s1++; + for (int i = 0; sz[i]; i++) status[p++] = sz[i]; + const char *s2 = "B | Up/Down:scroll Esc:back"; + while (*s2) status[p++] = *s2++; + status[p] = 0; + da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); + + da_cursor_save(&g_ac, &g_cursor); + da_cursor_draw(&g_ac, &g_cursor, DA_CURSOR_COLOR); +} + +static void redraw_all(void) { + if (view_mode) redraw_view(); + else redraw_list(); +} + +/* ---- 打开文件/目录 ---- */ +static void open_selected(void) { + if (selected < 0 || selected >= file_count) return; + fm_entry *f = &files[selected]; + + if (f->is_dir) { + /* 进入子目录 */ + if (path_depth < MAX_DEPTH - 1) { + path_clus[path_depth] = f->clus; + for (int i = 0; i < 13; i++) path_name[path_depth][i] = f->name[i]; + path_name[path_depth][12] = 0; + path_depth++; + load_current_dir(); + } + return; + } + + /* 文件:读取内容进查看器 */ + if (f->size == 0) { + view_size = 0; + } else { + u32 rd = f->size; + if (rd > VIEW_BUF) rd = VIEW_BUF; + if (f32_read_file_by_clus(f->clus, f->size, view_buf, VIEW_BUF) != 0) { + da_slog("fileman", "read file failed"); + return; + } + view_size = rd; + } + view_mode = 1; + view_scroll = 0; +} + +/* ---- 返回上级目录 ---- */ +static void go_up(void) { + if (path_depth > 0) { + path_depth--; + load_current_dir(); + } +} + __attribute__((visibility("default"))) void dsk_entry(const da_boot_context *ctx) { __asm__ volatile("cli"); @@ -160,7 +293,11 @@ void dsk_entry(const da_boot_context *ctx) { da_cursor_init(&g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h); da_mouse_init(); - load_root_dir(); + /* 初始化 fat32_io:注入 block_read/block_write */ + f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write); + + path_depth = 0; + load_current_dir(); redraw_all(); int e0 = 0, shift = 0; (void)shift; @@ -170,10 +307,8 @@ void dsk_entry(const da_boot_context *ctx) { /* 鼠标轮询 */ if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h)) { need_redraw = 1; - if (g_cursor.btn) { - /* 点击选择文件 */ - int margin = 12; - int top_y = DA_TITLEBAR_H + 4; + if (g_cursor.btn && !view_mode) { + int top_y = DA_TITLEBAR_H + 4 + CHAR_H + 4; int row_h = CHAR_H + 6; int click_y = g_cursor.my - top_y - CHAR_H - 6; if (click_y >= 0) { @@ -196,11 +331,36 @@ void dsk_entry(const da_boot_context *ctx) { if (sc == 0xAA || sc == 0xB6) { shift = 0; goto skip; } if (sc & 0x80) { e0 = 0; goto skip; } - if (sc == 0x01) { da_slog("fileman", "exit"); return; } + if (sc == 0x01) { /* Esc */ + if (view_mode) { + view_mode = 0; + need_redraw = 1; + } else { + da_slog("fileman", "exit"); + return; + } + goto skip; + } + if (view_mode) { + /* 查看器模式:Up/Down 滚动 */ + if (e0) { + if (sc == 0x48) { if (view_scroll > 0) view_scroll--; need_redraw = 1; } + else if (sc == 0x50) { view_scroll++; need_redraw = 1; } + e0 = 0; + goto skip; + } + /* PageUp/PageDown */ + if (sc == 0x49) { view_scroll -= 10; if (view_scroll < 0) view_scroll = 0; need_redraw = 1; } + else if (sc == 0x51) { view_scroll += 10; need_redraw = 1; } + goto skip; + } + + /* 列表模式 */ if (e0) { if (sc == 0x48) { /* Up */ if (selected > 0) selected--; + if (selected < scroll_off) scroll_off = selected; need_redraw = 1; } else if (sc == 0x50) { /* Down */ if (selected < file_count - 1) selected++; @@ -211,12 +371,17 @@ void dsk_entry(const da_boot_context *ctx) { } if (sc == 0x1C) { /* Enter - 打开 */ - if (selected >= 0 && selected < file_count) { - da_slog("fileman", "open file (stub)"); - } + open_selected(); need_redraw = 1; } else if (sc == 0x0E) { /* Backspace - 返回上级 */ - da_slog("fileman", "back (root only)"); + go_up(); + need_redraw = 1; + } else if (sc == 0x49) { /* PageUp */ + selected -= 10; if (selected < 0) selected = 0; + if (selected < scroll_off) scroll_off = selected; + need_redraw = 1; + } else if (sc == 0x51) { /* PageDown */ + selected += 10; if (selected >= file_count) selected = file_count - 1; need_redraw = 1; } } diff --git a/CODE/utsm-ipc/README.md b/CODE/utsm-ipc/README.md new file mode 100644 index 0000000..fe10e37 --- /dev/null +++ b/CODE/utsm-ipc/README.md @@ -0,0 +1,174 @@ +# UTSM ↔ Linux IPC 骨架 (Phase 1.3) + +## 概述 + +Phase 1.3 实现了 UTSM 监控核与 Linux 主导核之间的双向 IPC 通信骨架,包含三个通信通道中的两个: + +1. **共享内存环形缓冲区**(异步消息) — ✅ 已实现 +2. **VMCALL hypercall**(同步请求/响应) — ✅ 已实现 +3. **IPI 通知**(中断注入) — ⏳ Phase 1.4+ + +## 文件结构 + +### UTSM 侧(监控核) + +``` +CODE/UTSM/ +├── include/utsm/ +│ ├── ipc_shm.h — IPC 共享内存 API 声明 +│ └── hypercall.h — VMCALL 处理器 API 声明 +├── vmm/ +│ ├── ipc_shm.c — 共享内存分配、初始化、EPT 映射、ring buffer 操作 +│ ├── hypercall.c — VMCALL VM-Exit 处理(PING/HELLO/SHM_INFO/CONSOLE_WRITE) +│ ├── vmexit.c — VM-Exit 分发器(增加 VMCALL case) +│ ├── vmexit_asm.S — VM-Exit 汇编入口(重写:保存/恢复 guest GPR + 汇编 vmresume) +│ ├── vmm.c — VMM 核心(g_guest_regs 全局变量 + vmm_vmexit_entry 简化) +│ └── ept.c — EPT 页表(增加 ept_gpa_to_hpa() 翻译函数) +``` + +### 共享协议 + +``` +CODE/utsm-ipc/ +└── ipc_proto.h — 双内核共享的 IPC 协议头文件 +``` + +### Linux 侧(主导核) + +``` +CODE/linux/ +├── configs/ +│ └── utsm_x86_64_defconfig — 增加 CONFIG_UTSM_HCALL=y +├── patches/ +│ ├── utsm_hcall.c — Linux 内核驱动:VMCALL + /dev/utsm + SHM 映射 +│ ├── utsm_Kconfig — Kconfig 配置 +│ └── utsm_Makefile — 模块 Makefile +└── build.sh — 更新:自动安装驱动到 Linux 源码树 +``` + +## 通信协议 + +### 共享内存布局(GPA 0x04000000,1MB) + +``` +0x04000000 utsm_ipc_shm_header (64 bytes) + ├── magic = "UTSMHC\0\0" + ├── version = 1 + ├── utsm_ready (UTSM 设置) + └── linux_ready (Linux 设置) + +0x04000040 utsm_ipc_ring utsm_to_linux (UTSM 写,Linux 读) + └── 64 条消息,每条 256 字节 + +0x04001040 utsm_ipc_ring linux_to_utsm (Linux 写,UTSM 读) + └── 64 条消息,每条 256 字节 + +0x04002040 payload pool (~1MB,用于大数据传输) +``` + +### VMCALL Hypercall ABI + +``` +Linux guest 执行 VMCALL 指令: + RAX = UTSM_HCALL_MAGIC | op (0x5554534D48430000 | op) + RDI = arg0 + RSI = arg1 + RDX = arg2 + +UTSM 处理 VMCALL VM-Exit: + 1. 从 g_guest_regs 读取 RAX/RDI/RSI/RDX + 2. 验证 magic,提取 op + 3. 分发到对应处理器 + 4. 返回值写入 g_guest_regs.rax + 5. GUEST_RIP 前进过 VMCALL 指令 + 6. vmresume(GPR = 修改后的 guest 值) +``` + +### 已实现的 Hypercall 操作 + +| 操作码 | 名称 | 功能 | +|--------|------|------| +| 0x0001 | PING | 返回 "PONG" (0x504F4E47),用于探测 UTSM | +| 0x0002 | HELLO | 握手,UTSM 回复 HELLO_ACK 消息到 ring | +| 0x0010 | SHM_INFO | 返回共享内存 GPA + size | +| 0x0020 | CAP_VALIDATE | 能力校验(stub) | +| 0x0030 | UTRW_READ | 封缄内存读(stub) | +| 0x0031 | UTRW_WRITE | 封缄内存写(stub) | +| 0x0040 | DRR_CHECKPOINT | DRR checkpoint 触发(stub) | +| 0x0050 | CONSOLE_WRITE | 将 Linux 字符串输出到 UTSM 串口 | +| 0x0051 | CONSOLE_READ | 从 UTSM 串口读取(stub) | + +## 关键设计决策 + +### 1. VM-Exit 汇编重写 + +VMCS 不保存 guest GPR(RAX-R15)。原汇编只保存 host caller-saved 寄存器,无法让 C 处理器读写 guest GPR。 + +重写后: +- VM-Exit 时保存全部 16 个 guest GPR 到 `g_guest_regs` 结构 +- 调用 C 分发器(不再从 C 调用 vmresume) +- 恢复全部 16 个 guest GPR(C 可能已修改 rax = 返回值) +- 汇编中执行 vmresume(GPR = 修改后的 guest 值) + +### 2. EPT GPA→HPA 翻译 + +Linux guest 使用 identity-mapped 页表(GVA=GPA),hypercall 传递的指针是 GPA。UTSM 需要: +- `ept_gpa_to_hpa(gpa)` — 4 级 EPT walk,支持 4KB/2MB/1GB 页 +- 翻译后加 HHDM 偏移得到 host 虚拟地址 + +### 3. GPA 布局修复 + +原 `LINUX_GUEST_INITRD_GPA = 0x04000000` 与 `UTSM_IPC_SHM_GPA = 0x04000000` 冲突。 + +修复后: +``` +0x00400000-0x004FFFFF 固定区域 (boot_params/cmdline/GDT/PGT/stack) +0x01000000-0x02FFFFFF Linux kernel +0x03000000-0x03FFFFFF initrd +0x04000000-0x040FFFFF IPC shared memory (1MB) ← e820 reserved +0x05000000-0x08FFFFFF general RAM (64MB) +``` + +## 验证方法 + +### 1. UTSM 侧编译验证(已完成) + +```powershell +cd d:\Code\Deshab\CODE\UTSM +mingw32-make +# → utsm.elf 生成,0 warnings from new code +``` + +### 2. Linux 内核构建(需 Linux 环境) + +```bash +cd CODE/linux +./build.sh +# → SYSTEM/boot/linux-bzImage +# → SYSTEM/boot/linux-initrd.img +``` + +### 3. QEMU 端到端测试(需 VMX 支持) + +1. 取消 `SYSTEM/limine/limine.conf` 中 Linux 模块的注释 +2. 运行 `ISO/run_qemu.bat`(需 KVM 或硬件 VMX) +3. 预期串口输出: + - `[UTSM] IPC shm init ok` + - `[LINUX] loader init ok` + - `[LINUX] vmlaunch` + - Linux 启动日志 + - `[utsm] UTSM monitor detected (PONG=0x504F4E47)` + - `[utsm] /dev/utsm registered (IPC ready)` + - `[HCALL] HELLO from Linux guest` +4. Linux 用户态测试: + ```bash + echo "hello UTSM" > /dev/utsm # → UTSM 串口显示 [LINUX] hello UTSM + cat /dev/utsm # → 读取 UTSM 发来的消息 + ``` + +## 后续阶段 + +- **Phase 1.4**: virtio-console 双向通信(`/dev/hvc0` ↔ UTSM 串口) +- **Phase 1.5**: virtio-fs 文件共享(Linux 挂载 `/utsm`) +- IPI 中断注入(替代当前的轮询方式) +- UTRW/CAP/DRR hypercall 完整实现 diff --git a/CODE/utsm-ipc/ipc_proto.h b/CODE/utsm-ipc/ipc_proto.h new file mode 100644 index 0000000..5f1d1bd --- /dev/null +++ b/CODE/utsm-ipc/ipc_proto.h @@ -0,0 +1,231 @@ +#ifndef UTSM_IPC_PROTO_H +#define UTSM_IPC_PROTO_H + +/* + * UTSM ↔ Linux IPC Protocol — shared between both kernels. + * + * This header is included by: + * - UTSM kernel (CODE/UTSM/vmm/ipc_shm.c, hypercall.c) + * - Linux kernel module (CODE/linux/patches/utsm-ipc.patch → drivers/utsm/) + * + * Communication channels: + * 1. Shared memory ring buffer (async messages, bidirectional) + * 2. VMCALL hypercall (sync request/response) + * 3. IPI notification (UTSM → Linux via guest IRQ injection) + * + * Shared memory layout (at GPA 0x04000000, 1MB total): + * 0x04000000: utsm_ipc_shm_header (64 bytes) + * 0x04000040: utsm_ipc_ring utsm_to_linux (UTSM writes, Linux reads) + * 0x04001040: utsm_ipc_ring linux_to_utsm (Linux writes, UTSM reads) + * 0x04002040: payload pool (for large data transfers) + */ + +#include + +/* ===== Constants ===== */ + +#define UTSM_IPC_MAGIC 0x5554534D48430000ULL /* "UTSMHC\00\00" */ +#define UTSM_IPC_VERSION 1 + +#define UTSM_IPC_SHM_GPA 0x04000000ULL +#define UTSM_IPC_SHM_SIZE (1024 * 1024) /* 1MB */ + +#define UTSM_IPC_QUEUE_DEPTH 64 /* messages per ring */ +#define UTSM_IPC_MSG_DATA_SIZE 240 /* inline data per message */ +#define UTSM_IPC_PAYLOAD_SIZE (UTSM_IPC_SHM_SIZE - 2 * 1024 - 64) /* ~1MB */ + +/* ===== Hypercall operations ===== */ + +enum utsm_hcall_op { + UTSM_HCALL_PING = 0x0001, /* Simple ping (returns PONG) */ + UTSM_HCALL_HELLO = 0x0002, /* Hello handshake */ + UTSM_HCALL_SHM_INFO = 0x0010, /* Query shared memory GPA + size */ + UTSM_HCALL_CAP_VALIDATE = 0x0020, /* Capability validation */ + UTSM_HCALL_UTRW_READ = 0x0030, /* Sealed memory read (UTRW) */ + UTSM_HCALL_UTRW_WRITE = 0x0031, /* Sealed memory write (UTRW) */ + UTSM_HCALL_DRR_CHECKPOINT= 0x0040, /* Trigger DRR checkpoint */ + UTSM_HCALL_CONSOLE_WRITE = 0x0050, /* Write to UTSM serial console */ + UTSM_HCALL_CONSOLE_READ = 0x0051, /* Read from UTSM serial console */ +}; + +/* Hypercall return codes */ +#define UTSM_HCALL_OK 0 +#define UTSM_HCALL_INVAL (-1) +#define UTSM_HCALL_PERM (-2) +#define UTSM_HCALL_NOMEM (-3) +#define UTSM_HCALL_NOSYS (-4) + +/* ===== Message types ===== */ + +enum utsm_ipc_msg_type { + UTSM_MSG_NONE = 0, + UTSM_MSG_HELLO = 1, /* Initial handshake */ + UTSM_MSG_HELLO_ACK= 2, /* Handshake acknowledgment */ + UTSM_MSG_PING = 3, /* Ping request */ + UTSM_MSG_PONG = 4, /* Pong response */ + UTSM_MSG_DATA = 5, /* Generic data message */ + UTSM_MSG_ACK = 6, /* Acknowledgment */ + UTSM_MSG_NACK = 7, /* Negative acknowledgment */ + UTSM_MSG_SHUTDOWN = 8, /* Shutdown notification */ +}; + +/* ===== Ring buffer structures ===== */ + +/* Single IPC message (256 bytes, cache-line aligned) */ +struct utsm_ipc_msg { + uint32_t type; /* enum utsm_ipc_msg_type */ + uint32_t seq; /* Sequence number (monotonic) */ + uint32_t data_len; /* Actual bytes in data[] */ + uint32_t flags; /* Reserved for future use */ + uint8_t data[UTSM_IPC_MSG_DATA_SIZE]; /* Inline payload */ +} __attribute__((packed)); + +/* Ring buffer: single-producer, single-consumer queue. + * + * Producer writes at tail, then increments tail (with memory barrier). + * Consumer reads at head, then increments head (with memory barrier). + * + * Empty when head == tail. + * Full when (tail + 1) % capacity == head. */ +struct utsm_ipc_ring { + volatile uint32_t head; /* Consumer updates (Linux or UTSM) */ + volatile uint32_t tail; /* Producer updates (UTSM or Linux) */ + uint32_t capacity; /* Max messages (UTSM_IPC_QUEUE_DEPTH) */ + uint32_t reserved; + struct utsm_ipc_msg messages[UTSM_IPC_QUEUE_DEPTH]; +} __attribute__((packed)); + +/* Shared memory header */ +struct utsm_ipc_shm_header { + uint64_t magic; /* UTSM_IPC_MAGIC */ + uint32_t version; /* UTSM_IPC_VERSION */ + uint32_t header_size; /* sizeof(struct utsm_ipc_shm_header) */ + uint64_t shm_phys; /* Physical address of SHM region */ + uint64_t shm_size; /* Total SHM size */ + volatile uint32_t utsm_ready; /* UTSM sets to 1 when initialized */ + volatile uint32_t linux_ready; /* Linux sets to 1 when initialized */ + uint8_t reserved[24]; +} __attribute__((packed)); + +/* Full shared memory region */ +struct utsm_ipc_shm { + struct utsm_ipc_shm_header header; + struct utsm_ipc_ring utsm_to_linux; /* UTSM writes, Linux reads */ + struct utsm_ipc_ring linux_to_utsm; /* Linux writes, UTSM reads */ + uint8_t payload_pool[UTSM_IPC_PAYLOAD_SIZE]; +} __attribute__((packed)); + +/* ===== Ring buffer operations (inline, usable by both kernels) ===== */ + +/* Memory barriers — adapted for each kernel's environment. + * UTSM: use compiler barriers (__asm__ volatile("" ::: "memory")) + * Linux: use smp_store_release / smp_load_acquire + * + * For simplicity, we use full memory barriers here. */ + +static inline void utsm_ipc_mb(void) { + __asm__ volatile("mfence" ::: "memory"); +} + +/* Initialize a ring buffer */ +static inline void utsm_ipc_ring_init(struct utsm_ipc_ring *ring) { + ring->head = 0; + ring->tail = 0; + ring->capacity = UTSM_IPC_QUEUE_DEPTH; + ring->reserved = 0; + utsm_ipc_mb(); +} + +/* Try to enqueue a message. Returns 0 on success, -1 if full. */ +static inline int utsm_ipc_ring_push(struct utsm_ipc_ring *ring, + const struct utsm_ipc_msg *msg) { + uint32_t tail = ring->tail; + uint32_t next_tail = (tail + 1) % ring->capacity; + + if (next_tail == ring->head) { + return -1; /* Full */ + } + + ring->messages[tail] = *msg; + utsm_ipc_mb(); /* Ensure message is visible before updating tail */ + ring->tail = next_tail; + + return 0; +} + +/* Try to dequeue a message. Returns 0 on success, -1 if empty. */ +static inline int utsm_ipc_ring_pop(struct utsm_ipc_ring *ring, + struct utsm_ipc_msg *msg) { + uint32_t head = ring->head; + + if (head == ring->tail) { + return -1; /* Empty */ + } + + *msg = ring->messages[head]; + utsm_ipc_mb(); /* Ensure we've read the message before updating head */ + ring->head = (head + 1) % ring->capacity; + + return 0; +} + +/* Check if ring is empty */ +static inline int utsm_ipc_ring_empty(const struct utsm_ipc_ring *ring) { + return ring->head == ring->tail; +} + +/* Check if ring is full */ +static inline int utsm_ipc_ring_full(const struct utsm_ipc_ring *ring) { + uint32_t next_tail = (ring->tail + 1) % ring->capacity; + return next_tail == ring->head; +} + +/* ===== Hypercall ABI (Linux → UTSM) ===== + * + * Linux calls VMCALL with: + * RAX = UTSM_IPC_MAGIC | op + * RDI = arg0 + * RSI = arg1 + * RDX = arg2 + * + * UTSM handles the VMCALL VM-Exit and returns: + * RAX = return value (UTSM_HCALL_OK or error) + * + * The magic in RAX distinguishes UTSM hypercalls from other VMCALL users. + */ + +#define UTSM_HCALL_MAGIC 0x5554534D48430000ULL /* "UTSMHC\00\00" */ + +static inline long utsm_hcall(uint64_t op, uint64_t a0, uint64_t a1, uint64_t a2) { + long ret; + __asm__ volatile("vmcall" + : "=a"(ret) + : "a"(UTSM_HCALL_MAGIC | op), "D"(a0), "S"(a1), "d"(a2) + : "memory"); + return ret; +} + +/* Convenience hypercall wrappers */ +static inline long utsm_hcall_ping(void) { + return utsm_hcall(UTSM_HCALL_PING, 0, 0, 0); +} + +static inline long utsm_hcall_hello(void) { + return utsm_hcall(UTSM_HCALL_HELLO, 0, 0, 0); +} + +static inline long utsm_hcall_shm_info(uint64_t *gpa_out, uint64_t *size_out) { + uint64_t gpa = 0, size = 0; + long ret = utsm_hcall(UTSM_HCALL_SHM_INFO, (uint64_t)&gpa, (uint64_t)&size, 0); + if (ret == 0) { + if (gpa_out) *gpa_out = gpa; + if (size_out) *size_out = size; + } + return ret; +} + +static inline long utsm_hcall_console_write(const char *buf, uint64_t len) { + return utsm_hcall(UTSM_HCALL_CONSOLE_WRITE, (uint64_t)buf, len, 0); +} + +#endif /* UTSM_IPC_PROTO_H */ diff --git a/RE/Bug记录.txt b/RE/Bug记录.txt new file mode 100644 index 0000000..a0a97db --- /dev/null +++ b/RE/Bug记录.txt @@ -0,0 +1,6 @@ +开发者模式中启动的Shell的提示符没有在第一行也没有显示 +shell光标没有闪烁 +Login界面没有显示鼠标指针 +在桌面上时每按下按键屏幕就会闪烁 +窗口打开和关闭时的动画太卡,而且屏幕会闪,直接去掉动画 +启动一次之后,第二次开机启动会直接退出 \ No newline at end of file diff --git a/SYSTEM/boot/utsm.elf b/SYSTEM/boot/utsm.elf index fad5b5d..0c3c3d8 100644 Binary files a/SYSTEM/boot/utsm.elf and b/SYSTEM/boot/utsm.elf differ diff --git a/SYSTEM/limine/limine.conf b/SYSTEM/limine/limine.conf index 83457a7..35a874b 100644 --- a/SYSTEM/limine/limine.conf +++ b/SYSTEM/limine/limine.conf @@ -42,3 +42,10 @@ verbose: yes module_cmdline: dsk:main module_path: boot():/driver/test.fat32 module_cmdline: dkm:test_fat32 + + # Linux 6.6 LTS guest kernel (for UTSM+Linux dual-kernel architecture) + # Uncomment after building Linux via CODE/linux/build.sh + # module_path: boot():/boot/linux-bzImage + # module_cmdline: linux:bzImage + # module_path: boot():/boot/linux-initrd.img + # module_cmdline: linux:initrd diff --git a/SYSTEM/system/deshab64/deshab.elf b/SYSTEM/system/deshab64/deshab.elf index d20c7db..dab2acb 100644 Binary files a/SYSTEM/system/deshab64/deshab.elf and b/SYSTEM/system/deshab64/deshab.elf differ diff --git a/SYSTEM/system/deshab64/desktop.elf b/SYSTEM/system/deshab64/desktop.elf index 56d3a31..297d550 100644 Binary files a/SYSTEM/system/deshab64/desktop.elf and b/SYSTEM/system/deshab64/desktop.elf differ diff --git a/SYSTEM/system/deshab64/tools/browser.elf b/SYSTEM/system/deshab64/tools/browser.elf index ba1e3d5..489dce1 100644 Binary files a/SYSTEM/system/deshab64/tools/browser.elf and b/SYSTEM/system/deshab64/tools/browser.elf differ diff --git a/SYSTEM/system/deshab64/tools/editor.elf b/SYSTEM/system/deshab64/tools/editor.elf index 9cde1c8..c43a036 100644 Binary files a/SYSTEM/system/deshab64/tools/editor.elf and b/SYSTEM/system/deshab64/tools/editor.elf differ diff --git a/SYSTEM/system/deshab64/tools/fileman.elf b/SYSTEM/system/deshab64/tools/fileman.elf index 218dbab..dd7f399 100644 Binary files a/SYSTEM/system/deshab64/tools/fileman.elf and b/SYSTEM/system/deshab64/tools/fileman.elf differ diff --git a/SYSTEM/system/user/use/firstInit.txt b/SYSTEM/system/user/use/firstInit.txt index 879cdec..cf3045c 100644 --- a/SYSTEM/system/user/use/firstInit.txt +++ b/SYSTEM/system/user/use/firstInit.txt @@ -1,2 +1,2 @@ -0 +1 1 \ No newline at end of file diff --git a/SYSTEM/system/user/use/login.elf b/SYSTEM/system/user/use/login.elf index 92d708c..b95c3dc 100644 Binary files a/SYSTEM/system/user/use/login.elf and b/SYSTEM/system/user/use/login.elf differ