FUCK
This commit is contained in:
+4
-4
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef UTSM_EPT_H
|
||||
#define UTSM_EPT_H
|
||||
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* 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
|
||||
@@ -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 <utsm/types.h>
|
||||
|
||||
/* 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 */
|
||||
@@ -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 <utsm/types.h>
|
||||
#include <ipc_proto.h> /* 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 */
|
||||
@@ -0,0 +1,170 @@
|
||||
#ifndef UTSM_LINUX_LOADER_H
|
||||
#define UTSM_LINUX_LOADER_H
|
||||
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* ===== 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
|
||||
@@ -9,6 +9,10 @@
|
||||
#include <utsm/segment.h>
|
||||
#include <utsm/capability.h>
|
||||
#include <utsm/utrw.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/hypercall.h>
|
||||
|
||||
void utsm_init(void);
|
||||
int utsm_selftest_run(void);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef UTSM_VMM_H
|
||||
#define UTSM_VMM_H
|
||||
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* 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
|
||||
@@ -0,0 +1,268 @@
|
||||
#ifndef UTSM_VMX_H
|
||||
#define UTSM_VMX_H
|
||||
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* ===== 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
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <utsm/dsk.h>
|
||||
#include <utsm/block.h>
|
||||
#include <utsm/net.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,168 @@
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#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;
|
||||
}
|
||||
@@ -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 <utsm/hypercall.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <ipc_proto.h>
|
||||
#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;
|
||||
}
|
||||
@@ -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 <utsm/ipc_shm.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#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);
|
||||
}
|
||||
@@ -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 <utsm/linux_loader.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#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;
|
||||
}
|
||||
@@ -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 <utsm/linux_loader.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/hypercall.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* 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; }
|
||||
@@ -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)
|
||||
@@ -0,0 +1,523 @@
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#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 全局变量。 */
|
||||
@@ -0,0 +1,257 @@
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#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(); }
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+2269
-617
@@ -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 */
|
||||
}
|
||||
|
||||
+3
-1
@@ -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
|
||||
}
|
||||
|
||||
+2
-2
@@ -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; /* 镜像过大,防止越界 */
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
```
|
||||
@@ -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."
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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 <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
/* Shared IPC protocol header (included via build.sh -I flag) */
|
||||
#include <ipc_proto.h>
|
||||
|
||||
#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");
|
||||
@@ -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 $@
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
+299
-60
@@ -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();
|
||||
|
||||
+18
-10
@@ -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;
|
||||
|
||||
+123
-19
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
+237
-72
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 完整实现
|
||||
@@ -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 <stdint.h>
|
||||
|
||||
/* ===== 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 */
|
||||
@@ -0,0 +1,6 @@
|
||||
开发者模式中启动的Shell的提示符没有在第一行也没有显示
|
||||
shell光标没有闪烁
|
||||
Login界面没有显示鼠标指针
|
||||
在桌面上时每按下按键屏幕就会闪烁
|
||||
窗口打开和关闭时的动画太卡,而且屏幕会闪,直接去掉动画
|
||||
启动一次之后,第二次开机启动会直接退出
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
0
|
||||
1
|
||||
1
|
||||
Binary file not shown.
Reference in New Issue
Block a user