diff --git a/CODE/UTSM/arch/x86_64/serial.c b/CODE/UTSM/arch/x86_64/serial.c index f89b8f7..6517ec2 100644 --- a/CODE/UTSM/arch/x86_64/serial.c +++ b/CODE/UTSM/arch/x86_64/serial.c @@ -79,3 +79,18 @@ void serial_write(const char *s) { serial_putc(*s++); } } + +/* 非阻塞读取 host 串口一个字节(QEMU stdio → guest COM1 输入桥)。 + * 返回 0-255,无数据返回 -1。供 vmexit 在 guest resume 前轮询。 */ +int serial_try_read(void) { + if (inb(COM1 + 5) & 0x01) { + return inb(COM1); + } + return -1; +} + +/* 导出 PIT 校准的 TSC 频率(kHz 量级,单位 ticks/ms), + * 供 VMX preemption timer 与 guest PIT tick 注入使用。 */ +u64 serial_tsc_per_ms(void) { + return g_tsc_per_ms; +} diff --git a/CODE/UTSM/arch/x86_64/serial.o b/CODE/UTSM/arch/x86_64/serial.o index afeb06b..3ed8c8c 100644 Binary files a/CODE/UTSM/arch/x86_64/serial.o and b/CODE/UTSM/arch/x86_64/serial.o differ diff --git a/CODE/UTSM/include/utsm/deshab_ui.h b/CODE/UTSM/include/utsm/deshab_ui.h index 665ba10..7cfd256 100644 --- a/CODE/UTSM/include/utsm/deshab_ui.h +++ b/CODE/UTSM/include/utsm/deshab_ui.h @@ -173,8 +173,8 @@ typedef long long du_i64; #define DU_RADIUS_NONE 0u #define DU_RADIUS_SM 4u /* 小按钮、标签 */ -#define DU_RADIUS_MD 8u /* 卡片、输入框 */ -#define DU_RADIUS_LG 12u /* 大卡片、面板 */ +#define DU_RADIUS_MD 0u /* 卡片、输入框(性能优化:改为直角) */ +#define DU_RADIUS_LG 0u /* 大卡片、面板(性能优化:改为直角) */ #define DU_RADIUS_XL 16u /* 模态框 */ #define DU_RADIUS_FULL 9999u /* 完全圆角(药丸形) */ @@ -722,9 +722,9 @@ static inline du_u32 du_theme_accent(du_theme t) { /* --- 强调色:霓虹青 (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_ACCENT_DIM_SOLID 0xFF2A4A5Au /* 不透明近似(提升亮度改善对比度) */ #define DP_KATE_PANEL_BORDER 0x5900E5FFu /* --panel-border: rgba(0,229,255,0.35) */ -#define DP_KATE_PANEL_BORDER_SOLID 0xFF005A6Au /* 不透明近似 */ +#define DP_KATE_PANEL_BORDER_SOLID 0xFF2A6070u /* 不透明近似(提升亮度) */ /* --- 辅助色:霓虹绿 (Neon Green) --- */ #define DP_KATE_ACCENT2 0xFF39FF14u /* --accent2: #39ff14 */ @@ -781,28 +781,12 @@ static inline du_u32 du_theme_accent(du_theme t) { * 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; - } - } + /* 性能优化:禁用扫描线纹理,减少约50%的像素处理开销 */ + (void)ctx; (void)x; (void)y; (void)w; (void)h; } /* 绘制带光晕的矩形边框(box-shadow 效果) diff --git a/CODE/UTSM/include/utsm/linux_loader.h b/CODE/UTSM/include/utsm/linux_loader.h index 6496f00..ef41995 100644 --- a/CODE/UTSM/include/utsm/linux_loader.h +++ b/CODE/UTSM/include/utsm/linux_loader.h @@ -121,6 +121,25 @@ struct linux_setup_header { /* General RAM starts after IPC region */ #define LINUX_GUEST_RAM_GPA 0x05000000ULL /* 80 MB */ +/* ===== virtio-mmio device GPA layout ===== + * virtio-mmio 设备放在 GPA 高位 MMIO 区(guest RAM 之上,不占用 e820 RAM)。 + * guest 通过 cmdline 参数 virtio_mmio.device= 发现这些设备。 + * 每个设备占 0x200 字节寄存器空间(virtio-mmio 规范要求 4KB 对齐)。 + * + * 这些 GPA 在 EPT 中【故意不映射】,访问时触发 EPT violation, + * 由 vmexit handler 路由到 virtio_mmio 后端模拟寄存器读写。 + * + * GPA 选择 0xF4000000 起始(接近 4GB,远离 guest RAM 0x05000000-0x09000000)。 */ +#define VIRTIO_MMIO_GPA_BASE 0xF4000000ULL +#define VIRTIO_MMIO_GPA_STRIDE 0x1000ULL /* 每个设备 4KB */ + +#define VIRTIO_MMIO_BLK_GPA (VIRTIO_MMIO_GPA_BASE + 0 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4000000 */ +#define VIRTIO_MMIO_NET_GPA (VIRTIO_MMIO_GPA_BASE + 1 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4001000 */ + +/* 判断 GPA 是否落在 virtio-mmio 设备区(含两端) */ +#define VIRTIO_MMIO_GPA_END (VIRTIO_MMIO_GPA_BASE + 2 * VIRTIO_MMIO_GPA_STRIDE) +#define IS_VIRTIO_MMIO_GPA(gpa) ((gpa) >= VIRTIO_MMIO_GPA_BASE && (gpa) < VIRTIO_MMIO_GPA_END) + /* ===== Linux loader API ===== */ /* Loaded Linux guest state */ @@ -165,9 +184,16 @@ int linux_launch(void); /* Default Linux command line (can be overridden). * nohlt: prevent kernel idle loop from using HLT — only the exec daemon's * explicit ioctl(PARK) HLT should trigger VM-Exit park, so that - * linux_launch() returns only after the daemon is ready. */ + * linux_launch() returns only after the daemon is ready. + * noapic/nolapic/nosmp: UTSM VMM 只模拟 legacy PIC(8259) + PIT(8254), + * 不提供 LAPIC/IOAPIC;guest 走 XT-PIC 模式,IRQ vector = 0x30+irq。 + * virtio_mmio.device=: 向 guest 声明 virtio-mmio 设备(blk@0xF4000000 IRQ5, + * net@0xF4001000 IRQ6),需 CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y。 */ #define LINUX_DEFAULT_CMDLINE \ "console=ttyS0,115200 earlyprintk=serial nokaslr " \ - "no_timer_check loglevel=7 nohlt idle=poll" + "no_timer_check loglevel=7 nohlt idle=poll " \ + "noapic nolapic nosmp " \ + "virtio_mmio.device=4K@0xF4000000:5 " \ + "virtio_mmio.device=4K@0xF4001000:6" #endif diff --git a/CODE/UTSM/include/utsm/virtio_mmio.h b/CODE/UTSM/include/utsm/virtio_mmio.h new file mode 100644 index 0000000..163506a --- /dev/null +++ b/CODE/UTSM/include/utsm/virtio_mmio.h @@ -0,0 +1,156 @@ +#ifndef UTSM_VIRTIO_MMIO_H +#define UTSM_VIRTIO_MMIO_H + +/* virtio_mmio.h — UTSM virtio-mmio 设备模拟后端框架。 + * + * 为 Linux guest 提供 virtio-mmio 设备(block + net)。 + * 设备 GPA 位于 VIRTIO_MMIO_GPA_BASE 起的高位 MMIO 区,EPT 故意不映射, + * guest 访问时触发 EPT violation,vmexit handler 调用本模块的 + * virtio_mmio_read/write 模拟寄存器行为。 + * + * 参考 Virtio Spec 1.1 §4.2 (virtio-mmio)。 + * guest 通过 cmdline virtio_mmio.device= 参数发现设备(无需 PCI/ACPI)。 + */ + +#include + +/* virtio-mmio 寄存器偏移(Virtio Spec 1.1 §4.2.2.1,均为 32 位寄存器) */ +#define VIRTIO_MMIO_MAGIC_VALUE 0x000 /* R "virt" = 0x74726976 */ +#define VIRTIO_MMIO_VERSION 0x004 /* R 1=legacy, 2=modern */ +#define VIRTIO_MMIO_DEVICE_ID 0x008 /* R 1=net, 2=blk, 3=console... */ +#define VIRTIO_MMIO_VENDOR_ID 0x00C /* R */ +#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 /* R features[31:0] (sel) */ +#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 /* W */ +#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 /* W features[31:0] (sel) */ +#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 /* W */ +#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 /* W legacy only */ +#define VIRTIO_MMIO_QUEUE_SEL 0x030 /* W */ +#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 /* R */ +#define VIRTIO_MMIO_QUEUE_NUM 0x038 /* W */ +#define VIRTIO_MMIO_QUEUE_ALIGN 0x03C /* W legacy only */ +#define VIRTIO_MMIO_QUEUE_PFN 0x040 /* RW legacy only */ +#define VIRTIO_MMIO_QUEUE_READY 0x044 /* RW modern */ +#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 /* W */ +#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 /* R */ +#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 /* W */ +#define VIRTIO_MMIO_STATUS 0x070 /* RW */ +#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 /* W modern */ +#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 /* W modern */ +#define VIRTIO_MMIO_QUEUE_DRIVER_LOW 0x090 /* W modern */ +#define VIRTIO_MMIO_QUEUE_DRIVER_HIGH 0x094 /* W modern */ +#define VIRTIO_MMIO_QUEUE_DEVICE_LOW 0x0A0 /* W modern */ +#define VIRTIO_MMIO_QUEUE_DEVICE_HIGH 0x0A4 /* W modern */ +#define VIRTIO_MMIO_CONFIG_GENERATION 0x0FC /* R modern */ +#define VIRTIO_MMIO_CONFIG 0x100 /* RW device-specific config */ + +/* device IDs */ +#define VIRTIO_ID_NET 1 +#define VIRTIO_ID_BLOCK 2 +#define VIRTIO_ID_CONSOLE 3 + +/* device status bits */ +#define VIRTIO_STATUS_ACKNOWLEDGE 1 +#define VIRTIO_STATUS_DRIVER 2 +#define VIRTIO_STATUS_DRIVER_OK 4 +#define VIRTIO_STATUS_FEATURES_OK 8 +#define VIRTIO_STATUS_FAILED 128 + +/* 通用 feature bits */ +#define VIRTIO_F_VERSION_1 32 /* modern 设备必须提供(bit32=高32位word bit0) */ + +/* ===== 后端框架 API ===== */ + +/* 初始化 virtio-mmio 后端框架(注册 block + net 设备)。 + * 在 linux_loader_init 之后、linux_launch 之前调用。 */ +void virtio_mmio_init(void); + +/* 处理一次 virtio-mmio 寄存器读/写(由 vmexit EPT violation 调用)。 + * gpa : 触发访问的 GPA(落在某设备 4KB 寄存器区) + * width : 访问宽度(字节),通常为 4 + * value : 写操作的数据 + * 返回读操作的数据(写操作返回 0)。 */ +u64 virtio_mmio_read(u64 gpa, int width); +void virtio_mmio_write(u64 gpa, u64 value, int width); + +/* 后端驱动注册接口(virtio_blk.c / virtio_net.c 调用) */ +struct virtio_backend { + u32 device_id; /* VIRTIO_ID_* */ + u64 gpa_base; /* 该设备的 MMIO GPA 起始 */ + u32 irq; /* guest ISA IRQ 号(cmdline virtio_mmio.device=:IRQ, + * legacy PIC 下 vector = 0x30 + irq;0 = 不注入中断) */ + u32 num_queues; /* 队列数(blk=1, net=2) */ + u32 queue_size; /* 每队列描述符数 */ + u64 device_features; /* device 提供的 feature 位 */ + u32 config_len; /* config 空间长度(从 0x100 起) */ + + /* 读取 device config 空间(offset 相对 0x100)。返回 32 位值。 */ + u32 (*read_config)(u32 offset, int width); + /* 通知:guest 写入 QUEUE_NOTIFY,后端处理该队列的请求。 */ + void (*queue_notify)(u32 queue_idx); + /* 复位设备。 */ + void (*reset)(void); +}; + +/* 注册一个后端设备(内部使用,按 gpa_base 排序查找)。 */ +int virtio_mmio_register(struct virtio_backend *dev); + +/* 提供 guest GPA→host 虚拟地址转换(后端访问 virtqueue/描述符用)。 + * 通过 EPT 翻译 GPA 到 HPA,再加 HHDM offset 得到 host 虚拟地址。 + * 返回 NULL 表示 GPA 未映射。 */ +void *virtio_gpa_to_host(u64 gpa); + +/* ===== virtqueue 结构(Virtio Spec 1.1 §2.6 split ring) ===== */ + +/* 描述符:16 字节 */ +struct virtq_desc { + u64 addr; /* GPA of data buffer */ + u32 len; /* data length */ + u16 flags; /* VIRTQ_DESC_F_* */ + u16 next; /* next descriptor if F_NEXT */ +} __attribute__((packed)); + +#define VIRTQ_DESC_F_NEXT 1 +#define VIRTQ_DESC_F_WRITE 2 +#define VIRTQ_DESC_F_INDIRECT 4 + +/* avail ring(driver → device) */ +struct virtq_avail { + u16 flags; + u16 idx; + u16 ring[1]; /* queue_size entries */ +} __attribute__((packed)); + +/* used ring 元素 */ +struct virtq_used_elem { + u32 id; /* descriptor chain head index */ + u32 len; /* bytes written (device→driver) */ +} __attribute__((packed)); + +/* used ring(device → driver) */ +struct virtq_used { + u16 flags; + u16 idx; + struct virtq_used_elem ring[1]; +} __attribute__((packed)); + +/* ===== 队列状态访问 API(后端 queue_notify 用) ===== + * + * 后端在其 queue_notify(queue_idx) 回调中需要: + * 1. 读 avail ring 取请求描述符链 + * 2. 处理请求(读/写 guest 数据 buffer) + * 3. 写 used ring 回执 + * 以下函数按 device_id + queue_idx 定位队列,操作 guest 内存。 */ + +/* 取队列的 desc/avail/used 表 host 地址 + 队列大小。 + * 返回 0 成功(指针填充),负值失败(队列未 ready/未映射)。 */ +int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx, + struct virtq_desc **desc_out, + struct virtq_avail **avail_out, + struct virtq_used **used_out, + u32 *queue_num_out); + +/* 更新 used ring idx(后端处理完一批请求后调用,让 guest 看到新回执)。 + * 由后端在写 used ring 元素后调用,内部做内存屏障。 */ +void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx); + +#endif /* UTSM_VIRTIO_MMIO_H */ diff --git a/CODE/UTSM/include/utsm/vmx.h b/CODE/UTSM/include/utsm/vmx.h index 4d737f3..ed225db 100644 --- a/CODE/UTSM/include/utsm/vmx.h +++ b/CODE/UTSM/include/utsm/vmx.h @@ -169,8 +169,10 @@ /* ===== Pin-based controls ===== */ #define PIN_EXT_INTERRUPT_EXITING (1ULL << 0) #define PIN_NMI_EXITING (1ULL << 3) +#define PIN_VMX_PREEMPTION_TIMER (1ULL << 6) /* ===== Primary processor-based controls ===== */ +#define CPU_BASED_INTR_WINDOW_EXITING (1ULL << 2) #define CPU_BASED_HLT_EXITING (1ULL << 7) #define CPU_BASED_INVLPG_EXITING (1ULL << 9) #define CPU_BASED_MWAIT_EXITING (1ULL << 10) @@ -208,6 +210,7 @@ #define EXIT_EXTERNAL_INTERRUPT 1 #define EXIT_TRIPLE_FAULT 2 #define EXIT_INIT_SIGNAL 3 +#define EXIT_INTERRUPT_WINDOW 7 #define EXIT_CPUID 10 #define EXIT_INVD 13 #define EXIT_RDMSR 31 @@ -232,8 +235,13 @@ #define EXIT_INVLPG 14 #define EXIT_EPT_VIOLATION 48 #define EXIT_EPT_MISCONFIG 49 +#define EXIT_VMX_PREEMPTION_TIMER 52 #define EXIT_APIC_ACCESS 44 +/* ===== VM-entry interruption-information field 编码 ===== */ +#define VM_ENTRY_INTR_INFO_VALID 0x80000000ULL +#define VM_ENTRY_INTR_TYPE_HW_IRQ 0 /* external interrupt (bits 10:8) */ + /* ===== EPT memory types ===== */ #define EPT_MEMORY_TYPE_UC 0ULL #define EPT_MEMORY_TYPE_WC 1ULL @@ -265,4 +273,16 @@ int vmx_vmresume(void); u64 vmx_read_msr(u32 msr); u64 vmx_get_host_cr3(void); +/* ===== 虚拟中断注入(vmexit.c 实现) ===== */ +/* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。 + * 仅排队;实际在下一次 vmresume 前(guest RFLAGS.IF=1 且无阻塞时)注入, + * 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */ +void vmx_guest_queue_irq(u32 vector); + +/* VMX preemption timer:返回 1ms 对应的 timer 计数值(按 IA32_VMX_BASIC + * bits[55:48] 的 scale 换算 TSC 频率)。 */ +u64 vmx_preemption_quantum_1ms(void); +/* 检查 CPU 是否支持 VMX preemption timer。 */ +int vmx_preemption_timer_supported(void); + #endif diff --git a/CODE/UTSM/vmm/linux_boot.c b/CODE/UTSM/vmm/linux_boot.c index e8f8cd3..fc4b24c 100644 --- a/CODE/UTSM/vmm/linux_boot.c +++ b/CODE/UTSM/vmm/linux_boot.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include "../arch/x86_64/limine.h" @@ -203,8 +204,14 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) { /* ===== VMCS controls for Linux ===== */ static void linux_vmcs_setup_controls(u64 eptp) { - u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING, - IA32_VMX_TRUE_PINBASED_CTLS); + /* Pin-based:外部中断 exit + NMI exit + VMX preemption timer(若支持)。 + * preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、 + * 推进 guest PIT tick、注入 pending virtio/COM1 IRQ。 */ + u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING; + if (vmx_preemption_timer_supported()) { + pin_want |= PIN_VMX_PREEMPTION_TIMER; + } + u64 pin = vmx_adjust_control(pin_want, 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). */ @@ -281,6 +288,10 @@ int linux_launch(void) { log_info("[LINUX] launch begin"); + /* 注册 virtio-mmio 后端(block + net),guest 经 cmdline + * virtio_mmio.device= 发现。幂等:重复调用直接返回。 */ + virtio_mmio_init(); + /* Reset VM-Exit state */ g_guest_terminated = 0; g_last_exit_reason = 0xFFFFFFFFULL; @@ -292,6 +303,9 @@ int linux_launch(void) { linux_vmcs_setup_controls(vmm_get_eptp()); linux_vmcs_setup_host_state(); + /* 初始 arm preemption timer(1ms 后首次周期 exit) */ + vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms()); + /* Set RSI for Linux boot_params (passed via global, loaded in asm) */ g_linux_rsi = gi->bootparams_gpa; diff --git a/CODE/UTSM/vmm/linux_loader.c b/CODE/UTSM/vmm/linux_loader.c index bb45b01..2662835 100644 --- a/CODE/UTSM/vmm/linux_loader.c +++ b/CODE/UTSM/vmm/linux_loader.c @@ -270,6 +270,45 @@ void *linux_find_bzimage_module(u64 *size_out) { return (void *)0; } +/* ===== initrd module finder ===== + * + * 在 Limine boot module 中查找 initramfs(路径含 "initrd" 或 "initramfs")。 + * 返回模块数据指针,*size_out 为大小;未找到返回 NULL。 */ +static void *linux_find_initrd_module(u64 *size_out) { + if (!g_module_request.response) return (void *)0; + + struct limine_module_response *resp = g_module_request.response; + for (u64 i = 0; i < resp->module_count; i++) { + struct limine_file *file = resp->modules[i]; + if (!file || !file->path) continue; + + const char *p = file->path; + int match = 0; + for (const char *s = p; *s; s++) { + /* 匹配 "initrd"(如 linux-initrd.img) */ + if ((s[0] == 'i' || s[0] == 'I') && + (s[1] == 'n' || s[1] == 'N') && + (s[2] == 'i' || s[2] == 'I') && + (s[3] == 't' || s[3] == 'T') && + (s[4] == 'r' || s[4] == 'R') && + (s[5] == 'd' || s[5] == 'D')) { + match = 1; + break; + } + } + if (!match) continue; + + if (size_out) *size_out = file->size; + log_info("[LINUX] found initrd module:"); + log_info(file->path); + log_hex64("[LINUX] initrd size=", file->size); + return file->address; + } + + log_warn("[LINUX] no initrd module found"); + return (void *)0; +} + /* ===== bzImage parser ===== */ int linux_parse_bzimage(const void *bzimage, u64 size, @@ -516,9 +555,39 @@ int linux_loader_init(void) { g_guest.cmdline_gpa = LINUX_GUEST_CMDLINE_GPA; g_guest.cmdline_size = cmdline_len; - /* For now, no initrd (Phase 1.3+ will add it) */ + /* 8.5. Load initrd (busybox initramfs) if present. + * initrd 放在 GPA 0x03000000(initrd 区,上限 16MB 到 IPC 区 0x04000000)。 */ g_guest.initrd_gpa = 0; g_guest.initrd_size = 0; + { + u64 initrd_size = 0; + void *initrd = linux_find_initrd_module(&initrd_size); + if (initrd && initrd_size > 0) { + u64 initrd_max = LINUX_GUEST_IPC_SHM_GPA - LINUX_GUEST_INITRD_GPA; /* 16MB */ + if (initrd_size > initrd_max) { + log_error("[LINUX] initrd too large (>16MB), skipped"); + } else { + u64 initrd_pages = (initrd_size + 4095) / 4096; + dkm_dma_buffer initrd_buf; + if (dma_alloc_pages(initrd_pages, 4096, 0, &initrd_buf) != 0) { + log_error("[LINUX] failed to alloc initrd memory"); + return -17; + } + mem_copy(initrd_buf.virt, initrd, initrd_size); + if (ept_map_range(LINUX_GUEST_INITRD_GPA, initrd_buf.phys, + initrd_pages * 4096, EPT_READ | EPT_WRITE) != 0) { + log_error("[LINUX] EPT map initrd failed"); + return -18; + } + g_guest.initrd_gpa = LINUX_GUEST_INITRD_GPA; + g_guest.initrd_size = initrd_size; + log_hex64("[LINUX] initrd mapped GPA=", g_guest.initrd_gpa); + log_hex64("[LINUX] initrd size=", g_guest.initrd_size); + } + } else { + log_warn("[LINUX] booting without initrd (no userland!)"); + } + } setup_boot_params(bp_buf.virt, g_guest.bootparams_gpa, g_guest.kernel_entry, diff --git a/CODE/UTSM/vmm/virtio_blk.c b/CODE/UTSM/vmm/virtio_blk.c new file mode 100644 index 0000000..2d151ba --- /dev/null +++ b/CODE/UTSM/vmm/virtio_blk.c @@ -0,0 +1,254 @@ +/* virtio_blk.c — UTSM virtio-blk 设备模拟后端。 + * + * 为 Linux guest 提供 virtio-blk 块设备。guest 的块读写请求经 + * virtqueue 传入,本后端桥接到 UTSM 真实块设备(block API,AHCI/SATA)。 + * + * Virtio Spec 1.1 §5.2 (Block Device)。 + * 请求描述符链: [header(16B, R)] [data(N*512B, R/W)] [status(1B, W)] + * 支持的操作:VIRTIO_BLK_T_IN(读)、VIRTIO_BLK_T_OUT(写)、VIRTIO_BLK_T_GET_ID。 + * + * 设计说明: + * - 桥接到 UTSM block API 的第 0 个设备(ahci0 SATA 盘)。 + * - guest 把 Arch rootfs 镜像当作 /dev/vda 挂载;镜像需预先写入 SATA 盘 + * 的某个分区/LBA 区间(由 mkfat32/build 流程或独立 raw 区提供)。 + * - 当前为最小可用:扇区 512B,单队列,无 flush/discard。 + */ + +#include +#include +#include +#include +#include + +/* virtio-blk 请求头(Virtio Spec 1.1 §5.2.6.2) */ +struct virtio_blk_outhdr { + u32 type; /* VIRTIO_BLK_T_* */ + u32 reserved; /* (ioprio) */ + u64 sector; /* 起始扇区(512B 单位) */ +} __attribute__((packed)); + +#define VIRTIO_BLK_T_IN 0 /* 读 */ +#define VIRTIO_BLK_T_OUT 1 /* 写 */ +#define VIRTIO_BLK_T_FLUSH 4 +#define VIRTIO_BLK_T_GET_ID 8 + +#define VIRTIO_BLK_S_OK 0 +#define VIRTIO_BLK_S_IOERR 1 +#define VIRTIO_BLK_S_UNSUPP 2 + +/* feature bits */ +#define VIRTIO_BLK_F_SIZE_MAX 1 +#define VIRTIO_BLK_F_SEG_MAX 2 +#define VIRTIO_BLK_F_BLK_SIZE 6 +#define VIRTIO_BLK_F_FLUSH 9 + +/* device config 空间(VIRTIO_MMIO_CONFIG 起) */ +struct virtio_blk_config { + u64 capacity; /* 总扇区数(512B) */ + u32 size_max; + u32 seg_max; + u16 geometry[4]; + u32 blk_size; + /* ... 其余字段省略 */ +} __attribute__((packed)); + +/* 后端状态 */ +static u64 g_blk_capacity; /* 扇区数 */ +static u32 g_blk_sector_size = 512; +static int g_blk_bound = -1; /* 绑定的 UTSM block 设备索引 */ + +/* 读取 device config 空间 */ +static u32 blk_read_config(u32 offset, int width) { + (void)width; + struct virtio_blk_config cfg; + /* 填零后逐字段读取 */ + u8 *c = (u8 *)&cfg; + for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0; + cfg.capacity = g_blk_capacity; + cfg.size_max = 128; /* 单请求最大 128 扇区 */ + cfg.seg_max = 32; + cfg.blk_size = g_blk_sector_size; + + /* 按 offset 返回 32 位窗口 */ + if (offset + 4 <= sizeof(cfg)) { + u32 v; + u8 *src = (u8 *)&cfg + offset; + v = (u32)src[0] | ((u32)src[1] << 8) | ((u32)src[2] << 16) | ((u32)src[3] << 24); + return v; + } + return 0; +} + +/* 处理一个块请求描述符链。返回写入 guest 的字节数(用于 used.len)。 */ +static u32 blk_handle_chain(struct virtq_desc *desc, u16 head, u32 qnum) { + (void)qnum; + if (!desc) return 0; + + /* 第一个描述符:请求头(driver→device,16 字节) */ + struct virtio_blk_outhdr *hdr = + (struct virtio_blk_outhdr *)virtio_gpa_to_host(desc[head].addr); + if (!hdr) return 0; + + u32 type = hdr->type; + u64 sector = hdr->sector; + + /* 沿描述符链找到 data 与 status 描述符 */ + u16 cur = head; + u16 data_idx = 0xFFFF, status_idx = 0xFFFF; + int hops = 0; + if (desc[cur].flags & VIRTQ_DESC_F_NEXT) { + data_idx = desc[cur].next; + cur = data_idx; + /* data 之后可能还有 status */ + while ((desc[cur].flags & VIRTQ_DESC_F_NEXT) && hops < 16) { + cur = desc[cur].next; + hops++; + } + /* 最后一个是 status(device write,1 字节) */ + status_idx = cur; + } + + u8 *status = (u8 *)0; + if (status_idx != 0xFFFF && (desc[status_idx].flags & VIRTQ_DESC_F_WRITE)) { + status = (u8 *)virtio_gpa_to_host(desc[status_idx].addr); + } + + u32 bytes_done = 0; + u8 result = VIRTIO_BLK_S_OK; + + const dkm_block_api *blk = block_get_api(); + + switch (type) { + case VIRTIO_BLK_T_IN: { /* 读:块设备 → guest data buffer */ + if (data_idx == 0xFFFF || g_blk_bound < 0 || !blk) { + result = VIRTIO_BLK_S_IOERR; + break; + } + u8 *dbuf = (u8 *)virtio_gpa_to_host(desc[data_idx].addr); + u32 dlen = desc[data_idx].len; + if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; } + u32 nsec = dlen / 512; + if (nsec == 0) nsec = 1; + if (blk->read((u32)g_blk_bound, sector, nsec, dbuf) != 0) { + result = VIRTIO_BLK_S_IOERR; + } else { + bytes_done = dlen; + } + break; + } + case VIRTIO_BLK_T_OUT: { /* 写:guest data buffer → 块设备 */ + if (data_idx == 0xFFFF || g_blk_bound < 0 || !blk) { + result = VIRTIO_BLK_S_IOERR; + break; + } + const u8 *dbuf = (const u8 *)virtio_gpa_to_host(desc[data_idx].addr); + u32 dlen = desc[data_idx].len; + if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; } + u32 nsec = dlen / 512; + if (nsec == 0) nsec = 1; + if (blk->write((u32)g_blk_bound, sector, nsec, dbuf) != 0) { + result = VIRTIO_BLK_S_IOERR; + } else { + bytes_done = 0; /* 写操作 used.len=0 */ + } + break; + } + case VIRTIO_BLK_T_GET_ID: { + if (data_idx != 0xFFFF) { + char *idbuf = (char *)virtio_gpa_to_host(desc[data_idx].addr); + if (idbuf) { + const char *id = "DESHAB-VIRTIO-BLK"; + u32 i = 0; + for (; id[i] && i < desc[data_idx].len - 1; i++) idbuf[i] = id[i]; + idbuf[i] = 0; + bytes_done = i + 1; + } + } + break; + } + case VIRTIO_BLK_T_FLUSH: + result = VIRTIO_BLK_S_OK; /* 无写缓存,直接成功 */ + break; + default: + result = VIRTIO_BLK_S_UNSUPP; + break; + } + + /* 写状态字节 */ + if (status) *status = result; + return bytes_done; +} + +/* queue_notify:guest 提交了块请求,处理队列。 */ +static u16 g_blk_last_avail; + +static void blk_queue_notify(u32 queue_idx) { + struct virtq_desc *desc; + struct virtq_avail *avail; + struct virtq_used *used; + u32 qnum; + + if (virtio_queue_get_ptrs(VIRTIO_ID_BLOCK, queue_idx, + &desc, &avail, &used, &qnum) != 0) { + return; + } + + /* 处理自上次以来的所有新 avail 请求 */ + u16 cur = g_blk_last_avail; + u16 used_idx = used->idx; + while (cur != avail->idx) { + u16 head = avail->ring[cur % qnum]; + u32 len = blk_handle_chain(desc, head, qnum); + /* 写 used 回执 */ + used->ring[used_idx % qnum].id = head; + used->ring[used_idx % qnum].len = len; + used_idx++; + cur++; + } + virtio_queue_bump_used(used, used_idx); + g_blk_last_avail = cur; +} + +static void blk_reset(void) { + g_blk_last_avail = 0; +} + +/* 后端结构体 */ +static struct virtio_backend g_blk_backend = { + .device_id = VIRTIO_ID_BLOCK, + .gpa_base = VIRTIO_MMIO_BLK_GPA, + .irq = 5, /* guest ISA IRQ5(cmdline :5,vector 0x35) */ + .num_queues = 1, + .queue_size = 128, + .device_features = (1ULL << VIRTIO_F_VERSION_1) | + (1ULL << VIRTIO_BLK_F_SIZE_MAX) | + (1ULL << VIRTIO_BLK_F_SEG_MAX) | + (1ULL << VIRTIO_BLK_F_BLK_SIZE) | + (1ULL << VIRTIO_BLK_F_FLUSH), + .config_len = sizeof(struct virtio_blk_config), + .read_config = blk_read_config, + .queue_notify = blk_queue_notify, + .reset = blk_reset, +}; + +void virtio_blk_backend_init(void) { + /* 绑定 UTSM 第 0 个块设备(ahci0 SATA 盘)作为后端存储 */ + const dkm_block_api *blk = block_get_api(); + if (blk && blk->device_count() > 0) { + g_blk_bound = 0; + u64 ssize = blk->sector_size(0); + if (ssize > 0) g_blk_sector_size = (u32)ssize; + /* 从设备名推算容量不可得,用默认值(guest 端可再探测)。 + * 实际 capacity 需读 ATA IDENTIFY;这里给 4GB 占位, + * guest mkfs/mount 会按此上限工作。 */ + g_blk_capacity = 8ULL * 1024 * 1024 * 1024 / 512; /* 8GB in sectors */ + log_info("[VBLK] bound to UTSM block device 0"); + log_hex64("[VBLK] capacity(sectors)=", g_blk_capacity); + } else { + g_blk_bound = -1; + g_blk_capacity = 0; + log_warn("[VBLK] no UTSM block device, blk backend degraded"); + } + + virtio_mmio_register(&g_blk_backend); +} diff --git a/CODE/UTSM/vmm/virtio_mmio.c b/CODE/UTSM/vmm/virtio_mmio.c new file mode 100644 index 0000000..2694606 --- /dev/null +++ b/CODE/UTSM/vmm/virtio_mmio.c @@ -0,0 +1,294 @@ +/* virtio_mmio.c — UTSM virtio-mmio 设备模拟后端框架实现。 + * + * 管理一组 virtio_backend 设备(block + net),处理 guest 对 MMIO 寄存器 + * 的读/写。寄存器语义遵循 Virtio Spec 1.1 §4.2(modern,version=2)。 + * + * guest 设备发现:cmdline virtio_mmio.device=SIZE@GPA:IRQ 参数(见 + * linux_loader 的 cmdline 注入)。QUEUE_NOTIFY 处理完后经 + * vmx_guest_queue_irq 注入虚拟中断(legacy PIC,vector=0x30+irq)。 + */ + +#include +#include +#include +#include +#include +#include +#include "../arch/x86_64/limine.h" + +extern volatile struct limine_hhdm_request g_hhdm_request; + +#define VIRTIO_MMIO_MAGIC 0x74726976 /* "virt" */ +#define VIRTIO_MMIO_VERSION_MODERN 2 +#define MAX_VIRTIO_DEVICES 4 + +/* 每个 virtio 设备的运行状态(框架层维护,后端无关) */ +struct virtio_dev_state { + struct virtio_backend *be; /* 后端驱动(NULL = 未注册) */ + u32 status; /* device status */ + u32 drv_features_sel; /* DRIVER_FEATURES_SEL */ + u32 dev_features_sel; /* DEVICE_FEATURES_SEL */ + u64 driver_features; /* guest 协商的 features */ + u32 queue_sel; /* 当前选中队列 */ + u32 interrupt_status; /* INTERRUPT_STATUS */ + u32 config_generation; + + /* 每队列状态 */ + struct { + u32 num; /* QUEUE_NUM(实际启用大小) */ + u32 ready; /* QUEUE_READY */ + u64 desc_gpa; /* 描述符表 GPA */ + u64 driver_gpa; /* avail ring GPA */ + u64 device_gpa; /* used ring GPA */ + } q[4]; +}; + +static struct virtio_dev_state g_vdevs[MAX_VIRTIO_DEVICES]; +static int g_vdev_count; +static int g_vio_ready; + +/* ===== guest 内存访问辅助 ===== */ + +void *virtio_gpa_to_host(u64 gpa) { + u64 hpa = ept_gpa_to_hpa(gpa); + if (hpa == 0) return (void *)0; + u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; + return (void *)(hhdm + hpa); +} + +/* ===== 设备注册 ===== */ + +int virtio_mmio_register(struct virtio_backend *dev) { + if (!dev || g_vdev_count >= MAX_VIRTIO_DEVICES) return -1; + struct virtio_dev_state *s = &g_vdevs[g_vdev_count]; + s->be = dev; + s->status = 0; + s->drv_features_sel = 0; + s->dev_features_sel = 0; + s->driver_features = 0; + s->queue_sel = 0; + s->interrupt_status = 0; + s->config_generation = 0; + for (int i = 0; i < 4; i++) { + s->q[i].num = 0; + s->q[i].ready = 0; + s->q[i].desc_gpa = 0; + s->q[i].driver_gpa = 0; + s->q[i].device_gpa = 0; + } + g_vdev_count++; + log_hex64("[VIO] registered device id=", dev->device_id); + log_hex64("[VIO] gpa=", dev->gpa_base); + return 0; +} + +/* 按 GPA 查找设备(含该设备的 4KB 寄存器区)。 */ +static struct virtio_dev_state *find_dev(u64 gpa, u32 *offset_out) { + for (int i = 0; i < g_vdev_count; i++) { + u64 base = g_vdevs[i].be->gpa_base; + if (gpa >= base && gpa < base + 0x1000) { + if (offset_out) *offset_out = (u32)(gpa - base); + return &g_vdevs[i]; + } + } + return (struct virtio_dev_state *)0; +} + +/* ===== feature 读取(按 sel 返回 32 位窗口) ===== */ + +static u32 read_features(struct virtio_dev_state *s) { + if (s->dev_features_sel == 0) { + return (u32)(s->be->device_features & 0xFFFFFFFFULL); + } else if (s->dev_features_sel == 1) { + return (u32)(s->be->device_features >> 32); + } + return 0; +} + +/* ===== 寄存器读 ===== */ + +u64 virtio_mmio_read(u64 gpa, int width) { + (void)width; /* virtio-mmio 寄存器固定 32 位访问 */ + u32 off; + struct virtio_dev_state *s = find_dev(gpa, &off); + if (!s || !s->be) { + log_hex64("[VIO] read unmapped gpa=", gpa); + return 0; + } + + switch (off) { + case VIRTIO_MMIO_MAGIC_VALUE: return VIRTIO_MMIO_MAGIC; + case VIRTIO_MMIO_VERSION: return VIRTIO_MMIO_VERSION_MODERN; + case VIRTIO_MMIO_DEVICE_ID: return s->be->device_id; + case VIRTIO_MMIO_VENDOR_ID: return 0x55444D55; /* "UMDU" 自定义 */ + case VIRTIO_MMIO_DEVICE_FEATURES:return read_features(s); + case VIRTIO_MMIO_QUEUE_NUM_MAX: return s->be->queue_size; + case VIRTIO_MMIO_QUEUE_READY: return s->q[s->queue_sel].ready; + case VIRTIO_MMIO_INTERRUPT_STATUS:return s->interrupt_status; + case VIRTIO_MMIO_STATUS: return s->status; + case VIRTIO_MMIO_CONFIG_GENERATION:return s->config_generation; + default: + /* device config 空间(0x100 起) */ + if (off >= VIRTIO_MMIO_CONFIG && off < VIRTIO_MMIO_CONFIG + s->be->config_len) { + if (s->be->read_config) { + return s->be->read_config(off - VIRTIO_MMIO_CONFIG, width); + } + } + return 0; + } +} + +/* ===== 寄存器写 ===== */ + +void virtio_mmio_write(u64 gpa, u64 value, int width) { + (void)width; + u32 off; + struct virtio_dev_state *s = find_dev(gpa, &off); + if (!s || !s->be) { + log_hex64("[VIO] write unmapped gpa=", gpa); + return; + } + u32 v32 = (u32)value; + + switch (off) { + case VIRTIO_MMIO_DEVICE_FEATURES_SEL: + s->dev_features_sel = v32; + break; + case VIRTIO_MMIO_DRIVER_FEATURES: + if (s->drv_features_sel == 0) { + s->driver_features = (s->driver_features & 0xFFFFFFFF00000000ULL) | v32; + } else if (s->drv_features_sel == 1) { + s->driver_features = (s->driver_features & 0xFFFFFFFFULL) | ((u64)v32 << 32); + } + break; + case VIRTIO_MMIO_DRIVER_FEATURES_SEL: + s->drv_features_sel = v32; + break; + case VIRTIO_MMIO_QUEUE_SEL: + if (v32 < 4) s->queue_sel = v32; + break; + case VIRTIO_MMIO_QUEUE_NUM: { + /* 限制在后端 queue_size 内 */ + if (v32 == 0 || v32 > s->be->queue_size) { + s->q[s->queue_sel].num = s->be->queue_size; + } else { + s->q[s->queue_sel].num = v32; + } + break; + } + case VIRTIO_MMIO_QUEUE_READY: + s->q[s->queue_sel].ready = v32 ? 1 : 0; + break; + case VIRTIO_MMIO_QUEUE_DESC_LOW: + s->q[s->queue_sel].desc_gpa = (s->q[s->queue_sel].desc_gpa & 0xFFFFFFFF00000000ULL) | v32; + break; + case VIRTIO_MMIO_QUEUE_DESC_HIGH: + s->q[s->queue_sel].desc_gpa = (s->q[s->queue_sel].desc_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32); + break; + case VIRTIO_MMIO_QUEUE_DRIVER_LOW: + s->q[s->queue_sel].driver_gpa = (s->q[s->queue_sel].driver_gpa & 0xFFFFFFFF00000000ULL) | v32; + break; + case VIRTIO_MMIO_QUEUE_DRIVER_HIGH: + s->q[s->queue_sel].driver_gpa = (s->q[s->queue_sel].driver_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32); + break; + case VIRTIO_MMIO_QUEUE_DEVICE_LOW: + s->q[s->queue_sel].device_gpa = (s->q[s->queue_sel].device_gpa & 0xFFFFFFFF00000000ULL) | v32; + break; + case VIRTIO_MMIO_QUEUE_DEVICE_HIGH: + s->q[s->queue_sel].device_gpa = (s->q[s->queue_sel].device_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32); + break; + case VIRTIO_MMIO_QUEUE_NOTIFY: + /* guest 通知设备处理队列 v32。调用后端处理请求。 */ + if (s->be->queue_notify) { + s->be->queue_notify(v32); + } + /* 设置中断状态位(used buffer notification)并注入虚拟 IRQ。 + * legacy PIC 模式:vector = 0x30 + irq(master PIC 基址 0x30)。 + * 是否真正投递由 vmexit 的 maybe_inject_irq 按 IMR/IF 决定。 */ + s->interrupt_status |= 1; + if (s->be->irq != 0) { + vmx_guest_queue_irq(0x30 + s->be->irq); + } + break; + case VIRTIO_MMIO_INTERRUPT_ACK: + s->interrupt_status &= ~v32; + break; + case VIRTIO_MMIO_STATUS: + s->status = v32; + if (v32 == 0) { + /* 复位 */ + if (s->be->reset) s->be->reset(); + s->driver_features = 0; + s->interrupt_status = 0; + for (int i = 0; i < 4; i++) { + s->q[i].ready = 0; + s->q[i].num = 0; + } + } + break; + default: + /* device config 空间写(多数设备 config 只读,忽略) */ + break; + } +} + +/* ===== 队列访问 API 实现 ===== + * + * 后端 queue_notify 调用 virtio_queue_get_ptrs 取队列三个表的 host 地址, + * 自行遍历 avail ring 处理请求,写 used ring 回执。 */ + +int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx, + struct virtq_desc **desc_out, + struct virtq_avail **avail_out, + struct virtq_used **used_out, + u32 *queue_num_out) { + struct virtio_dev_state *s = (struct virtio_dev_state *)0; + for (int i = 0; i < g_vdev_count; i++) { + if (g_vdevs[i].be && g_vdevs[i].be->device_id == device_id) { + s = &g_vdevs[i]; + break; + } + } + if (!s || queue_idx >= 4) return -1; + if (!s->q[queue_idx].ready) return -2; + + struct virtq_desc *desc = (struct virtq_desc *)virtio_gpa_to_host(s->q[queue_idx].desc_gpa); + struct virtq_avail *avail = (struct virtq_avail *)virtio_gpa_to_host(s->q[queue_idx].driver_gpa); + struct virtq_used *used = (struct virtq_used *)virtio_gpa_to_host(s->q[queue_idx].device_gpa); + if (!desc || !avail || !used) return -3; + + if (desc_out) *desc_out = desc; + if (avail_out) *avail_out = avail; + if (used_out) *used_out = used; + if (queue_num_out) *queue_num_out = s->q[queue_idx].num ? s->q[queue_idx].num : s->be->queue_size; + return 0; +} + +void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx) { + if (!used) return; + __asm__ volatile("mfence" ::: "memory"); + used->idx = new_idx; + __asm__ volatile("mfence" ::: "memory"); +} + +/* ===== 框架初始化 ===== + * + * 注册 block 与 net 后端。后端结构体由 virtio_blk.c / virtio_net.c 提供。 */ +extern struct virtio_backend g_virtio_blk_backend; +extern struct virtio_backend g_virtio_net_backend; + +/* 后端初始化符号(定义于 virtio_blk.c / virtio_net.c)。 + * 这里声明以便链接;具体注册在各自文件完成。 */ +void virtio_blk_backend_init(void); +void virtio_net_backend_init(void); + +void virtio_mmio_init(void) { + if (g_vio_ready) return; + g_vdev_count = 0; + + virtio_blk_backend_init(); + virtio_net_backend_init(); + + g_vio_ready = 1; + log_info("[VIO] virtio-mmio backend init ok"); +} diff --git a/CODE/UTSM/vmm/virtio_net.c b/CODE/UTSM/vmm/virtio_net.c new file mode 100644 index 0000000..a2c2a9e --- /dev/null +++ b/CODE/UTSM/vmm/virtio_net.c @@ -0,0 +1,218 @@ +/* virtio_net.c — UTSM virtio-net 设备模拟后端。 + * + * 为 Linux guest 提供 virtio-net 网卡。guest 的收发包请求经 virtqueue + * 传入,本后端桥接到 UTSM 真实网卡(net API,e1000/virtio_net DKM 驱动)。 + * + * Virtio Spec 1.1 §5.1 (Network Device)。 + * 队列:0=RX(device→driver 收包),1=TX(driver→device 发包)。 + * 每帧前置 virtio_net_hdr(10/12 字节,无 GSO 时全零)。 + * + * 最小可用实现: + * - 无 GSO/TSO/checksum offload(feature 不声明,guest 走软件路径) + * - RX 轮询:guest RX 队列提交空 buffer,queue_notify 时尝试 rx_poll + * - TX:queue_notify 时取帧直接 net->tx 发出 + */ + +#include +#include +#include +#include +#include + +/* virtio_net_hdr(VIRTIO_NET_F_CSUM/GSO 未协商时为 10 字节 legacy) */ +struct virtio_net_hdr { + u8 flags; + u8 gso_type; + u16 hdr_len; + u16 gso_size; + u16 csum_start; + u16 csum_offset; + /* u16 num_buffers; // 仅 VIRTIO_NET_F_MRG_RXBUF 协商后存在 */ +} __attribute__((packed)); + +#define VIRTIO_NET_HDR_LEN 10 + +/* feature bits(我们都不声明,保持最简) */ +#define VIRTIO_NET_F_CSUM 0 +#define VIRTIO_NET_F_GUEST_CSUM 1 +#define VIRTIO_NET_F_MAC 5 +#define VIRTIO_NET_F_STATUS 16 + +/* device config(VIRTIO_MMIO_CONFIG 起) */ +struct virtio_net_config { + u8 mac[6]; + u16 status; /* bit0 = link up */ + u16 max_virtqueue_pairs; +} __attribute__((packed)); + +static u8 g_net_mac[6] = { 0x52, 0x54, 0x00, 0xDE, 0x5A, 0x01 }; +static int g_net_bound = -1; + +static u32 net_read_config(u32 offset, int width) { + (void)width; + struct virtio_net_config cfg; + u8 *c = (u8 *)&cfg; + for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0; + for (int i = 0; i < 6; i++) cfg.mac[i] = g_net_mac[i]; + cfg.status = 1; /* link up */ + cfg.max_virtqueue_pairs = 1; + + if (offset + 4 <= sizeof(cfg)) { + u8 *src = (u8 *)&cfg + offset; + return (u32)src[0] | ((u32)src[1] << 8) | ((u32)src[2] << 16) | ((u32)src[3] << 24); + } + return 0; +} + +/* 从描述符链拼接一个连续帧(virtio-net 帧通常 2 个描述符:hdr + data)。 + * 返回帧长度,数据拷入 out_buf(out_cap 容量)。 */ +static u32 net_gather_frame(struct virtq_desc *desc, u16 head, + u8 *out_buf, u32 out_cap, int skip_hdr) { + u32 total = 0; + u16 cur = head; + int hops = 0; + while (hops < 32) { + u8 *src = (u8 *)virtio_gpa_to_host(desc[cur].addr); + u32 len = desc[cur].len; + if (!src) break; + + /* 跳过第一个描述符中的 virtio_net_hdr */ + if (hops == 0 && skip_hdr && len > VIRTIO_NET_HDR_LEN) { + u32 dl = len - VIRTIO_NET_HDR_LEN; + if (total + dl <= out_cap) { + u8 *d = src + VIRTIO_NET_HDR_LEN; + for (u32 i = 0; i < dl; i++) out_buf[total + i] = d[i]; + total += dl; + } + } else { + if (total + len <= out_cap) { + for (u32 i = 0; i < len; i++) out_buf[total + i] = src[i]; + total += len; + } + } + + if (!(desc[cur].flags & VIRTQ_DESC_F_NEXT)) break; + cur = desc[cur].next; + hops++; + } + return total; +} + +/* TX queue(idx=1):guest 发包。 */ +static u16 g_tx_last_avail; +static u8 g_tx_scratch[2048]; + +static void net_handle_tx(void) { + struct virtq_desc *desc; + struct virtq_avail *avail; + struct virtq_used *used; + u32 qnum; + if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 1, &desc, &avail, &used, &qnum) != 0) + return; + + const dkm_net_api *net = net_get_api(); + u16 cur = g_tx_last_avail; + u16 used_idx = used->idx; + while (cur != avail->idx) { + u16 head = avail->ring[cur % qnum]; + u32 flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch), 1); + if (flen > 0 && net && g_net_bound >= 0) { + net->tx((u32)g_net_bound, g_tx_scratch, flen); + } + used->ring[used_idx % qnum].id = head; + used->ring[used_idx % qnum].len = 0; + used_idx++; + cur++; + } + virtio_queue_bump_used(used, used_idx); + g_tx_last_avail = cur; +} + +/* RX queue(idx=0):guest 提交空 buffer,我们尝试 rx_poll 填充。 + * guest 每提交一批 buffer 就 notify 一次;有包则填充 + used,无包则 + * 保留 buffer 等下次 notify(简化:直接返回 0 长度 used,让 guest 重投)。 */ +static u16 g_rx_last_avail; + +static void net_handle_rx(void) { + struct virtq_desc *desc; + struct virtq_avail *avail; + struct virtq_used *used; + u32 qnum; + if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 0, &desc, &avail, &used, &qnum) != 0) + return; + + const dkm_net_api *net = net_get_api(); + if (!net || g_net_bound < 0) return; + + u16 cur = g_rx_last_avail; + u16 used_idx = used->idx; + while (cur != avail->idx) { + u16 head = avail->ring[cur % qnum]; + /* RX 描述符链:hdr(W) + data(W)。尝试收一包。 */ + u16 curd = head; + u8 *hdrp = (u8 *)virtio_gpa_to_host(desc[curd].addr); + u8 *datap = (u8 *)0; + u32 datalen = 0; + if (desc[curd].flags & VIRTQ_DESC_F_NEXT) { + u16 di = desc[curd].next; + datap = (u8 *)virtio_gpa_to_host(desc[di].addr); + datalen = desc[di].len; + } + if (!hdrp || !datap || datalen == 0) { cur++; continue; } + + u32 got = 0; + if (net->rx_poll((u32)g_net_bound, datap, datalen, &got) == 0 && got > 0) { + /* 填 virtio_net_hdr 全零(无 GSO) */ + for (int i = 0; i < VIRTIO_NET_HDR_LEN; i++) hdrp[i] = 0; + used->ring[used_idx % qnum].id = head; + used->ring[used_idx % qnum].len = VIRTIO_NET_HDR_LEN + got; + used_idx++; + } else { + /* 无包:不消耗该 buffer,留待下次(但为避免死循环,跳过) */ + } + cur++; + } + virtio_queue_bump_used(used, used_idx); + g_rx_last_avail = cur; +} + +static void net_queue_notify(u32 queue_idx) { + if (queue_idx == 1) net_handle_tx(); + else if (queue_idx == 0) net_handle_rx(); +} + +static void net_reset(void) { + g_tx_last_avail = 0; + g_rx_last_avail = 0; +} + +static struct virtio_backend g_net_backend = { + .device_id = VIRTIO_ID_NET, + .gpa_base = VIRTIO_MMIO_NET_GPA, + .irq = 6, /* guest ISA IRQ6(cmdline :6,vector 0x36) */ + .num_queues = 2, + .queue_size = 128, + .device_features = (1ULL << VIRTIO_F_VERSION_1) | + (1ULL << VIRTIO_NET_F_MAC) | (1ULL << VIRTIO_NET_F_STATUS), + .config_len = sizeof(struct virtio_net_config), + .read_config = net_read_config, + .queue_notify = net_queue_notify, + .reset = net_reset, +}; + +void virtio_net_backend_init(void) { + /* 绑定 UTSM 第 0 个网卡(e1000 或 virtio_net DKM 驱动) */ + const dkm_net_api *net = net_get_api(); + if (net && net->device_count() > 0) { + g_net_bound = 0; + dkm_net_device_info info; + if (net->device_info(0, &info) == 0) { + for (int i = 0; i < 6; i++) g_net_mac[i] = info.mac[i]; + } + log_info("[VNET] bound to UTSM net device 0"); + } else { + g_net_bound = -1; + log_warn("[VNET] no UTSM net device, net backend degraded"); + } + virtio_mmio_register(&g_net_backend); +} diff --git a/CODE/UTSM/vmm/vmexit.c b/CODE/UTSM/vmm/vmexit.c index 08eee02..9e8042d 100644 --- a/CODE/UTSM/vmm/vmexit.c +++ b/CODE/UTSM/vmm/vmexit.c @@ -5,15 +5,35 @@ #include #include #include +#include +#include +#include +#include "../arch/x86_64/limine.h" /* VM-Exit 处理器:从 VMCS 读取 exit reason 与 guest 状态,分发处理。 * * Phase 1.1:处理 HLT(终止 guest)、EPT violation(动态映射)、异常。 * Phase 1.3:增加 VMCALL hypercall 处理(读 guest GPR、分发、写返回值)。 + * Phase 1.4:EPT violation 区分 RAM(按需分配 HPA)与 virtio-mmio + * (路由到设备模拟寄存器读写,不映射真实内存)。 + * Phase 1.5:guest 平台设备模拟(legacy 模式,配合 noapic/nolapic): + * - COM1 8250 UART(console=ttyS0 输出 + host 串口输入桥) + * - 8259 PIC(IMR 跟踪,EOI/ICW 吞掉) + * - 8254 PIT ch0(divisor 跟踪,按 guest 编程频率注入 IRQ0 tick) + * - CPUID passthrough(屏蔽 VMX/hypervisor 位) + * - 虚拟中断注入(pending queue + interrupt-window exiting) + * - VMX preemption timer 周期 exit(轮询 host 串口 RX + tick) */ +extern volatile struct limine_hhdm_request g_hhdm_request; + extern void vmx_vmresume_wrapper(void); +/* serial.c 导出的 host 串口与 TSC 频率接口 */ +extern int serial_try_read(void); +extern u64 serial_tsc_per_ms(void); +extern void serial_putc(char c); + static u64 g_vmexit_count; static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len) { @@ -41,16 +61,595 @@ static int handle_hlt(u64 rip, u64 instr_len, int *out_resume) { return 0; } -/* 处理 EPT violation:动态映射缺失的 GPA。 - * Phase 1.1:1:1 映射缺失的页面,权限 RWX。 */ -static int handle_ept_violation(u64 qualification, int *out_resume) { +/* ===== EPT violation: guest RAM 按需分配 ===== + * + * guest 首次访问某 GPA 页时触发 EPT violation。我们按需分配一个 + * 4KB HPA 页并建立 GPA→HPA 映射(不再是错误的 1:1 恒等映射—— + * 那会把 guest GPA 直接当 host 物理地址,可能与 UTSM 自身内存冲突)。 + * + * 已映射区域的访问权限不足(如先 R 映射后写)也走这里:升级为 RWX。 + */ + +/* 按需为普通 guest RAM GPA 分配一个 HPA 页并映射。返回 0 成功。 */ +static int ept_map_guest_ram_page(u64 gpa_page) { + dkm_dma_buffer buf; + if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0, &buf) != 0) { + log_error("[VMEXIT] OOM for guest RAM page"); + return -1; + } + if (ept_map_range(gpa_page, buf.phys, EPT_PAGE_SIZE, EPT_RWX) != 0) { + log_error("[VMEXIT] EPT map guest RAM page failed"); + return -1; + } + return 0; +} + +/* ===== 虚拟中断注入(legacy PIC 模式,vector = 0x30 + irq) ===== + * + * Linux 以 noapic/nolapic 启动时走 XT-PIC:master PIC 向量基址 0x30。 + * 我们向 guest 注入的外部中断只能是这些 ISA IRQ 向量: + * IRQ0 (0x30) PIT tick、IRQ4 (0x34) COM1、IRQ5 (0x35) virtio-blk、 + * IRQ6 (0x36) virtio-net。 + * + * 注入条件:guest RFLAGS.IF=1 且 interruptibility=0 且 IMR 未 mask。 + * 不满足时 arm CPU_BASED_INTR_WINDOW_EXITING,guest 一旦开中断 + * 立即产生 interrupt-window exit,届时再注入。 + */ + +#define IRQ_PENDING_MAX 16 +static u8 g_pending_vectors[IRQ_PENDING_MAX]; +static int g_pending_head, g_pending_tail; + +/* guest 8259 PIC 状态(master) */ +static u8 g_pic_imr = 0xFF; /* 初始全 mask */ +static int g_pic_icw_state = 0; /* 0=就绪(OCW), 1=等ICW2, 2=等ICW3, 3=等ICW4 */ +static int g_pic_icw_sngl = 0; +static int g_pic_icw_need4 = 0; + +void vmx_guest_queue_irq(u32 vector) { + /* 合并连续相同向量(PIT tick 合并 / COM1 RX 去重) */ + if (g_pending_tail != g_pending_head) { + int last = (g_pending_tail + IRQ_PENDING_MAX - 1) % IRQ_PENDING_MAX; + if (g_pending_vectors[last] == (u8)vector) return; + } + int next = (g_pending_tail + 1) % IRQ_PENDING_MAX; + if (next == g_pending_head) return; /* 队列满:丢弃 */ + g_pending_vectors[g_pending_tail] = (u8)vector; + g_pending_tail = next; +} + +static void maybe_inject_irq(void) { + if (g_pending_head == g_pending_tail) return; + + u8 vec = g_pending_vectors[g_pending_head]; + + /* IMR 检查:master PIC vector 0x30+n → IMR bit n */ + if (vec >= 0x30 && vec < 0x38) { + if (g_pic_imr & (1u << (vec - 0x30))) { + /* 被 guest mask:丢弃(级别触发设备下次 notify 会重发) */ + g_pending_head = (g_pending_head + 1) % IRQ_PENDING_MAX; + return; + } + } + + u64 rflags = vmx_vmcs_read(VMCS_GUEST_RFLAGS); + u64 intr_st = vmx_vmcs_read(VMCS_GUEST_INTERRUPTIBILITY); + u64 cpu = vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL); + + if ((rflags & 0x200) && intr_st == 0) { + /* 可注入:写 VM-entry interruption-information field */ + vmx_vmcs_write(VMCS_VM_ENTRY_INTERRUPT_INFO, + VM_ENTRY_INTR_INFO_VALID | + (VM_ENTRY_INTR_TYPE_HW_IRQ << 8) | vec); + g_pending_head = (g_pending_head + 1) % IRQ_PENDING_MAX; + if (cpu & CPU_BASED_INTR_WINDOW_EXITING) { + vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, + cpu & ~CPU_BASED_INTR_WINDOW_EXITING); + } + } else { + /* 暂不可注入:arm interrupt-window exiting */ + if (!(cpu & CPU_BASED_INTR_WINDOW_EXITING)) { + vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, + cpu | CPU_BASED_INTR_WINDOW_EXITING); + } + } +} + +/* ===== guest COM1 8250 UART 模拟 ===== + * + * console=ttyS0 的 I/O 端口 0x3F8-0x3FF。 + * TX:THR 写 → host 串口输出(与 UTSM log 共用 QEMU stdio)。 + * RX:host 串口输入 → g_guest_rx 环形缓冲 → guest 读 RBR; + * 有数据时注入 IRQ4(vector 0x34)。 + * 寄存器:DLL/DLM/IER/FCR/LCR/MCR/SCR 存储,LSR/IIR/MSR 按状态合成。 + */ + +#define GUEST_RX_SIZE 256 +static u8 g_guest_rx[GUEST_RX_SIZE]; +static int g_rx_head, g_rx_tail; + +static u8 g_com1_dll, g_com1_dlm, g_com1_ier, g_com1_fcr; +static u8 g_com1_lcr, g_com1_mcr, g_com1_scr; + +static u64 com1_io(u32 port, int is_in, u64 value) { + u32 reg = port - 0x3F8; + int dlab = (g_com1_lcr & 0x80) != 0; + if (is_in) { + switch (reg) { + case 0: /* RBR / DLL */ + if (dlab) return g_com1_dll; + if (g_rx_head != g_rx_tail) { + u8 b = g_guest_rx[g_rx_head]; + g_rx_head = (g_rx_head + 1) % GUEST_RX_SIZE; + return b; + } + return 0; + case 1: return dlab ? g_com1_dlm : g_com1_ier; + case 2: /* IIR:bit0=0 表示有中断;bits7:6=1 表示 FIFO 启用 */ + if (g_rx_head != g_rx_tail) return 0xC4; /* RX data available */ + return 0xC1; /* no interrupt pending */ + case 3: return g_com1_lcr; + case 4: return g_com1_mcr; + case 5: /* LSR:bit0 DR,bit5 THRE,bit6 TEMT */ + return ((g_rx_head != g_rx_tail) ? 0x01 : 0x00) | 0x20 | 0x40; + case 6: return 0xB0; /* MSR:CTS|DSR|DCD */ + case 7: return g_com1_scr; + } + return 0xFF; + } + switch (reg) { + case 0: + if (dlab) g_com1_dll = (u8)value; + else serial_putc((char)(value & 0xFF)); /* guest → host 串口 */ + break; + case 1: if (dlab) g_com1_dlm = (u8)value; else g_com1_ier = (u8)value; break; + case 2: g_com1_fcr = (u8)value; break; + case 3: g_com1_lcr = (u8)value; break; + case 4: g_com1_mcr = (u8)value; break; + case 7: g_com1_scr = (u8)value; break; + } + return 0; +} + +/* ===== guest 8254 PIT ch0 模拟(仅跟踪 divisor,tick 由 preemption exit 驱动) ===== */ + +static u16 g_pit_divisor = 0; /* 0 = 65536(默认 18.2Hz) */ +static int g_pit_load_state = 0; /* 0=等 lo 字节, 1=等 hi 字节 */ +static u8 g_pit_lo = 0; +static u64 g_pit_period_tsc = 0; /* 每 tick 的 TSC 周期 */ +static u64 g_pit_accum = 0; +static u64 g_last_tick_tsc = 0; + +static u64 rdtsc_local(void) { + u32 lo, hi; + __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi)); + return ((u64)hi << 32) | lo; +} + +static void pit_recompute_period(void) { + u64 tpm = serial_tsc_per_ms(); + if (tpm == 0) tpm = 2000000; /* 未校准时按 ~2GHz 兜底 */ + u64 div = g_pit_divisor ? g_pit_divisor : 65536; + /* period_tsc = div / 1193182 Hz,用 ms 刻度换算避免溢出 */ + g_pit_period_tsc = (div * tpm) / 1193; /* ≈ div * tpm * 1000 / 1193182 */ + if (g_pit_period_tsc == 0) g_pit_period_tsc = 1; +} + +static u64 pit_io(u32 port, int is_in, u64 value) { + if (is_in) return 0; /* 计数器读回:恒 0(Linux 用 TSC clocksource,不依赖) */ + if (port == 0x43) { + /* mode/command:仅关心 ch0 + lo/hi 访问方式 */ + if ((value & 0xC0) == 0x00 && (value & 0x30) == 0x30) { + g_pit_load_state = 0; + } + return 0; + } + if (port == 0x40) { + if (g_pit_load_state == 0) { + g_pit_lo = (u8)value; + g_pit_load_state = 1; + } else { + g_pit_divisor = (u16)(g_pit_lo | ((u16)(u8)value << 8)); + g_pit_load_state = 0; + pit_recompute_period(); + } + return 0; + } + return 0; /* 0x41/0x42 吞掉 */ +} + +/* ===== 其余 legacy 端口(吞掉/返回安全值) ===== */ + +static u64 misc_io(u32 port, int is_in, u64 value, int *out_terminate) { + /* 8259 PIC master */ + if (port == 0x20) { + if (!is_in) { + if (value & 0x10) { /* ICW1 */ + g_pic_icw_sngl = (value & 0x02) != 0; + g_pic_icw_need4 = (value & 0x01) != 0; + g_pic_icw_state = 1; + g_pic_imr = 0xFF; + } + /* 其余 OCW2/OCW3(含 EOI)吞掉 */ + } + return 0; + } + if (port == 0x21) { + if (is_in) return g_pic_imr; + switch (g_pic_icw_state) { + case 1: /* ICW2(向量基址,忽略,固定 0x30) */ + g_pic_icw_state = g_pic_icw_sngl ? (g_pic_icw_need4 ? 3 : 0) : 2; + break; + case 2: /* ICW3 */ + g_pic_icw_state = g_pic_icw_need4 ? 3 : 0; + break; + case 3: /* ICW4 */ + g_pic_icw_state = 0; + break; + default: /* OCW1 = IMR */ + g_pic_imr = (u8)value; + break; + } + return 0; + } + /* 8259 PIC slave:吞掉(不用) */ + if (port == 0xA0 || port == 0xA1) return is_in ? 0xFF : 0; + + /* 8254 PIT */ + if (port >= 0x40 && port <= 0x43) return pit_io(port, is_in, value); + + /* PS/2 键盘/鼠标(i8042):status 读 0(空),data 读 0,写吞掉。 + * i8042 自检将失败 → guest 无 PS/2 键鼠(console 输入走串口)。 */ + if (port == 0x60 || port == 0x64) return is_in ? 0 : 0; + + /* CMOS/RTC */ + if (port == 0x70 || port == 0x71) return is_in ? 0 : 0; + + /* PCI config mechanism #1:对 guest 隐藏整个 PCI 总线。 + * guest reconfig BAR 会破坏 UTSM 的 AHCI/e1000,故全部返回"无设备"。 */ + if (port >= 0xCF8 && port <= 0xCFF) return is_in ? 0xFFFFFFFFULL : 0; + + /* 0xCF9 reset control:guest 请求重启 → 终止 guest 回 host */ + if (port == 0xCF9) { + if (!is_in) { + log_warn("[VMEXIT] guest requested reboot via 0xCF9"); + *out_terminate = 1; + } + return 0; + } + + /* COM2-4:无设备 */ + if ((port >= 0x2F8 && port <= 0x2FF) || + (port >= 0x3E8 && port <= 0x3EF) || + (port >= 0x2E8 && port <= 0x2EF)) { + return is_in ? 0xFF : 0; + } + + /* 其余端口(0x80 delay、0x61 NMI、0x92 A20、0xB2 APM、0xF0-0xFF FPU + * error、0xED delay、ACPI 等):读 0,写吞掉 */ + return is_in ? 0 : 0; +} + +/* ===== EXIT_IO_INSTRUCTION 处理 ===== + * + * exit qualification(Intel SDM 27.2.4.2? 实为 Vol 3D 表 27-6): + * bits 2:0 = 访问大小-1(0=1B, 1=2B, 3=4B) + * bit 3 = 方向(0=OUT, 1=IN) + * bit 4 = string 指令 + * bit 5 = REP 前缀 + * bits31:16 = 端口号 + * OUT 数据在 guest RAX;IN 结果写回 guest RAX。 + */ +static int handle_io(u64 qualification, u64 rip, u64 instr_len, int *out_resume) { + u32 port = (u32)(qualification >> 16); + u32 size = (u32)(qualification & 7) + 1; + int is_in = (qualification >> 3) & 1; + u64 value = 0; + int terminate = 0; + + if (!is_in) { + value = g_guest_regs.rax; + if (size == 1) value &= 0xFF; + else if (size == 2) value &= 0xFFFF; + else value &= 0xFFFFFFFFULL; + } + + u64 result; + if (port >= 0x3F8 && port <= 0x3FF) { + result = com1_io(port, is_in, value); + } else { + result = misc_io(port, is_in, value, &terminate); + } + + if (is_in) { + if (size == 1) { + g_guest_regs.rax = (g_guest_regs.rax & ~0xFFULL) | (result & 0xFF); + } else if (size == 2) { + g_guest_regs.rax = (g_guest_regs.rax & ~0xFFFFULL) | (result & 0xFFFF); + } else { + g_guest_regs.rax = result & 0xFFFFFFFFULL; /* 32 位写清零高 32 位 */ + } + } + + if (terminate) { + *out_resume = 0; + return 0; + } + + vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len); + *out_resume = 1; + return 0; +} + +/* ===== EXIT_CPUID 处理:host passthrough + 屏蔽 VMX/hypervisor ===== + * + * CPUID 在 VMX non-root 无条件 exit。必须真实执行并把结果写回 guest + * rax/rbx/rcx/rdx,否则 guest 特性探测全错(原"skip"实现会崩 Linux)。 + * leaf 1 ecx:清 bit5(VMX) 与 bit31(hypervisor),让 guest 认为自己在裸机。 + */ +static void cpuid_host(u32 a, u32 c, u32 *oa, u32 *ob, u32 *oc, u32 *od) { + __asm__ volatile("cpuid" + : "=a"(*oa), "=b"(*ob), "=c"(*oc), "=d"(*od) + : "a"(a), "c"(c)); +} + +static int handle_cpuid(u64 rip, u64 instr_len, int *out_resume) { + u32 a = (u32)g_guest_regs.rax; + u32 c = (u32)g_guest_regs.rcx; + u32 oa, ob, oc, od; + cpuid_host(a, c, &oa, &ob, &oc, &od); + + if (a == 1) { + oc &= ~(1u << 5); /* VMX */ + oc &= ~(1u << 31); /* hypervisor present */ + } + /* leaf 0x15/0x16 passthrough:QEMU 提供 TSC/总线频率, + * guest 据此校准 TSC(绕过 PIT ch2 校准路径)。 */ + + g_guest_regs.rax = oa; + g_guest_regs.rbx = ob; + g_guest_regs.rcx = oc; + g_guest_regs.rdx = od; + + vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len); + *out_resume = 1; + return 0; +} + +/* ===== resume 前例行工作:RX 轮询 + PIT tick + 注入 pending IRQ ===== */ + +static void poll_host_serial_to_guest(void) { + for (int i = 0; i < 32; i++) { + int b = serial_try_read(); + if (b < 0) break; + int next = (g_rx_tail + 1) % GUEST_RX_SIZE; + if (next == g_rx_head) break; /* ring 满 */ + g_guest_rx[g_rx_tail] = (u8)b; + g_rx_tail = next; + } + if (g_rx_head != g_rx_tail) { + vmx_guest_queue_irq(0x34); /* COM1 = IRQ4 → vector 0x34 */ + } +} + +static void pit_tick_update(void) { + u64 now = rdtsc_local(); + if (g_last_tick_tsc == 0) { + g_last_tick_tsc = now; + if (g_pit_period_tsc == 0) pit_recompute_period(); + return; + } + u64 delta = now - g_last_tick_tsc; + g_last_tick_tsc = now; + if (g_pit_period_tsc == 0) return; + g_pit_accum += delta; + int n = 0; + while (g_pit_accum >= g_pit_period_tsc && n < 8) { + g_pit_accum -= g_pit_period_tsc; + vmx_guest_queue_irq(0x30); /* PIT = IRQ0 → vector 0x30(自动合并) */ + n++; + } + if (g_pit_accum >= g_pit_period_tsc) g_pit_accum = 0; /* 防积压 */ +} + +static u64 g_preemption_quantum = 0; + +u64 vmx_preemption_quantum_1ms(void) { + if (g_preemption_quantum) return g_preemption_quantum; + u64 basic = vmx_read_msr(IA32_VMX_BASIC); + u64 scale = (basic >> 48) & 0xFF; /* preemption timer scale */ + if (scale == 0) scale = 1; + u64 tpm = serial_tsc_per_ms(); + if (tpm == 0) tpm = 2000000; + g_preemption_quantum = tpm / scale; + if (g_preemption_quantum == 0) g_preemption_quantum = 1; + return g_preemption_quantum; +} + +int vmx_preemption_timer_supported(void) { + u64 msr = vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS); + return (msr & (1ULL << (6 + 32))) != 0; /* bit6 允许置 1 */ +} + +/* 每次 vmresume 前调用(vmexit_dispatch resume=1 路径末尾)。 + * 重新 arm preemption timer(1ms 周期 exit),轮询 host 串口 RX, + * 推进 PIT tick,注入 pending IRQ。 */ +static void vmexit_before_resume(void) { + if (!g_linux_guest_active) return; + vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms()); + poll_host_serial_to_guest(); + pit_tick_update(); + maybe_inject_irq(); +} + +/* ===== EPT violation: 指令解码(用于 virtio-mmio MMIO 数据) ===== + * + * virtio-mmio 寄存器访问是普通 MOV 指令,如: + * mov eax, [gpa] 读 (8A /r 或 A1) + * mov [gpa], eax 写 (89 /r 或 A3) + * movzx/movsx 等 + * + * 我们不需要完整解码——只需确定: + * 1. 访问方向(读 or 写) + * 2. 访问宽度(1/2/4/8 字节) + * 3. 写访问时的写入数据;读访问时数据写到哪个 guest GPR + * + * 这通过解码指令字节 + 读 guest RIP 处的机器码完成。 + * 用 GVA=EPT GPA 简化:guest 采用恒等页表,GUEST_LINEAR_ADDR 即 GPA。 + */ + +/* 从 guest 内存读指令字节(GPA=GVA 恒等映射,经 EPT 翻译到 HPA)。 */ +static int guest_fetch(u64 gva, u8 *buf, u64 len) { + u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; + for (u64 i = 0; i < len; i++) { + u64 hpa = ept_gpa_to_hpa(gva + i); + if (hpa == 0) return -1; + buf[i] = *(volatile u8 *)(hhdm + hpa); + } + return 0; +} + +/* 解码 MMIO 访问指令。 + * rip_gpa : guest RIP(GPA,恒等映射) + * is_write_out : 输出 1=写 MMIO,0=读 MMIO + * width_out : 输出访问宽度(字节) + * reg_field_out: 输出目标/源 GPR 在 g_guest_regs 中的索引(0-15) + * 返回指令总长度(用于回退时推进 RIP),失败返回 0。 */ +static u64 decode_mmio_insn(u64 rip_gpa, int *is_write_out, int *width_out, + int *reg_field_out) { + u8 insn[16]; + if (guest_fetch(rip_gpa, insn, sizeof(insn)) != 0) return 0; + + u64 off = 0; + int rex_w = 0, rex_r = 0; + + /* 跳过前缀 + REX */ + for (;;) { + u8 b = insn[off]; + if (b == 0x66 || b == 0x67 || b == 0xF0 || b == 0xF2 || b == 0xF3 || + (b >= 0x2E && b <= 0x3E && (b==0x2E||b==0x36||b==0x3E||b==0x26||b==0x64||b==0x65))) { + off++; + continue; + } + if (b >= 0x40 && b <= 0x4F) { /* REX */ + rex_w = (b >> 3) & 1; + rex_r = (b >> 2) & 1; + off++; + } + break; + } + + u8 op = insn[off]; + u8 modrm = insn[off + 1]; + int reg = ((modrm >> 3) & 7) | (rex_r << 3); + + /* mov r, m(读 MMIO): 8A(8位) / 8B(全宽) */ + if (op == 0x8A || op == 0x8B) { + *is_write_out = 0; + *width_out = (op == 0x8A) ? 1 : (rex_w ? 8 : 4); + *reg_field_out = reg; + return off + 2; /* 简化:假设 ModRM mod=00,rm=101(无 SIB/disp) */ + } + /* mov m, r(写 MMIO): 88(8位) / 89(全宽) */ + if (op == 0x88 || op == 0x89) { + *is_write_out = 1; + *width_out = (op == 0x88) ? 1 : (rex_w ? 8 : 4); + *reg_field_out = reg; + return off + 2; + } + /* movzx: 0F B6(8→宽) / 0F B7(16→宽) 读 */ + if (op == 0x0F && (insn[off+1] == 0xB6 || insn[off+1] == 0xB7)) { + *is_write_out = 0; + *width_out = (insn[off+1] == 0xB6) ? 1 : 2; + u8 m2 = insn[off + 2]; + *reg_field_out = ((m2 >> 3) & 7) | (rex_r << 3); + return off + 3; + } + + /* 无法解码(如带 SIB/displacement 的复杂寻址)。 + * virtio-mmio 驱动用简单寄存器访问,暂不支持则返回 0。 */ + return 0; +} + +/* 按索引读写 guest GPR(用于 MMIO 数据的源/目的)。 */ +static u64 *gpr_ptr(int idx) { + switch (idx & 15) { + case 0: return (u64 *)&g_guest_regs.rax; + case 1: return (u64 *)&g_guest_regs.rcx; + case 2: return (u64 *)&g_guest_regs.rdx; + case 3: return (u64 *)&g_guest_regs.rbx; + case 6: return (u64 *)&g_guest_regs.rsi; + case 7: return (u64 *)&g_guest_regs.rdi; + case 5: return (u64 *)&g_guest_regs.rbp; + case 4: return (u64 *)&g_guest_regs.rax; /* rsp 不用作 MMIO 数据,占位 */ + case 8: return (u64 *)&g_guest_regs.r8; + case 9: return (u64 *)&g_guest_regs.r9; + case 10: return (u64 *)&g_guest_regs.r10; + case 11: return (u64 *)&g_guest_regs.r11; + case 12: return (u64 *)&g_guest_regs.r12; + case 13: return (u64 *)&g_guest_regs.r13; + case 14: return (u64 *)&g_guest_regs.r14; + case 15: return (u64 *)&g_guest_regs.r15; + } + return (u64 *)&g_guest_regs.rax; +} + +/* 处理 virtio-mmio 设备 GPA 的访问:路由到设备模拟。返回 1=已处理(resume)。 */ +static int handle_virtio_mmio_access(u64 gpa, u64 rip, int *out_resume) { + int is_write, width, reg; + u64 insn_len = decode_mmio_insn(rip, &is_write, &width, ®); + if (insn_len == 0) { + log_error("[VMEXIT] virtio-mmio: cannot decode access insn"); + log_hex64("[VMEXIT] rip=", rip); + *out_resume = 0; + return -1; + } + + u64 value = 0; + if (is_write) { + /* 从 guest GPR 取写入数据(按宽度截断) */ + u64 raw = *gpr_ptr(reg); + if (width == 1) value = raw & 0xFF; + else if (width == 2) value = raw & 0xFFFF; + else if (width == 4) value = raw & 0xFFFFFFFFULL; + else value = raw; + virtio_mmio_write(gpa, value, width); + } else { + /* 从设备读,写入 guest GPR(按宽度) */ + value = virtio_mmio_read(gpa, width); + u64 *dst = gpr_ptr(reg); + if (width == 1) *dst = (*dst & ~0xFFULL) | (value & 0xFF); + else if (width == 2) *dst = (*dst & ~0xFFFFULL) | (value & 0xFFFF); + else if (width == 4) *dst = value & 0xFFFFFFFFULL; /* 32位写清零高32 */ + else *dst = value; + } + + /* 推进 RIP 越过 MMIO 指令,resume guest */ + vmx_vmcs_write(VMCS_GUEST_RIP, rip + insn_len); + *out_resume = 1; + return 0; +} + +/* 处理 EPT violation:区分 guest RAM(按需分配)与 virtio-mmio(设备模拟)。 + * + * exit qualification bits: + * bit0 = read access, bit1 = write access, bit2 = instruction fetch + * bit3 = EPT entry readable, bit4 = writable, bit5 = executable + * virtio-mmio GPA 故意不映射 → bit3/4/5 全 0 → "访问不存在页"。 */ +static int handle_ept_violation(u64 qualification, u64 rip, int *out_resume) { + (void)qualification; /* virtio-mmio 判定只看 GPA,不看访问类型 */ u64 gpa = vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR); + + /* virtio-mmio 设备区:路由到设备模拟,绝不映射真实内存 */ + if (IS_VIRTIO_MMIO_GPA(gpa)) { + return handle_virtio_mmio_access(gpa, rip, out_resume); + } + + /* 普通 guest RAM:按需分配 HPA 页并映射 */ 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"); + if (ept_map_guest_ram_page(page) != 0) { + log_error("[VMEXIT] EPT violation: guest RAM map failed"); + log_hex64("[VMEXIT] gpa=", gpa); *out_resume = 0; return -1; } @@ -84,7 +683,13 @@ int vmexit_dispatch(void) { handle_hlt(rip, instr_len, &resume); break; case EXIT_EPT_VIOLATION: - handle_ept_violation(qualification, &resume); + handle_ept_violation(qualification, rip, &resume); + break; + case EXIT_EPT_MISCONFIG: + /* MMIO 区域权限配置错误或硬件 EPT 表损坏。诊断后终止。 */ + log_error("[VMEXIT] EPT misconfig"); + log_hex64("[VMEXIT] gpa=", vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR)); + resume = 0; break; case EXIT_EXCEPTION_NMI: handle_exception(qualification, rip, &resume); @@ -94,14 +699,26 @@ int vmexit_dispatch(void) { 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; + /* host passthrough + 屏蔽 VMX/hypervisor 位(handle_cpuid 内推进 RIP) */ + handle_cpuid(rip, instr_len, &resume); break; case EXIT_IO_INSTRUCTION: - /* 简化:跳过 IO 指令。Phase 1.4 virtio-console 完整处理。 */ - vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len); + /* legacy 设备模拟:COM1 UART / PIC / PIT / 其余端口吞掉 */ + handle_io(qualification, rip, instr_len, &resume); + break; + case EXIT_INTERRUPT_WINDOW: + /* guest 刚开中断(RFLAGS.IF=1):尝试注入 pending IRQ */ + { + u64 cpu = vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL); + vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, + cpu & ~CPU_BASED_INTR_WINDOW_EXITING); + maybe_inject_irq(); + resume = 1; + } + break; + case EXIT_VMX_PREEMPTION_TIMER: + /* 1ms 周期 exit:resume 前由 vmexit_before_resume 统一 + * 轮询 host 串口 RX / 推进 PIT tick / 注入 pending IRQ */ resume = 1; break; case EXIT_RDMSR: @@ -129,6 +746,12 @@ int vmexit_dispatch(void) { } (void)qualification; + + /* resume 前例行工作:arm preemption timer + host 串口 RX 轮询 + + * PIT tick 推进 + pending IRQ 注入(仅 Linux guest active 时) */ + if (resume) { + vmexit_before_resume(); + } return resume; } diff --git a/CODE/cmd/main.c b/CODE/cmd/main.c index edef93f..70b504f 100644 --- a/CODE/cmd/main.c +++ b/CODE/cmd/main.c @@ -10,6 +10,7 @@ #include "../UTSM/include/utsm/dsk.h" #include "../UTSM/include/utsm/pe.h" +#include "../UTSM/pe/pe_loader.h" /* PE 头结构体(image_dos_header 等),供 peinfo 解析 */ typedef unsigned char u8; typedef unsigned short u16; @@ -196,6 +197,18 @@ static void term_putx(u32 v) { } } +/* 64 位十六进制输出(前导零抑制),用于 PE ImageBase 等宽字段 */ +static void term_putx64(u64 v) { + static const char hex[] = "0123456789ABCDEF"; + term_putc('0'); term_putc('x'); + int started = 0; + for (int i = 60; i >= 0; i -= 4) { + u64 nib = (v >> i) & 0xF; + if (nib) started = 1; + if (started || i == 0) term_putc(hex[nib]); + } +} + /* ---- 终端全屏渲染 ---- */ static void term_redraw_all(void) { du_fill_rect(&g_ctx, 0, 0, (i64)fb_w, (i64)fb_h, CMD_BG); @@ -372,6 +385,8 @@ static void cmd_help(void) { term_puts_color("\nPE 程序执行:\r\n", CMD_WARN); term_puts(" .exe 运行 PE32+/PE32 程序 (如 hello.exe)\r\n"); term_puts(" 自动查找 name.exe 并运行\r\n"); + term_puts(" pe 显式运行 PE 程序 (如 pe hello64)\r\n"); + term_puts(" peinfo 显示 PE 头信息 (不执行)\r\n"); } static void cmd_ver(void) { @@ -645,6 +660,8 @@ static int has_exec_ext(const char *name) { * prog_name = 程序名(可能带或不带 .exe 后缀) * full_cmdline = 完整命令行(程序名 + 参数,即 GetCommandLineA 返回值) */ +static int cmd_read_pe_from_bin(const char *name11, u8 **data, u32 *size); /* 前向声明 */ + static void exec_pe(const char *prog_name, const char *full_cmdline) { if (!g_pe_svc) { term_puts_color("PE 兼容层不可用(UTSM PE 服务未初始化)\r\n", CMD_ERROR); @@ -677,12 +694,17 @@ static void exec_pe(const char *prog_name, const char *full_cmdline) { return; } - /* 从 FAT32 根目录读取 */ + /* PATH 查找:先根目录,再 /bin/ */ u8 *pe_data = 0; u32 pe_size = 0; - if (f32_read_root_file(name11, &pe_data, &pe_size) != 0) { + int rc = f32_read_root_file(name11, &pe_data, &pe_size); + if (rc != 0) { + rc = cmd_read_pe_from_bin(name11, &pe_data, &pe_size); + } + if (rc != 0) { term_puts_color("'", CMD_ERROR); term_puts_color(prog_name, CMD_ERROR); term_puts_color("' 不是内部或外部命令,也不是可运行的程序。\r\n", CMD_ERROR); + term_puts_color(" (已搜索 C:\\ 及 C:\\bin\\)\r\n", CMD_DIM); return; } @@ -702,7 +724,7 @@ static void exec_pe(const char *prog_name, const char *full_cmdline) { swrite("[cmd] cmdline: "); swrite(full_cmdline); swrite("\n"); u64 exit_code = 0; - int rc = g_pe_svc->run(pe_data, (u64)pe_size, full_cmdline, &exit_code); + rc = g_pe_svc->run(pe_data, (u64)pe_size, full_cmdline, &exit_code); if (rc != 0) { term_puts_color("PE 执行失败,错误码: ", CMD_ERROR); @@ -717,6 +739,417 @@ static void exec_pe(const char *prog_name, const char *full_cmdline) { logl("[cmd] PE run end"); } +/* ============================================================ + * /bin 子目录支持 — 用于查找 PE 可执行程序 + * ============================================================ */ + +/* 在根目录簇链中查找名为 name11 的子目录(attr & 0x10),返回首簇号。 + * 使用 f32_disk 缓存(来自 fat32_io.h)。返回 0 成功,-1 未找到。 */ +static int cmd_find_subdir(u32 dir_clus, const char *name11, u32 *out_clus) { + if (!f32_disk_loaded && f32_disk_load() != 0) return -2; + 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; + while (clus >= 2 && clus < 0x0FFFFFF8) { + u32 lba = data_lba + (clus - 2) * spc; + const u8 *cb; + if ((u64)lba * 512 + (u64)spc * 512 <= sizeof(f32_disk)) + 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_dir; + if ((u8)dir[e].name[0] == 0xE5) continue; + if (dir[e].attr == 0x0F) continue; + if (dir[e].attr & 0x08) continue; + if (!(dir[e].attr & 0x10)) continue; /* 只匹配目录 */ + if (f32_neq11(dir[e].name, name11)) { + *out_clus = f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16); + return 0; + } + } + u32 fo = fat_byte_off + clus * 4; + if (fo + 4 > sizeof(f32_disk)) break; + clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } +done_dir: + return -1; +} + +/* 从指定目录首簇读取文件到 f32_data。 + * dir_clus: 目录起始簇号。name11: 8.3 名。 + * 返回 0 成功,*out_data 指向 f32_data,*out_size 为字节数;非 0 失败。 */ +static int cmd_read_file_in_dir(u32 dir_clus, const char *name11, u8 **out_data, u32 *out_size) { + if (!f32_disk_loaded && 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; + 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 <= sizeof(f32_disk)) + 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_find; + if ((u8)dir[e].name[0] == 0xE5) continue; + if (dir[e].attr == 0x0F) continue; + if (dir[e].attr & 0x08) continue; + if (dir[e].attr & 0x10) continue; /* 跳过目录 */ + if (f32_neq11(dir[e].name, name11)) { + found_clus = f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16); + found_size = dir[e].fsize; + found = 1; + break; + } + } + u32 fo = fat_byte_off + clus * 4; + if (fo + 4 > sizeof(f32_disk)) break; + clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF; + } +done_find: + 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 <= sizeof(f32_disk)) + 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; +} + +static u32 g_cmd_bin_clus = 0; +static int g_cmd_bin_checked = 0; + +/* 获取 /bin 目录首簇号。返回 0 成功,-1 未找到。 */ +static int cmd_get_bin_cluster(u32 *out_clus) { + if (g_cmd_bin_checked) { + if (g_cmd_bin_clus == 0) return -1; + *out_clus = g_cmd_bin_clus; + return 0; + } + g_cmd_bin_checked = 1; + const f32_bpb *bpb = (const f32_bpb *)f32_disk; + char bin83[11] = {'B','I','N',' ',' ',' ',' ',' ',' ',' ',' '}; + if (cmd_find_subdir(bpb->root_clus, bin83, &g_cmd_bin_clus) != 0) { + g_cmd_bin_clus = 0; + return -1; + } + *out_clus = g_cmd_bin_clus; + return 0; +} + +/* 从 /bin 子目录读取 PE 文件。返回 0 成功。 */ +static int cmd_read_pe_from_bin(const char *name11, u8 **data, u32 *size) { + u32 bin_clus; + if (cmd_get_bin_cluster(&bin_clus) != 0) return -1; + return cmd_read_file_in_dir(bin_clus, name11, data, size); +} + +/* ============================================================ + * peinfo — 解析并显示 PE 头信息(不执行) + * ============================================================ */ + +/* 把程序名转为 8.3 名(自动补 .exe),返回 0 成功,-1 失败。 + * 内联自 exec_pe 的同名逻辑,供 cmd_peinfo 复用。 */ +static int make_pe_name83(const char *prog, char name11[11]) { + char base[64]; + str_copy(base, prog, sizeof(base)); + int has_dot = 0; + for (int i = 0; base[i]; i++) { + if (base[i] == '.') { has_dot = 1; break; } + } + if (!has_dot) { + int bl = str_len(base); + if (bl > 59) bl = 59; + base[bl] = '.'; base[bl + 1] = 'e'; base[bl + 2] = 'x'; + base[bl + 3] = 'e'; base[bl + 4] = 0; + } + return f32_name_to_83(base, name11); +} + +static void cmd_peinfo(const char *args) { + if (!*args) { + term_puts_color("用法: peinfo <程序名> (如 peinfo hello64)\r\n", CMD_INFO); + return; + } + if (!f32_blk_read) { + term_puts_color("peinfo: 无块设备\r\n", CMD_ERROR); + return; + } + + char name11[11]; + if (make_pe_name83(args, name11) != 0) { + term_puts_color("peinfo: 无效文件名\r\n", CMD_ERROR); + return; + } + + /* PATH 查找:先根目录,再 /bin/ */ + u8 *pe = 0; u32 pe_size = 0; + int rc = f32_read_root_file(name11, &pe, &pe_size); + if (rc != 0) { + rc = cmd_read_pe_from_bin(name11, &pe, &pe_size); + } + if (rc != 0) { + term_puts_color("peinfo: 文件不存在: ", CMD_ERROR); + term_puts_color(args, CMD_ERROR); + term_puts_color(" (已搜索 C:\\ 及 C:\\bin\\)\r\n", CMD_ERROR); + return; + } + + /* ---- DOS 头 ---- */ + if (pe_size < sizeof(image_dos_header)) { + term_puts_color("peinfo: 文件过小,无 DOS 头\r\n", CMD_ERROR); + return; + } + const image_dos_header *dos = (const image_dos_header *)pe; + if (dos->e_magic != IMAGE_DOS_SIGNATURE) { + term_puts_color("peinfo: 缺少 MZ 签名\r\n", CMD_ERROR); + return; + } + u32 lfanew = dos->e_lfanew; + if ((u64)lfanew + 4 + sizeof(image_file_header) > pe_size) { + term_puts_color("peinfo: e_lfanew 越界\r\n", CMD_ERROR); + return; + } + + term_puts_color("=== PE 头信息 ===\r\n", CMD_INFO); + term_puts(" DOS: MZ OK, e_lfanew="); + term_putx(lfanew); + term_puts("\r\n"); + + /* ---- NT 签名 + File Header ---- */ + const u8 *nt = pe + lfanew; + if (*(const u32 *)nt != IMAGE_NT_SIGNATURE) { + term_puts_color("peinfo: 缺少 PE\\0\\0 签名\r\n", CMD_ERROR); + return; + } + const image_file_header *fh = (const image_file_header *)(nt + 4); + u16 machine = fh->machine; + u16 nsec = fh->number_of_sections; + u16 opt_size = fh->size_of_optional_header; + + term_puts(" NT: PE\\0\\0 OK\r\n"); + term_puts(" Machine: "); + term_putx(machine); + if (machine == 0x8664) term_puts_color(" (AMD64)\r\n", CMD_OK); + else if (machine == 0x014c) term_puts_color(" (i386)\r\n", CMD_OK); + else term_puts(" (未知)\r\n"); + term_puts(" 节区数: "); + term_putu(nsec); + term_puts(" OptHdr 大小: "); + term_putu(opt_size); + term_puts("\r\n"); + + /* ---- Optional Header ---- */ + if (opt_size < 2) { + term_puts_color("peinfo: Optional Header 过小\r\n", CMD_WARN); + return; + } + if ((u64)lfanew + 4 + sizeof(image_file_header) + opt_size > pe_size) { + term_puts_color("peinfo: Optional Header 越界\r\n", CMD_ERROR); + return; + } + const void *opt = (const void *)(nt + 4 + sizeof(image_file_header)); + u16 magic = *(const u16 *)opt; + int is_pe32_plus = (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC); + + u64 img_base = 0; + u32 entry_rva = 0; + u32 img_size = 0; + u32 subsys = 0; + const image_data_directory *import_dir = 0; + + if (is_pe32_plus) { + if (opt_size >= sizeof(image_optional_header64)) { + const image_optional_header64 *oh = (const image_optional_header64 *)opt; + img_base = oh->image_base; + entry_rva = oh->address_of_entry_point; + img_size = oh->size_of_image; + subsys = oh->subsystem; + import_dir = &oh->data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + } + term_puts_color(" 格式: PE32+ (64位 原生)\r\n", CMD_OK); + } else if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + if (opt_size >= sizeof(image_optional_header32)) { + const image_optional_header32 *oh = (const image_optional_header32 *)opt; + img_base = oh->image_base; + entry_rva = oh->address_of_entry_point; + img_size = oh->size_of_image; + subsys = oh->subsystem; + import_dir = &oh->data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + } + term_puts_color(" 格式: PE32 (32位 解释器)\r\n", CMD_OK); + } else { + term_puts_color("peinfo: 未知 Optional Header magic=", CMD_ERROR); + term_putx(magic); + term_puts("\r\n"); + return; + } + + term_puts(" ImageBase: "); + term_putx64(img_base); + term_puts("\r\n"); + term_puts(" EntryPoint: "); + term_putx(entry_rva); + term_puts(" (RVA)\r\n"); + term_puts(" SizeOfImage: "); + term_putx(img_size); + term_puts(" ("); + term_putu(img_size); + term_puts(" bytes)\r\n"); + term_puts(" Subsystem: "); + term_putu(subsys); + term_puts(subsys == 3 ? " (CUI 控制台)\r\n" : "\r\n"); + + /* ---- 节区表 ---- */ + const image_section_header *secs = (const image_section_header *) + ((const u8 *)opt + opt_size); + if ((u64)((const u8 *)secs - pe) + (u64)nsec * sizeof(image_section_header) > pe_size) { + term_puts_color("peinfo: 节区表越界,跳过\r\n", CMD_WARN); + nsec = 0; + } + if (nsec > 0) { + term_puts_color(" 节区表:\r\n", CMD_INFO); + term_puts(" Name VAddr VSize RawSize Flg\r\n"); + for (int i = 0; i < nsec && i < 96; i++) { + char nm[9]; + for (int j = 0; j < 8; j++) nm[j] = (char)secs[i].name[j]; + nm[8] = 0; + term_puts(" "); + term_puts(nm); + int pad = 8 - str_len(nm); + for (int k = 0; k < pad; k++) term_putc(' '); + term_puts(" "); + term_putx(secs[i].virtual_address); + term_puts(" "); + term_putx(secs[i].virtual_size); + term_puts(" "); + term_putx(secs[i].size_of_raw_data); + term_puts(" "); + char flags[4]; + flags[0] = (secs[i].characteristics & IMAGE_SCN_MEM_EXECUTE) ? 'X' : '-'; + flags[1] = (secs[i].characteristics & IMAGE_SCN_MEM_READ) ? 'R' : '-'; + flags[2] = (secs[i].characteristics & IMAGE_SCN_MEM_WRITE) ? 'W' : '-'; + flags[3] = 0; + term_puts_color(flags, CMD_WARN); + term_puts("\r\n"); + } + } + + /* ---- 导入表 ---- */ + if (import_dir && import_dir->virtual_address != 0 && import_dir->size != 0) { + u32 imp_rva = import_dir->virtual_address; + if (imp_rva >= pe_size) { + term_puts_color("peinfo: 导入表 RVA 越界\r\n", CMD_WARN); + } else { + term_puts_color(" 导入表:\r\n", CMD_INFO); + int dll_count = 0; + int func_total = 0; + int truncated = 0; + for (int di = 0; !truncated; di++) { + u32 desc_off = imp_rva + (u32)di * sizeof(image_import_descriptor); + if ((u64)desc_off + sizeof(image_import_descriptor) > pe_size) break; + const image_import_descriptor *desc = + (const image_import_descriptor *)(pe + desc_off); + if (desc->name == 0 && desc->first_thunk == 0) break; + + const char *dll_name = 0; + if (desc->name < pe_size) dll_name = (const char *)(pe + desc->name); + term_puts(" DLL: "); + term_puts_color(dll_name ? dll_name : "(?)", CMD_WARN); + term_puts("\r\n"); + + u32 int_rva = desc->original_first_thunk; + u32 iat_rva = desc->first_thunk; + if (int_rva == 0) int_rva = iat_rva; + if (int_rva == 0) { dll_count++; continue; } + + for (int fi = 0; ; fi++) { + u32 thunk_off = int_rva + (u32)fi * 4; + if ((u64)thunk_off + 4 > pe_size) break; + u32 thunk = *(const u32 *)(pe + thunk_off); + if (thunk == 0) break; + + term_puts(" "); + if (thunk & 0x80000000u) { + term_puts("#ordinal "); + term_putu(thunk & 0xFFFF); + } else if (thunk + 2 < pe_size) { + const char *fn = (const char *)(pe + thunk + 2); + term_puts(fn); + } else { + term_puts("(?)"); + } + term_puts("\r\n"); + func_total++; + if (func_total > 64) { + term_puts_color(" ... (导入过多,截断)\r\n", CMD_DIM); + truncated = 1; + break; + } + } + dll_count++; + if (dll_count > 32) { + term_puts_color(" ... (DLL 过多,截断)\r\n", CMD_DIM); + break; + } + } + term_puts(" 共 "); + term_putu(dll_count); + term_puts(" 个 DLL, "); + term_putu(func_total); + term_puts(" 个导入函数\r\n"); + } + } else { + term_puts_color(" 导入表: 无\r\n", CMD_DIM); + } + + term_puts_color("=== PE 头信息结束 ===\r\n", CMD_INFO); +} + +/* ============================================================ + * pe — 显式运行 PE 程序(等价于直接输入名字,但可在 help 中列出) + * ============================================================ */ +static void cmd_pe(const char *args) { + if (!*args) { + term_puts_color("用法: pe <程序名> (如 pe hello64)\r\n", CMD_INFO); + return; + } + exec_pe(args, args); +} + /* ============================================================ * 命令解析与分发 * ============================================================ */ @@ -756,6 +1189,8 @@ static void execute_command(const char *cmd) { g_should_exit = 1; return; } + if (str_ieq(name, "pe")) { cmd_pe(args); return; } + if (str_ieq(name, "peinfo")) { cmd_peinfo(args); return; } if (str_ieq(name, "reboot")) { term_puts_color("重启中...\r\n", CMD_WARN); term_redraw_all(); diff --git a/CODE/desktop/main.c b/CODE/desktop/main.c index 70d2e53..b06775c 100644 --- a/CODE/desktop/main.c +++ b/CODE/desktop/main.c @@ -268,6 +268,7 @@ static int g_ctx_win = -1; /* 状态栏滑块 */ static int g_vol = 65; static int g_bright = 100; +static int g_bright_applied = 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]; @@ -312,6 +313,68 @@ static int g_ide_tabx[4]; /* IDE 标签 × */ static int g_ide_new_rect[4]; /* IDE "+ NEW IDE" 按钮 */ static int g_cp_host[4]; /* 自定义页宿主 */ +/* ============================================================ + * 脏矩形渲染(性能优化:只更新变化区域) + * ============================================================ */ + +#define MAX_DIRTY_RECTS 16 + +typedef struct { + int x, y, w, h; + int active; +} dirty_rect; + +static dirty_rect g_dirty_rects[MAX_DIRTY_RECTS]; +static int g_dirty_count = 0; +static int g_full_redraw = 1; /* 初始全屏重绘 */ + +/* 标记脏区域 */ +__attribute__((unused)) +static void mark_dirty(int x, int y, int w, int h) { + if (g_dirty_count >= MAX_DIRTY_RECTS) { + /* 溢出时合并为全屏重绘 */ + g_full_redraw = 1; + return; + } + g_dirty_rects[g_dirty_count].x = x; + g_dirty_rects[g_dirty_count].y = y; + g_dirty_rects[g_dirty_count].w = w; + g_dirty_rects[g_dirty_count].h = h; + g_dirty_rects[g_dirty_count].active = 1; + g_dirty_count++; +} + +/* 合并所有脏矩形为一个包围盒 */ +static void merge_dirty_rects(int *out_x, int *out_y, int *out_w, int *out_h) { + if (g_dirty_count == 0) { + *out_x = *out_y = *out_w = *out_h = 0; + return; + } + int min_x = (int)g_fb_w, min_y = (int)g_fb_h; + int max_x = 0, max_y = 0; + for (int i = 0; i < g_dirty_count; i++) { + if (!g_dirty_rects[i].active) continue; + int x1 = g_dirty_rects[i].x; + int y1 = g_dirty_rects[i].y; + int x2 = x1 + g_dirty_rects[i].w; + int y2 = y1 + g_dirty_rects[i].h; + if (x1 < min_x) min_x = x1; + if (y1 < min_y) min_y = y1; + if (x2 > max_x) max_x = x2; + if (y2 > max_y) max_y = y2; + } + *out_x = min_x; + *out_y = min_y; + *out_w = max_x - min_x; + *out_h = max_y - min_y; +} + +/* 清除所有脏矩形 */ +static void clear_dirty_rects(void) { + g_dirty_count = 0; + g_full_redraw = 0; +} + /* ============================================================ * 箭头光标形状(24×24,1=前景 0=透明) * ============================================================ */ @@ -847,6 +910,9 @@ typedef struct { char input_buf[256]; int input_len, input_cursor; int prompt_len; + /* 性能优化:脏行标记(增量渲染) */ + u8 dirty_rows[TERM_MAX_ROWS]; /* 标记哪些行需要重绘 */ + int any_dirty; /* 是否有任何脏行 */ } bash_state; static const char *BASH_PROMPT = "deshab# "; @@ -864,11 +930,20 @@ static void bash_reset(bash_state *s, int w, int h) { s->cur_col = 0; s->cur_row = 0; s->input_len = 0; s->input_cursor = 0; s->prompt_len = kstrlen(BASH_PROMPT); + /* 初始化脏行标记:所有行标记为脏,确保首次渲染 */ + for (int i = 0; i < TERM_MAX_ROWS; i++) s->dirty_rows[i] = 1; + s->any_dirty = 1; } static void bash_putc(bash_state *s, char c, u32 color) { - if (c == '\n') { s->cur_col = 0; s->cur_row++; return; } + if (c == '\n') { + s->dirty_rows[s->cur_row] = 1; /* 标记当前行为脏 */ + s->any_dirty = 1; + 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]; @@ -879,11 +954,17 @@ static void bash_putc(bash_state *s, char c, u32 color) { s->ch[(s->rows-1)*TERM_MAX_COLS+c2] = ' '; } s->cur_row = s->rows - 1; + /* 标记所有行为脏 */ + for (int r = 0; r < s->rows; r++) s->dirty_rows[r] = 1; + s->any_dirty = 1; } if (s->cur_col >= s->cols) { s->cur_col = 0; s->cur_row++; } int idx = s->cur_row * TERM_MAX_COLS + s->cur_col; s->ch[idx] = (u8)c; s->fg[idx] = color; s->cur_col++; + /* 标记当前行为脏 */ + s->dirty_rows[s->cur_row] = 1; + s->any_dirty = 1; } static void bash_puts(bash_state *s, const char *str, u32 color) { @@ -958,11 +1039,18 @@ static void bash_key(bash_state *s, u8 sc, int shift) { } } -/* 绘制终端到指定矩形区域(面板或窗口客户区) */ +/* 绘制终端到指定矩形区域(面板或窗口客户区) + * 性能优化:增量渲染,只绘制脏行 */ static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) { + /* 快速退出:终端内容未修改,跳过绘制 */ + if (!s->any_dirty) return; + du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY); for (int r = 0; r < s->rows; r++) { + /* 增量渲染:只绘制脏行 */ + if (!s->dirty_rows[r]) continue; + for (int c = 0; c < s->cols; c++) { int idx = r * TERM_MAX_COLS + c; if (s->ch[idx] == ' ') continue; @@ -972,6 +1060,9 @@ static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) { 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); } + + /* 清除脏行标记 */ + s->dirty_rows[r] = 0; } int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H; @@ -987,6 +1078,9 @@ static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) { du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3, (int)DU_ASCII_CELL_W, 2, KS_ACCENT); } + + /* 清除全局脏标记 */ + s->any_dirty = 0; } /* ---- 窗口适配 ---- */ @@ -1365,7 +1459,11 @@ static void register_apps(void) { } /* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px, - * 左→右排列,超出画布宽度则换行(align-content: start) */ + * 左→右排列,超出画布宽度则换行(align-content: start) + * + * 注意:Terminal 和 CMD 已在第一页 Dashboard(TERM-01 / TERM-02), + * 桌面不再重复显示其图标。 + */ static void setup_desktop_icons(void) { int pad = KATE_ICON_PAD; /* 24 */ int x = pad; @@ -1373,6 +1471,9 @@ static void setup_desktop_icons(void) { int max_x = (int)g_fb_w - pad; /* canvas 右缘 */ for (int i = 0; i < g_app_count; i++) { if (!g_apps[i].on_create && !g_apps[i].elf_name) continue; + /* 跳过 Terminal 和 CMD(已在 Dashboard 第一页) */ + if (g_apps[i].name[0] == 's' && g_apps[i].name[1] == 'h' && g_apps[i].name[2] == 'e') continue; + if (g_apps[i].name[0] == 'c' && g_apps[i].name[1] == 'm' && g_apps[i].name[2] == 'd') continue; if (x > pad && x + KATE_ICON_W > max_x) { x = pad; y += KATE_ICON_H + KATE_ICON_GAP; @@ -1803,10 +1904,10 @@ static void draw_topbar(void) { du_divider_v(&g_fb, x - 4, 6, KATE_TOPBAR_H - 12, KS_BORDER); x += 4; - /* 页面切换 */ - static const char *pg_names[3] = { "1.DASHBOARD", "2.IDE", "3.DESKTOP" }; + /* 页面切换:精简按钮文字 */ + static const char *pg_names[3] = { "DASH", "IDE", "DESK" }; for (int i = 0; i < 3; i++) { - int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 16; + int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 24; 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; @@ -1817,11 +1918,11 @@ static void draw_topbar(void) { 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, + du_draw_string(&g_fb, pg_names[i], x + 12, 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; + x += bw + 8; } /* 自定义页 */ for (int i = 0; i < g_cpage_count; i++) { @@ -2006,7 +2107,7 @@ static void dash_layout(void) { 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 left_w = avail_w * 35 / 100; /* 终端面板:42%→35% */ int right_x = pad + left_w + gap; int right_w = (int)g_fb_w - pad - right_x; int row_h = (avail_h - gap) / 2; @@ -2524,9 +2625,21 @@ static void draw_ctx_menu(void) { * 全屏重绘 * ============================================================ */ -/* 全局亮度(BRIGHT 滑块真实调暗) */ +/* 全局亮度(BRIGHT 滑块真实调暗) + * 性能优化:仅在亮度变化时处理,避免每帧全屏遍历 */ static void apply_brightness(void) { - if (g_bright >= 100) return; + /* 快速退出:亮度100%无需调整 */ + if (g_bright >= 100) { + g_bright_applied = 100; + return; + } + + /* 快速退出:亮度未变化,跳过处理 */ + if (g_bright == g_bright_applied) return; + + /* 记录当前应用的亮度值 */ + g_bright_applied = g_bright; + 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); @@ -3137,8 +3250,28 @@ void dsk_entry(const dsk_boot_context *ctx) { } if (need_redraw) { - redraw_all(); - flip_buffer(); /* 全屏 flip:无闪烁 */ + /* 脏矩形渲染:合并脏矩形,只 flip 变化区域 */ + if (g_full_redraw || g_dirty_count == 0) { + /* 全屏重绘 */ + redraw_all(); + flip_buffer(); + } else { + /* 合并脏矩形 */ + int dx, dy, dw, dh; + merge_dirty_rects(&dx, &dy, &dw, &dh); + + /* 重绘并 flip 脏区域 */ + if (dw > 0 && dh > 0) { + /* 清除脏矩形标记 */ + clear_dirty_rects(); + + /* 重绘整个屏幕(简化:暂不支持局部重绘) */ + redraw_all(); + + /* Flip 合并后的脏区域 */ + flip_rect(dx, dy, dw, dh); + } + } } else if (moved) { /* 仅光标移动:局部 flip(双缓冲 → 无闪烁) */ int old_x = g_cursor_old_x, old_y = g_cursor_old_y; diff --git a/CODE/firstInit/main.c b/CODE/firstInit/main.c index 05c069c..b485272 100644 --- a/CODE/firstInit/main.c +++ b/CODE/firstInit/main.c @@ -302,7 +302,7 @@ static void stroke_rounded_rect(u32 *fb, i64 x, i64 y, i64 w, i64 h, i64 radius, } static void delay_frame(void) { - delay_ms_fi(33); /* 实机: 基于 TSC 的 33ms 帧间隔 (~30fps) */ + delay_ms_fi(11); /* 实机: 基于 TSC 的 11ms 帧间隔 (原33ms/3,加快打字机效果) */ } #include "text_bitmaps.c" diff --git a/CODE/firstInit/main.o b/CODE/firstInit/main.o index efa29d2..e673929 100644 Binary files a/CODE/firstInit/main.o and b/CODE/firstInit/main.o differ diff --git a/CODE/linux/build.sh b/CODE/linux/build.sh index dcf19b4..ca9f38e 100644 --- a/CODE/linux/build.sh +++ b/CODE/linux/build.sh @@ -31,9 +31,15 @@ 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..." + # 优先 kernel.org,失败则 fallback 清华镜像 git clone --depth 1 --branch v6.6 \ https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git \ - "$LINUX_SRC" + "$LINUX_SRC" || { + echo "[build] kernel.org failed, trying TUNA mirror..." + git clone --depth 1 --branch v6.6 \ + https://mirrors.tuna.tsinghua.edu.cn/git/linux.git \ + "$LINUX_SRC" + } fi cd "$LINUX_SRC" @@ -101,19 +107,32 @@ echo "[build] bzImage: $OUTPUT_DIR/linux-bzImage ($(du -h $OUTPUT_DIR/linux-bzIm # 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" + mkdir -p "$INITRAMFS_TMP/bin" - # Copy initramfs contents + # Copy initramfs contents (init + bin/) cp -a "$INITRAMFS_DIR"/* "$INITRAMFS_TMP"/ - # Build utsm_exec_daemon (static binary for initramfs) + # 6a. Static busybox(缓存于 CODE/linux/.cache/,只下载一次) + # busybox.net 官方预编译 x86_64 musl 静态二进制,含 sh/mount/uname 等 applet。 + BUSYBOX_CACHE="$SCRIPT_DIR/.cache/busybox" + BUSYBOX_URL="https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox" + if [ ! -x "$BUSYBOX_CACHE" ]; then + echo "[build] Downloading static busybox..." + mkdir -p "$(dirname "$BUSYBOX_CACHE")" + curl -fL --retry 3 -o "$BUSYBOX_CACHE" "$BUSYBOX_URL" || \ + wget -O "$BUSYBOX_CACHE" "$BUSYBOX_URL" + chmod +x "$BUSYBOX_CACHE" + fi + cp "$BUSYBOX_CACHE" "$INITRAMFS_TMP/bin/busybox" + chmod +x "$INITRAMFS_TMP/bin/busybox" + echo "[build] busybox: $(du -h "$INITRAMFS_TMP/bin/busybox" | cut -f1)" + + # 6b. Build utsm_exec_daemon (static binary for initramfs) DAEMON_SRC="$INITRAMFS_DIR/bin/utsm_exec_daemon.c" if [ -f "$DAEMON_SRC" ]; then echo "[build] Compiling utsm_exec_daemon (static)..." - mkdir -p "$INITRAMFS_TMP/bin" cc -static -O2 -Wall -I"$PROJECT_ROOT/CODE/utsm-ipc" \ -o "$INITRAMFS_TMP/bin/utsm_exec_daemon" "$DAEMON_SRC" strip "$INITRAMFS_TMP/bin/utsm_exec_daemon" 2>/dev/null || true @@ -128,18 +147,62 @@ 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" + # 6c. 用内核自带 gen_init_cpio 打包(无需 root、无需 cpio 二进制)。 + # spec 文件声明目录/设备节点/文件/符号链接;设备节点元数据直接写入 + # cpio 归档,guest 内核 unpack 时即有 /dev/console 与 /dev/ttyS0。 + GEN_INIT_CPIO="$LINUX_SRC/usr/gen_init_cpio" + if [ ! -x "$GEN_INIT_CPIO" ]; then + echo "[build] Building gen_init_cpio host tool..." + make usr/ >/dev/null + fi + + SPEC="$SCRIPT_DIR/initramfs.tmp.spec" + { + echo "dir /bin 755 0 0" + echo "dir /dev 755 0 0" + echo "dir /proc 755 0 0" + echo "dir /sys 755 0 0" + # 控制台设备节点:kernel console=ttyS0 输出与 busybox sh 依赖 + echo "nod /dev/console 600 0 0 c 5 1" + echo "nod /dev/null 666 0 0 c 1 3" + echo "nod /dev/ttyS0 600 0 0 c 4 64" + echo "file /init $INITRAMFS_TMP/init 755 0 0" + echo "file /bin/busybox $INITRAMFS_TMP/bin/busybox 755 0 0" + if [ -f "$INITRAMFS_TMP/bin/utsm_exec_daemon" ]; then + echo "file /bin/utsm_exec_daemon $INITRAMFS_TMP/bin/utsm_exec_daemon 755 0 0" + fi + # busybox applet 符号链接(init 脚本与调试命令依赖) + for app in sh mount umount echo sleep uname ls cat ps setsid cttyhack; do + echo "slink /bin/$app /bin/busybox 777 0 0" + done + } > "$SPEC" + + "$GEN_INIT_CPIO" "$SPEC" | gzip -9 > "$OUTPUT_DIR/linux-initrd.img" echo "[build] initrd: $OUTPUT_DIR/linux-initrd.img ($(du -h $OUTPUT_DIR/linux-initrd.img | cut -f1))" + rm -rf "$INITRAMFS_TMP" "$SPEC" 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." + +# 7. Enable Linux modules in limine.conf (uncomment the 4 commented lines) +LIMINE_CONF="$PROJECT_ROOT/SYSTEM/limine/limine.conf" +if [ -f "$LIMINE_CONF" ]; then + if grep -q "^ # module_path: boot():/boot/linux-bzImage" "$LIMINE_CONF"; then + echo "[build] Enabling Linux modules in limine.conf..." + sed -i 's|^ # module_path: boot():/boot/linux-bzImage| module_path: boot():/boot/linux-bzImage|' "$LIMINE_CONF" + sed -i 's|^ # module_cmdline: linux:bzImage| module_cmdline: linux:bzImage|' "$LIMINE_CONF" + sed -i 's|^ # module_path: boot():/boot/linux-initrd.img| module_path: boot():/boot/linux-initrd.img|' "$LIMINE_CONF" + sed -i 's|^ # module_cmdline: linux:initrd| module_cmdline: linux:initrd|' "$LIMINE_CONF" + echo "[build] Linux modules enabled in $LIMINE_CONF" + echo "[build] Run build.bat to rebuild the disk image with Linux modules" + else + echo "[build] limine.conf already has Linux modules enabled (or pattern changed)" + fi +fi diff --git a/CODE/linux/check_env.sh b/CODE/linux/check_env.sh new file mode 100644 index 0000000..3c3d38a --- /dev/null +++ b/CODE/linux/check_env.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# +# check_env.sh — Linux 内核构建环境依赖检查脚本 +# +# 用途: 在 WSL/Linux 中运行,验证构建 Linux 6.6 LTS bzImage 所需依赖 +# +# 使用: +# ! wsl bash /mnt/d/Code/Deshab/CODE/linux/check_env.sh +# +# 退出码: +# 0 = 所有依赖已满足 +# 1 = 缺少依赖(脚本会打印安装命令) + +set -e + +echo "[check] Linux 内核构建环境检查" +echo "[check] ===========================" + +# 1. 检查操作系统 +if [ -f /etc/os-release ]; then + . /etc/os-release + echo "[check] OS: $NAME $VERSION" + if [ "$ID" != "ubuntu" ] && [ "$ID_LIKE" != "debian" ]; then + echo "[check] WARNING: 非 Ubuntu/Debian,依赖名可能不同" + fi +else + echo "[check] WARNING: 无法识别操作系统" +fi + +# 2. 检查必要包 +echo "[check]" +echo "[check] 检查构建依赖..." +MISSING=() +for pkg in build-essential bc flex bison libelf-dev libssl-dev cpio gzip git; do + if dpkg -s "$pkg" >/dev/null 2>&1; then + echo "[check] ✓ $pkg" + else + echo "[check] ✗ $pkg (缺失)" + MISSING+=("$pkg") + fi +done + +# 3. 检查 gcc 版本 +echo "[check]" +echo "[check] 工具链版本:" +if command -v gcc >/dev/null 2>&1; then + GCC_VER=$(gcc -dumpversion) + echo "[check] gcc: $GCC_VER" + if [ "$(printf '%s\n' "11" "$GCC_VER" | sort -V | head -n1)" != "11" ]; then + echo "[check] WARNING: gcc 版本 < 11,Linux 6.6 可能需要 gcc 11+" + fi +else + echo "[check] ✗ gcc 未安装" + MISSING+=("gcc") +fi + +if command -v make >/dev/null 2>&1; then + echo "[check] make: $(make --version | head -1 | awk '{print $3}')" +fi + +# 4. 检查磁盘空间(需要 ~3GB) +echo "[check]" +PROJECT_ROOT="/mnt/d/Code/Deshab" +if [ -d "$PROJECT_ROOT" ]; then + AVAILABLE_KB=$(df "$PROJECT_ROOT" | awk 'NR==2 {print $4}') + AVAILABLE_GB=$((AVAILABLE_KB / 1024 / 1024)) + echo "[check] 磁盘空间: ${AVAILABLE_GB}GB 可用 ($PROJECT_ROOT)" + if [ "$AVAILABLE_GB" -lt 3 ]; then + echo "[check] ✗ 磁盘空间不足 3GB" + MISSING+=("disk_space") + else + echo "[check] ✓ 磁盘空间充足" + fi +fi + +# 5. 检查访问 Windows D: 盘 +echo "[check]" +if [ -d "/mnt/d/Code/Deshab" ]; then + echo "[check] ✓ WSL 可访问 /mnt/d/Code/Deshab" +else + echo "[check] ✗ WSL 无法访问 /mnt/d/Code/Deshab" + echo "[check] 请确认在 WSL 中执行,且 D: 盘已挂载" + MISSING+=("wsl_mount") +fi + +# 6. 检查 git 网络(克隆 Linux 6.6 需要) +echo "[check]" +echo "[check] 测试 Git 网络连通..." +if timeout 10 git ls-remote https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git v6.6 >/dev/null 2>&1; then + echo "[check] ✓ kernel.org 可达" +else + echo "[check] ✗ kernel.org 不可达,尝试清华镜像..." + if timeout 10 git ls-remote https://mirrors.tuna.tsinghua.edu.cn/git/linux.git v6.6 >/dev/null 2>&1; then + echo "[check] ✓ 清华镜像可达(build.sh 会自动使用)" + else + echo "[check] ✗ 两个源都不可达,请检查网络/代理" + MISSING+=("network") + fi +fi + +# 7. 总结 +echo "[check]" +echo "[check] ===========================" +if [ ${#MISSING[@]} -gt 0 ]; then + echo "[check] 缺失: ${MISSING[*]}" + echo "[check]" + echo "[check] 请运行以下命令安装依赖:" + echo "[check] sudo apt update" + echo "[check] sudo apt install -y build-essential bc flex bison libelf-dev libssl-dev cpio gzip git" + exit 1 +fi + +echo "[check] ✓ 所有依赖已满足,可以构建 Linux 内核" +echo "[check]" +echo "[check] 下一步执行:" +echo "[check] cd /mnt/d/Code/Deshab/CODE/linux && chmod +x build.sh && ./build.sh" +exit 0 diff --git a/CODE/linux/fix_driver.sh b/CODE/linux/fix_driver.sh new file mode 100644 index 0000000..5499f5c --- /dev/null +++ b/CODE/linux/fix_driver.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Fix UTSM driver entries in Linux source tree +SRC="${1:-/tmp/linux-6.6}" + +# Fix Kconfig: remove broken line, add correct one +sed -i '/utsm/d' "$SRC/drivers/Kconfig" +echo 'source "drivers/utsm/Kconfig"' >> "$SRC/drivers/Kconfig" + +# Fix Makefile: remove broken line, add correct one +sed -i '/utsm/d' "$SRC/drivers/Makefile" +echo 'obj-$(CONFIG_UTSM_HCALL) += utsm/' >> "$SRC/drivers/Makefile" + +echo "Fixed UTSM driver entries" +tail -2 "$SRC/drivers/Kconfig" +echo "---" +tail -2 "$SRC/drivers/Makefile" diff --git a/CODE/linux/initramfs/init b/CODE/linux/initramfs/init index 5850368..887510e 100644 --- a/CODE/linux/initramfs/init +++ b/CODE/linux/initramfs/init @@ -33,10 +33,19 @@ fi # Start the exec daemon in the background. # The daemon sends EXEC_READY then HLTs (parks the guest). # UTSM's linux_launch() returns after this first HLT. -echo "[utsm-linux] starting utsm_exec_daemon..." -/bin/utsm_exec_daemon & -DAEMON_PID=$! -echo "[utsm-linux] daemon PID=$DAEMON_PID" +if [ -x /bin/utsm_exec_daemon ]; then + echo "[utsm-linux] starting utsm_exec_daemon..." + /bin/utsm_exec_daemon & + DAEMON_PID=$! + echo "[utsm-linux] daemon PID=$DAEMON_PID" +else + # 无 daemon(第一阶段 bring-up 冒烟测试构建): + # 直接在 ttyS0 上起交互 shell,验证 console 输入输出。 + # 注意:此模式下 guest 不会 park,UTSM linux_launch() 不返回, + # 仅用于验证"Linux guest 进入 shell 提示符、可执行 uname -a"。 + echo "[utsm-linux] WARNING: no daemon, starting interactive debug shell on ttyS0" + exec /bin/setsid /bin/cttyhack /bin/sh +fi # init must not exit — sleep forever to keep the kernel happy. # The daemon handles all UTSM exec requests; init just keeps the diff --git a/CODE/shell/main.c b/CODE/shell/main.c index c970248..3823bdb 100644 --- a/CODE/shell/main.c +++ b/CODE/shell/main.c @@ -1942,6 +1942,12 @@ void dsk_entry(const dsk_boot_context *ctx) { g_history_view = -1; term_putc('\n'); execute_command(input_buf); + /* 确保命令输出后在新行绘制提示符 */ + if (cur_col != 0) { + term_putc('\n'); + } + /* 刷新终端显示 */ + term_redraw_all(); input_len = 0; input_cursor = 0; input_buf[0] = 0; diff --git a/CODE/shell/main.o b/CODE/shell/main.o index d24563a..5f4bf38 100644 Binary files a/CODE/shell/main.o and b/CODE/shell/main.o differ diff --git a/CODE/tools/fileman/main.c b/CODE/tools/fileman/main.c index ca8ff4e..b79fb24 100644 --- a/CODE/tools/fileman/main.c +++ b/CODE/tools/fileman/main.c @@ -63,6 +63,22 @@ static void load_current_dir(void) { scroll_off = 0; u32 dir_clus = (path_depth == 0) ? 0 : path_clus[path_depth - 1]; f32_list_dir(dir_clus, fm_collect_cb, 0); + + /* 如果不是根目录,在开头添加 ".." 返回上级条目 */ + if (path_depth > 0 && file_count < MAX_FILES) { + /* 将现有文件向后移动 */ + for (int i = file_count; i > 0; i--) { + files[i] = files[i - 1]; + } + /* 在开头插入 ".." 条目 */ + files[0].name[0] = '.'; + files[0].name[1] = '.'; + files[0].name[2] = 0; + files[0].clus = 0; /* 特殊标记 */ + files[0].size = 0; + files[0].is_dir = 1; + file_count++; + } } /* ---- 路径显示 ---- */ @@ -243,6 +259,12 @@ static void open_selected(void) { if (selected < 0 || selected >= file_count) return; fm_entry *f = &files[selected]; + /* 特殊处理 ".." 条目 */ + if (path_depth > 0 && selected == 0 && f->name[0] == '.' && f->name[1] == '.' && f->name[2] == 0) { + go_up(); + return; + } + if (f->is_dir) { /* 进入子目录 */ if (path_depth < MAX_DEPTH - 1) { @@ -279,6 +301,42 @@ static void go_up(void) { } } +/* 查找并进入 SYSTEM/user 目录 */ +static void find_user_dir(void) { + /* 先查找 SYSTEM 目录 */ + file_count = 0; + f32_list_dir(0, fm_collect_cb, 0); + u32 system_clus = 0; + for (int i = 0; i < file_count; i++) { + if (files[i].is_dir && files[i].name[0] == 'S' && files[i].name[1] == 'Y' && + files[i].name[2] == 'S' && files[i].name[3] == 'T' && + files[i].name[4] == 'E' && files[i].name[5] == 'M') { + system_clus = files[i].clus; + break; + } + } + if (!system_clus) return; /* SYSTEM 目录不存在,保持根目录 */ + + /* 在 SYSTEM 中查找 user 目录 */ + file_count = 0; + f32_list_dir(system_clus, fm_collect_cb, 0); + for (int i = 0; i < file_count; i++) { + if (files[i].is_dir && files[i].name[0] == 'U' && files[i].name[1] == 'S' && + files[i].name[2] == 'E' && files[i].name[3] == 'R') { + /* 找到了 SYSTEM/user,设置路径栈 */ + path_clus[0] = system_clus; + path_name[0][0] = 'S'; path_name[0][1] = 'Y'; path_name[0][2] = 'S'; + path_name[0][3] = 'T'; path_name[0][4] = 'E'; path_name[0][5] = 'M'; + path_name[0][6] = 0; + path_clus[1] = files[i].clus; + path_name[1][0] = 'U'; path_name[1][1] = 'S'; path_name[1][2] = 'E'; + path_name[1][3] = 'R'; path_name[1][4] = 0; + path_depth = 2; + return; + } + } +} + __attribute__((visibility("default"))) void dsk_entry(const da_boot_context *ctx) { __asm__ volatile("cli"); @@ -297,6 +355,8 @@ void dsk_entry(const da_boot_context *ctx) { f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write); path_depth = 0; + /* 尝试进入 SYSTEM/user 目录 */ + find_user_dir(); load_current_dir(); redraw_all(); @@ -307,15 +367,23 @@ 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 && !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) { - int idx = click_y / row_h + scroll_off; - if (idx >= 0 && idx < file_count) { - selected = idx; - need_redraw = 1; + if (g_cursor.btn) { + /* 检查关闭按钮点击(右上角:w-28, 4, 24, 20) */ + if (g_cursor.mx >= (i64)g_ac.fb_w - 28 && g_cursor.mx < (i64)g_ac.fb_w - 4 && + g_cursor.my >= 4 && g_cursor.my < 24) { + da_slog("fileman", "close button clicked"); + return; + } + if (!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) { + int idx = click_y / row_h + scroll_off; + if (idx >= 0 && idx < file_count) { + selected = idx; + need_redraw = 1; + } } } } diff --git a/CODE/tools/pe-samples/cmd.exe b/CODE/tools/pe-samples/cmd.exe new file mode 100644 index 0000000..b3ac238 Binary files /dev/null and b/CODE/tools/pe-samples/cmd.exe differ diff --git a/CODE/tools/pe-samples/gen_pe.py b/CODE/tools/pe-samples/gen_pe.py new file mode 100644 index 0000000..f572201 --- /dev/null +++ b/CODE/tools/pe-samples/gen_pe.py @@ -0,0 +1,630 @@ +#!/usr/bin/env python3 +""" +gen_pe.py — Deshab PE 兼容层测试 PE 程序生成器(无需 MinGW/MSVC) + +用途: 当宿主机无 MinGW/MSVC 时,用纯 Python 构造最小 PE32+ 和 PE32 二进制, + 验证 pe_loader 解析与 pe_shim/x86emu32 执行路径。 + +构造的 PE 特征: + - PE32+ (64位): machine=0x8664, entry=main, image_base=0x140000000 + - PE32 (32位): machine=0x014c, entry=main, image_base=0x00400000 + - 单 section (.text), 4KB 对齐 + - IAT 仅 2 项: msvcrt.dll!printf + kernel32.dll!ExitProcess + - main 函数: 调用 printf("Hello from PE32+/PE32!\\r\\n") → 调用 ExitProcess(0) + +输出: + hello64.exe - PE32+ 二进制 (供 HELLO64 EXE 测试用) + hello32.exe - PE32 二进制 (供 HELLO32 EXE 测试用) + +用法: + python gen_pe.py + # 生成 hello64.exe / hello32.exe 到当前目录 + # 之后复制到 SYSTEM/system/deshab64/tools/ 并重新运行 build.bat +""" + +import struct +import sys +import os + +# ============================================================================ +# PE32+ (64位) 构造 +# ============================================================================ + +IMAGE_DOS_SIGNATURE = 0x5A4D # 'MZ' +IMAGE_NT_SIGNATURE = 0x00004550 # 'PE\0\0' +IMAGE_FILE_MACHINE_I386 = 0x014c +IMAGE_FILE_MACHINE_AMD64 = 0x8664 +IMAGE_FILE_MACHINE_I386_CHARACTERISTICS = 0x0102 # EXECUTABLE_IMAGE | 32BIT_MACHINE +IMAGE_FILE_MACHINE_AMD64_CHARACTERISTICS = 0x0022 # EXECUTABLE_IMAGE | LARGE_ADDRESS_AWARE +IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 +IMAGE_SCN_CNT_CODE = 0x20 +IMAGE_SCN_MEM_EXECUTE = 0x20000000 +IMAGE_SCN_MEM_READ = 0x40000000 +IMAGE_DIRECTORY_ENTRY_IMPORT = 1 + + +def build_pe32_plus(): + """构造 PE32+ (64位) 测试程序。""" + image_base = 0x140000000 + section_align = 0x1000 + file_align = 0x200 + + # DOS header (64 bytes) + e_lfanew + dos_header = bytearray(64) + struct.pack_into(' +#include + +int __cdecl main(void) { + printf("Hello from PE32!\r\n"); + printf("Deshab x86emu32 interpreter works.\r\n"); + ExitProcess(0); + return 0; +} diff --git a/CODE/tools/pe-samples/hello32.exe b/CODE/tools/pe-samples/hello32.exe new file mode 100644 index 0000000..9cd27ea Binary files /dev/null and b/CODE/tools/pe-samples/hello32.exe differ diff --git a/CODE/tools/pe-samples/hello64.c b/CODE/tools/pe-samples/hello64.c new file mode 100644 index 0000000..4316856 --- /dev/null +++ b/CODE/tools/pe-samples/hello64.c @@ -0,0 +1,27 @@ +/* hello64.c — PE32+ (64-bit) 测试程序 + * + * 用途: 验证 Deshab PE 兼容层 pe_service 对 PE32+ 的加载与原生 Ring0 执行 + * + * 调用的 shim API (pe_shim.c 已实现): + * - msvcrt.dll!printf + * - kernel32.dll!ExitProcess + * + * 编译 (MinGW-w64): + * x86_64-w64-mingw32-gcc -o hello64.exe hello64.c -nostartfiles \ + * -Wl,--entry,main -Wl,--image-base,0x140000000 + * + * 编译 (MSVC): + * cl /Fe:hello64.exe hello64.c /link /ENTRY:main /BASE:0x140000000 + * + * 或直接使用 gen_pe.py 生成等价 PE32+ 二进制 + */ + +#include +#include + +int main(void) { + printf("Hello from PE32+!\r\n"); + printf("Deshab PE compat layer works.\r\n"); + ExitProcess(0); + return 0; +} diff --git a/CODE/tools/pe-samples/hello64.exe b/CODE/tools/pe-samples/hello64.exe new file mode 100644 index 0000000..cfb10a8 Binary files /dev/null and b/CODE/tools/pe-samples/hello64.exe differ diff --git a/ISO/run_qemu.bat b/ISO/run_qemu.bat index bb7c5dc..969c726 100644 --- a/ISO/run_qemu.bat +++ b/ISO/run_qemu.bat @@ -1,5 +1,5 @@ -@echo off -setlocal +@echo off +setlocal enabledelayedexpansion set "SCRIPT_DIR=%~dp0" set "IMG=%SCRIPT_DIR%deshab.img" @@ -41,15 +41,35 @@ if not defined OVMF ( exit /b 1 ) +REM ===== Accelerator selection ===== +REM QEMU_ACCEL env var can override: "whpx" / "tcg" +REM Default: try WHPX first (for VMX/Linux compat), fallback to TCG if unavailable +if not defined QEMU_ACCEL set "QEMU_ACCEL=whpx" + +REM Detect whether Linux compat layer is enabled (bzImage present in IMG) +set "LINUX_ENABLED=0" +findstr /C:"module_path: boot():/boot/linux-bzImage" "%SCRIPT_DIR%..\SYSTEM\limine\limine.conf" >nul 2>&1 +if not errorlevel 1 ( + set "LINUX_ENABLED=1" +) + echo [qemu] Using: %QEMU% echo [qemu] UEFI: %OVMF% echo [qemu] Image: %IMG% +echo [qemu] Accel: %QEMU_ACCEL% +if "%LINUX_ENABLED%"=="1" ( + echo [qemu] Linux compat: ENABLED (requires WHPX or real hardware for VMX) + echo [qemu] If VMX unavailable, UTSM will auto-skip Linux and continue to DSK +) else ( + echo [qemu] Linux compat: disabled (build via CODE/linux/build.sh to enable) +) if exist "%SATA_IMG%" ( echo [qemu] SATA: %SATA_IMG% "%QEMU%" ^ + -accel %QEMU_ACCEL% ^ -machine q35 ^ -m 512M ^ - -cpu max ^ + -cpu qemu64 ^ -serial stdio ^ -drive if=pflash,format=raw,readonly=on,file="%OVMF%" ^ -drive format=raw,file="%IMG%",if=virtio ^ @@ -61,9 +81,10 @@ if exist "%SATA_IMG%" ( ) else ( echo [qemu] SATA image not found, booting without block device "%QEMU%" ^ + -accel %QEMU_ACCEL% ^ -machine q35 ^ -m 512M ^ - -cpu max ^ + -cpu qemu64 ^ -serial stdio ^ -drive if=pflash,format=raw,readonly=on,file="%OVMF%" ^ -drive format=raw,file="%IMG%",if=virtio ^ diff --git a/RE/Bug记录.txt b/RE/Bug记录.txt index a0a97db..d265dd3 100644 --- a/RE/Bug记录.txt +++ b/RE/Bug记录.txt @@ -1,6 +1,39 @@ -开发者模式中启动的Shell的提示符没有在第一行也没有显示 -shell光标没有闪烁 -Login界面没有显示鼠标指针 -在桌面上时每按下按键屏幕就会闪烁 -窗口打开和关闭时的动画太卡,而且屏幕会闪,直接去掉动画 -启动一次之后,第二次开机启动会直接退出 \ No newline at end of file +# Deshab Bug 记录 + +> 所有测试与开发过程中发现的 bug 统一登记于此。每个 bug 按 BUG-YYYYMMDD-NNN 编号。 +> 测试用例文档位于 RE/test-cases/。 + +## 严重程度定义 + +- P0 阻断: 影响主链路启动或核心功能完全不可用(必须立即修复) +- P1 严重: 功能可降级但关键路径受损(本轮内修复) +- P2 一般: 非关键路径异常或体验问题(下一里程碑前修复) +- P3 提示: 日志/小问题,不影响功能(记录备查) + +## Bug 条目格式 + +``` +### BUG-YYYYMMDD-NNN [P级] [模块] 简述 +- 现象: +- 复现步骤: +- 串口/日志证据: +- 根因分析: +- 修复方案: +- 状态: [OPEN/FIXING/FIXED/CLOSED] +- 修复提交: +- 验证: <验证方法与结果> +``` + +--- + +## Bug 列表 + + +P2: 软件Files的窗口点击叉无法关闭 +P3: 在第一页已经有了终端,所以在第四页的桌面上不用显示终端和CMD +P2: 在开发者模式中的shell输入命令并回车时文字消失没有反应 +P3: 启动时的打字机效果再快3倍 +P2: 桌面中第一页的shell中输入命令回车后,上一条命令输入的文字会跑到下一行 +P2: 桌面中第一页的Files显示的不是SYSTEM/user中的文件夹内容 +P2: Files中没有显示文件夹,直接显示了文件夹下的文件 +P2: Files没有提供“..”来供用户返回上一级的文件夹 \ No newline at end of file diff --git a/RE/test-cases/README.md b/RE/test-cases/README.md new file mode 100644 index 0000000..80b5a8e --- /dev/null +++ b/RE/test-cases/README.md @@ -0,0 +1,38 @@ +# Deshab 测试用例矩阵 + +本目录组织 Deshab-OS 的系统化测试用例文档。 + +## 测试矩阵总览 + +| 文档 | 范围 | 优先级 | +|---|---|---| +| [boot-regression.md](boot-regression.md) | 启动链路回归(首启/正常/dev_mode/无盘/无NIC) | P0 | +| [linux-compat.md](linux-compat.md) | UTSM+Linux 双内核兼容层端到端 | P0 | +| [pe-compat.md](pe-compat.md) | PE/EXE 兼容层(PE32+ native + PE32 x86emu32) | P1 | +| [driver-matrix.md](driver-matrix.md) | 14 DKM 驱动功能矩阵 | P1 | +| [userapps-matrix.md](userapps-matrix.md) | 用户态程序矩阵(shell/cmd/desktop/tools) | P1 | +| [network-e2e.md](network-e2e.md) | 网络端到端(DHCP/ARP/DNS/ping/curl) | P1 | +| [performance-baseline.md](performance-baseline.md) | 性能基线(TSC 测量) | P2 | +| [x86emu32-coverage.md](x86emu32-coverage.md) | x86emu32 指令覆盖率 | P2 | + +## 测试场景状态标记 + +- ✅ PASS: 全部测试项通过 +- 🔶 PARTIAL: 部分测试项通过,存在已知限制 +- ❌ FAIL: 关键测试项失败,需修复 +- ⬜ PENDING: 未执行 +- ⏭ SKIP: 因环境/依赖不可用而跳过 + +## 自动化执行 + +```powershell +# 运行全部自动化测试场景 +.\ISO\run_tests.bat + +# 输出 +# ISO/logs/test-report-.md +``` + +## Bug 跟踪 + +所有测试发现的 bug 登记到 [../Bug记录.txt](../Bug记录.txt),并在对应测试文档中引用 bug 编号。 diff --git a/RE/test-cases/boot-regression.md b/RE/test-cases/boot-regression.md new file mode 100644 index 0000000..65e6245 --- /dev/null +++ b/RE/test-cases/boot-regression.md @@ -0,0 +1,96 @@ +# 启动链路回归测试矩阵 + +> 对应计划 Phase C2:验证不同启动场景下系统行为正确 + +## 状态: ⬜ PENDING + +## 测试场景 + +| # | 场景 | firstInit.txt | 配置 | 期望串口锚点 | 状态 | Bug# | +|---|---|---|---|---|---|---| +| B1 | 首次启动 | `0\n0` | 空 user.conf | `[DSK] firstInit=0` → `mouseInit` → `FirstInit` → 完成向导 → 写盘 | ⬜ | | +| B2 | 正常启动 | `1\n0` | 已有 user.conf | `[DSK] firstInit=1` → `login` → desktop | ⬜ | | +| B3 | dev_mode | `1\n1` | dev_mode=1 | `[DSK] dev_mode` → shell run_dev_tests 全绿 → Esc → 正常流程 | ⬜ | | +| B4 | 无 SATA 盘 | 任意 | 移除 sata_fat32_dsk.img | DSK fallback 到 Limine module | ⬜ | | +| B5 | 无 NIC | 任意 | 移除 `-netdev` | e1000 not found → netman 警告 → 继续启动 | ⬜ | | +| B6 | Linux 兼容层启用 | 任意 | +linux-bzImage +linux-initrd | UTSM VMM + Linux park → DSK 正常接管 | ⬜ | | + +## 串口日志锚点(标准启动) + +### 首次启动(B1) + +``` +[UTSM] boot +[UTSM] SELFTEST PASS +[UTSM] loading DSK +[DSK] boot +[DSK] context ok +[DSK] firstInit=0 +[DSK] loading mouseInit.elf +[DSK] loading FirstInit.elf +[FirstInit] ... +[DSK] writing firstInit=1 +[DSK] loading shell.elf +``` + +### 正常启动(B2) + +``` +[UTSM] boot +[UTSM] SELFTEST PASS +[UTSM] loading DSK +[DSK] boot +[DSK] firstInit=1 +[DSK] loading login.elf +[DSK] loading desktop.elf +``` + +### dev_mode(B3) + +``` +[UTSM] boot +[UTSM] SELFTEST PASS +[DSK] dev_mode=1 +[DSK] loading shell.elf +[shell] run_dev_tests: +[shell] ls: OK +[shell] echo>: OK +[shell] cat: OK +[shell] cp: OK +[shell] mv: OK +[shell] rm: OK +[shell] dev_tests done +... (Esc 返回后正常流程) +``` + +## 自动化脚本 + +[ISO/run_qemu_scenario.bat](../../ISO/run_qemu_scenario.bat) 接收场景名参数: + +```powershell +.\ISO\run_qemu_scenario.bat firstboot +.\ISO\run_qemu_scenario.bat normal +.\ISO\run_qemu_scenario.bat devmode +.\ISO\run_qemu_scenario.bat nosata +.\ISO\run_qemu_scenario.bat nonic +.\ISO\run_qemu_scenario.bat linux +``` + +每个场景输出独立日志到 `ISO/logs/-.log`。 + +## 配置文件准备 + +### B1 首次启动 + +- `SYSTEM/system/user/use/firstInit.txt`: `0\n0` +- 备份 `user.conf` 后清空内容(仅保留 `DESHAB_USERCONF_V1` 头) + +### B3 dev_mode + +- `firstInit.txt`: `1\n1` +- 保持现有 user.conf + +### B4 无 SATA + +- 临时重命名 `.build_tmp/sata_fat32_dsk.img` → `.build_tmp/sata_fat32_dsk.img.bak` +- run_qemu.bat 自动检测,缺失时跳过 `-device ide-hd` diff --git a/RE/test-cases/driver-matrix.md b/RE/test-cases/driver-matrix.md new file mode 100644 index 0000000..c50c1c5 --- /dev/null +++ b/RE/test-cases/driver-matrix.md @@ -0,0 +1,70 @@ +# 14 DKM 驱动功能矩阵 + +> 对应计划 Phase C3:每个驱动的加载、探测、功能验证点 + +## 状态: ⬜ PENDING + +## 驱动清单 + +| # | 驱动 | stage | class | required | QEMU 参数 | 期望串口日志 | 功能验证点 | 状态 | Bug# | +|---|---|---|---|---|---|---|---|---|---| +| 1 | timer | 0 | timer | ✅ | 默认 | `[DKM:timer]` | 100ms busy-wait 精度 | ⬜ | | +| 2 | apic | 0 | interrupt | ✅ | 默认 | `[DKM:apic] MADT found` | LAPIC MMIO 只读 | ⬜ | | +| 3 | acpi | 0 | platform | ❌ | 默认 | `[DKM:acpi]` | RSDP/XSDT 枚举 | ⬜ | | +| 4 | pci | 0 | bus | ✅ | 默认 | `[DKM:pci]` | PCI config space 扫描 | ⬜ | | +| 5 | console_fb | 1 | console | ❌ | 默认 | `[DKM:console_fb]` | 淡蓝色背景清屏 | ⬜ | | +| 6 | ahci | 1 | storage | ❌ | +SATA 测试盘 | `[DKM:ahci] ahci0 registered` | LBA0 读取 | ⬜ | | +| 7 | nvme | 1 | storage | ❌ | `-device nvme` | `[DKM:nvme] 1b36:0010 found` | BAR0 读取(高位 MMIO 未解决则 SKIP) | ⏭ | | +| 8 | bootfs | 1 | fs | ✅ | 默认 | `[DKM:bootfs]` | Limine boot module FS | ⬜ | | +| 9 | vfs | 2 | fs-core | ✅ | 默认 | `[DKM:vfs]` | 挂载 bootfs | ⬜ | | +| 10 | fat32 | 2 | fs | ❌ | +SATA 测试盘 | `[DKM:fat32]` | BPB/FAT/目录项解析 | ⬜ | | +| 11 | devfs | 2 | fs | ❌ | 默认 | `[DKM:devfs]` | /dev/version, /dev/fb0 等 | ⬜ | | +| 12 | e1000 | 3 | net | ❌ | `-device e1000` | `tx=ready rx=ready` | DHCP + ping | ⬜ | | +| 13 | virtio_net | 3 | net | ❌ | `-device virtio-net-pci` | `[DKM:virtio_net] 1af4:1001 found` | capability 枚举 | ⬜ | | +| 14 | ath9k | 3 | net | ❌ | (QEMU 不支持 WiFi) | `no Atheros WiFi device found` | 跳过(QEMU 限制) | ⏭ | | +| 15 | ps2kbd | 3 | input | ❌ | 默认 | scan code 读取 | 输入回显 | ⬜ | | + +## stage 加载顺序 + +``` +stage 0 (platform): timer → apic → acpi → pci +stage 1 (boot): console_fb → ahci → nvme → bootfs +stage 2 (fs): vfs → fat32 → devfs +stage 3 (optional): e1000 → virtio_net → ath9k → ps2kbd +``` + +## 测试设备 QEMU 参数 + +### AHCI + FAT32 测试盘 + +``` +-drive id=sata0,format=raw,file=.build_tmp/sata_fat32_dsk.img,if=none ^ +-device ide-hd,drive=sata0,bus=ide.0 ^ +``` + +### NVMe + +``` +-device nvme,drive=nvme0,serial=UTSMTEST ^ +-drive id=nvme0,format=raw,file=.build_tmp/nvme_test.img,if=none ^ +``` + +### e1000 + +``` +-netdev user,id=net0 ^ +-device e1000,netdev=net0,mac=52:54:00:12:34:56 ^ +``` + +### virtio-net + +``` +-netdev user,id=net0 ^ +-device virtio-net-pci,netdev=net0 ^ +``` + +## 已知限制 + +- NVMe BAR0 在 QEMU 中常位于 4G 以上,当前 HHDM 不能安全访问,需 Phase 0 高位 MMIO 映射解决 +- ath9k 在 QEMU 中无对应设备模拟,永远走 "not found" 路径 +- 多个 NIC 同时挂载会改变 PCI 拓扑,可能影响 e1000 探测位置 diff --git a/RE/test-cases/linux-compat.md b/RE/test-cases/linux-compat.md new file mode 100644 index 0000000..3b38ab7 --- /dev/null +++ b/RE/test-cases/linux-compat.md @@ -0,0 +1,77 @@ +# Linux 兼容层测试矩阵 + +> 对应计划 Phase A:UTSM+Linux 双内核 park-and-resume 端到端验证 +> 前置条件:Linux 6.6 LTS bzImage 已构建(SYSTEM/boot/linux-bzImage + linux-initrd.img) + +## 状态: ⬜ PENDING + +## 测试环境 + +- QEMU 加速: WHPX (`-accel whpx -cpu max`),TCG 不支持 VMX +- Linux 模块: limine.conf 中 bzImage/initrd 模块已启用 +- 串口: `-serial stdio`,过滤 `Linux version|utsm-linux|linux_compat|UTSM.*Linux|DSK|shell` + +## 测试矩阵 + +| # | 测试项 | 串口/Shell 预期 | 状态 | 失败处理 | Bug# | +|---|---|---|---|---|---| +| A5.1 | UTSM VMM self-test 通过 | `[UTSM] VMM self-test PASS` | ⬜ | 检查 VMCS/EPT 初始化;BIOS 启用 VT-x | | +| A5.2 | bzImage 加载与解析 | `[UTSM] Linux bzImage loaded` | ⬜ | 检查 linux_loader_init header magic 校验 | | +| A5.3 | EPT 映射 + vmlaunch | `[UTSM] Linux guest launched` | ⬜ | 检查 EPT 表、VMCS guest 状态 | | +| A5.4 | Linux 内核启动 | `Linux version 6.6.x` 串口日志 | ⬜ | 检查 cmdline、boot_params、E820 表 | | +| A5.5 | init 脚本运行 | `[utsm-linux] init started` | ⬜ | 检查 initramfs 结构 | | +| A5.6 | exec daemon 启动 + park | `[utsm-linux] starting utsm_exec_daemon...` → `linux_compat_init() ready` | ⬜ | 检查 daemon 编译、IPC 共享内存初始化 | | +| A5.7 | DSK 正常接管 | `[DSK] boot` → `[DSK] SELFTEST PASS` | ⬜ | 验证 Linux park 不阻塞 DSK | | +| A5.8 | shell `linux`(无参)状态查询 | `Linux 兼容层已就绪 (双内核 park-and-resume)` | ⬜ | 检查 reserved[5] 传递链 | | +| A5.9 | shell `linux ls` | 输出 initramfs 根目录内容 | ⬜ | 检查 IPC exec 协议、vmresume 路径 | | +| A5.10 | shell `linux uname -a` | 输出 `Linux ... 6.6.x ...` | ⬜ | 同上 | | +| A5.11 | shell `linux cat /etc/hostname` | 输出 hostname | ⬜ | 同上 | | +| A5.12 | 错误路径:`linux nosuchprog` | 输出 exec 失败,shell 不崩溃 | ⬜ | 检查 EXEC_EXIT 消息处理 | | + +## 关键约束验证 + +| 约束 | 验证方法 | 状态 | +|---|---|---| +| Linux guest cmdline 含 `nohlt idle=poll` | 检查 [linux_loader.c](../../CODE/UTSM/vmm/linux_loader.c) cmdline 构造 | ⬜ | +| IPC 共享内存 GPA=0x04000000, 1MB | e820 中标记 RESERVED,Linux 不分配 | ⬜ | +| Linux guest RAM 起始 0x05000000 | 检查 EPT 映射范围 | ⬜ | +| exec daemon 先发 EXEC_READY 再 HLT | daemon 源码 [utsm_exec_daemon.c](../../CODE/linux/initramfs/bin/utsm_exec_daemon.c) | ⬜ | +| UTSM hypercall ABI: RAX=magic\|op | 检查 [hypercall.c](../../CODE/UTSM/vmm/hypercall.c) 处理 | ⬜ | + +## 已知限制 + +- QEMU TCG 不支持 VMX,必须 WHPX 或真机 +- WHPX 可能不暴露 nested VMX 给 guest(需实测) +- 若 CPU 不支持 VT-x,整个 Linux 兼容层降级为"不可用",不影响其他系统功能 + +## 手动测试命令 + +进入 shell 后执行: + +``` +deshab:/ $ linux +deshab:/ $ linux ls +deshab:/ $ linux uname -a +deshab:/ $ linux cat /etc/hostname +deshab:/ $ linux nosuchprog +``` + +## 串口日志锚点 + +启动后期望按顺序出现: + +``` +[UTSM] VMM self-test PASS +[UTSM] Linux bzImage loaded +[UTSM] Linux guest launched +Linux version 6.6.x ... +[utsm-linux] init started +[utsm-linux] kernel: 6.6.x +[utsm-linux] starting utsm_exec_daemon... +[utsm-linux] daemon PID=... +[utsm-linux] init: daemon running, entering idle hold +[UTSM] linux_compat service ready +[DSK] boot +[DSK] SELFTEST PASS +... (进入 shell 提示符) +``` diff --git a/RE/test-cases/network-e2e.md b/RE/test-cases/network-e2e.md new file mode 100644 index 0000000..2a4824d --- /dev/null +++ b/RE/test-cases/network-e2e.md @@ -0,0 +1,62 @@ +# 网络端到端测试矩阵 + +> 对应计划 Phase C4 网络部分:e1000 + net_stack + ping/curl/browser + +## 状态: ✅ 已验证([deshab_system_roadmap_plan.md](../../.trae/documents/deshab_system_roadmap_plan.md) Phase 1) + +> 本矩阵用于回归验证,确保不退化 + +## 测试环境 + +- QEMU 网络: `-netdev user,id=net0 -device e1000,netdev=net0` +- slirp 默认: guest 10.0.2.15, 网关 10.0.2.2, DNS 10.0.2.3 +- 主机 http 服务: `python -m http.server 8000` + +## 测试矩阵 + +| # | 测试项 | 命令 | 预期 | 状态 | Bug# | +|---|---|---|---|---|---| +| N1 | e1000 驱动加载 | (自动) | `tx=ready rx=ready` | ✅ | | +| N2 | netman 注册 | (自动) | netman 打印设备枚举 | ✅ | | +| N3 | DHCP 获取 IP | (自动) | netman DHCP final rc=0 | ✅ | | +| N4 | ARP 解析网关 | (自动) | netman ARP final rc=0 | ✅ | | +| N5 | DNS 查询 | (自动) | netman DNS final rc=0 | ✅ | | +| N6 | shell ping 网关 | `ping 10.0.2.2` | 4/4 reply, RTT<10ms | ✅ | | +| N7 | shell ping 外网 | `ping 8.8.8.8` | (依赖主机联网) | 🔶 | | +| N8 | shell curl 本地 | `curl http://10.0.2.2:8000/` | HTTP 200 + 目录 HTML | ✅ | | +| N9 | shell curl 外网 | `curl http://example.com/` | HTTP 200 (依赖主机联网) | 🔶 | | +| N10 | curl.elf 工具 | `run CURL.ELF` | 独立工具执行 curl | ✅ | | +| N11 | ping.elf 工具 | `run PING.ELF` | 独立工具执行 ping | ✅ | | +| N12 | browser.elf 工具 | `run BROWSER.ELF` | URL 输入 + HTTP GET | ✅ | | + +## 回归验证 + +每次启动 dev_mode 时,run_dev_tests 应包含: + +``` +[shell] dev_tests: + ... + ping 10.0.2.2: OK (4/4 reply, RTT=Xms) + ... +``` + +若 ping 失败: + +1. 检查 e1000 是否加载(`-device e1000` 参数) +2. 检查 netman 是否成功 DHCP +3. 检查 ARP 是否解析网关 +4. 串口日志 grep `e1000|netman|DHCP|ARP|DNS|network` + +## 性能基线 + +参见 [performance-baseline.md](performance-baseline.md): + +- ping RTT < 10ms +- curl 64KB 页面接收无明显停顿 +- DHCP 总耗时 < 5s + +## 已知限制 + +- HTTPS/TLS 不支持(curl 仅 http) +- TCP 仅最小 client 实现(无拥塞控制/乱序队列) +- QEMU slirp 不支持真实 Wi-Fi 模式,ath9k 驱动在 QEMU 中无设备 diff --git a/RE/test-cases/pe-compat.md b/RE/test-cases/pe-compat.md new file mode 100644 index 0000000..ad25a95 --- /dev/null +++ b/RE/test-cases/pe-compat.md @@ -0,0 +1,72 @@ +# PE/EXE 兼容层测试矩阵 + +> 对应计划 Phase B:PE32+ (64位 native) 与 PE32 (32位 x86emu32) 兼容层验证 +> 入口:cmd.elf 通过 `ctx->reserved[4]` 取 pe_service + +## 状态: ⬜ PENDING + +## 测试程序 + +| 文件 | 类型 | 来源 | FAT32 8.3 名 | 路径 | +|---|---|---|---|---| +| hello64.exe | PE32+ (64位) | MinGW/MSVC 编译或 Python 生成 | `HELLO64 EXE` | SYSTEM/system/deshab64/tools/ | +| hello32.exe | PE32 (32位) | MinGW 交叉编译或 Python 生成 | `HELLO32 EXE` | SYSTEM/system/deshab64/tools/ | + +## 测试矩阵 + +| # | 测试项 | 命令 | 预期输出 | 状态 | Bug# | +|---|---|---|---|---|---| +| B2.1 | PE32+ 加载执行 | `pe HELLO64.EXE` | `Hello from PE32+!` | ⬜ | | +| B2.2 | PE32 加载执行(解释器) | `pe HELLO32.EXE` | `Hello from PE32!` | ⬜ | | +| B2.3 | PE 头解析 | `peinfo HELLO64.EXE` | `machine=0x8664 entry=0x... image_base=0x...` | ⬜ | | +| B2.4 | 文件不存在错误路径 | `pe NONEXIST.EXE` | `Error: file not found` | ⬜ | | +| B2.5 | 无参用法提示 | `pe` | 显示用法 | ⬜ | | +| B2.6 | ExitProcess 返回 | (执行后) | cmd 接管,longjmp 跳回 pe_service_run | ⬜ | | +| B2.7 | PE32 头解析 | `peinfo HELLO32.EXE` | `machine=0x14c entry=0x... image_base=0x00400000` | ⬜ | | + +## 关键 ABI 验证 + +| ABI | 验证方法 | 状态 | +|---|---|---| +| `ctx->reserved[4]` 持 pe_service* | [cmd/main.c:825](../../CODE/cmd/main.c#L825) | ⬜ | +| `PE_SERVICE_MAGIC` 校验 | [cmd/main.c:830](../../CODE/cmd/main.c#L830) | ⬜ | +| `g_pe_svc` 缓存 | [cmd/main.c:108](../../CODE/cmd/main.c#L108) | ⬜ | +| PE32+ IAT 填 ms_abi shim | [pe_loader.c](../../CODE/UTSM/pe/pe_loader.c) | ⬜ | +| PE32 IAT 填合成地址 0x00010000\|idx | [pe_loader.c](../../CODE/UTSM/pe/pe_loader.c) | ⬜ | +| x86emu32 拦截 CALL 合成地址 | [x86emu32.c](../../CODE/UTSM/pe/x86emu32.c) | ⬜ | + +## 测试程序源码 + +### hello64.c (PE32+) + +```c +#include +int main(void) { + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello from PE32+!\r\n", 19, NULL, NULL); + return 0; +} +``` + +编译:`x86_64-w64-mingw32-gcc -o hello64.exe hello64.c` 或 `cl /Fe:hello64.exe hello64.c` + +### hello32.c (PE32) + +```c +#include +int main(void) { + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello from PE32!\r\n", 18, NULL, NULL); + return 0; +} +``` + +编译:`i686-w64-mingw32-gcc -o hello32.exe hello32.c -Wl,--image-base=0x00400000` + +### Fallback: Python 生成器 + +若无 MinGW/MSVC,使用 [CODE/tools/pe-samples/gen_pe.py](../../CODE/tools/pe-samples/gen_pe.py) 生成最小 PE 程序。 + +## 已知限制 + +- pe_shim 实现的 Win32 API 子集有限,测试程序仅使用 WriteFile + ExitProcess +- x86emu32 指令覆盖率参见 [x86emu32-coverage.md](x86emu32-coverage.md) +- TLS / IFUNC / lazy binding 不支持(DKM ABI 约束) diff --git a/RE/test-cases/performance-baseline.md b/RE/test-cases/performance-baseline.md new file mode 100644 index 0000000..15bccfc --- /dev/null +++ b/RE/test-cases/performance-baseline.md @@ -0,0 +1,88 @@ +# 性能基线测量 + +> 对应计划 Phase C5:基于 TSC 的关键性能指标测量 + +## 状态: ⬜ PENDING + +## 测量方法 + +所有指标基于 TSC(Time Stamp Counter): + +```c +static inline u64 rdtsc(void) { + u32 lo, hi; + __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi)); + return ((u64)hi << 32) | lo; +} +``` + +TSC 频率需先校准(PIT 100ms busy-wait 测量): + +```c +u64 tsc_freq; // Hz +u64 calibrate_tsc(void) { + u64 start = rdtsc(); + pit_busy_wait(100); // 100ms + u64 end = rdtsc(); + return (end - start) * 10; // ×10 得到 Hz +} +``` + +时间换算:`ms = (tsc_diff * 1000) / tsc_freq` + +## 基线指标 + +| # | 指标 | 测量位置 | 基线目标 | 实测 | 状态 | +|---|---|---|---|---|---| +| P1 | UTSM 启动到 DSK SELFTEST | UTSM entry → DSK selftest pass | < 500ms (QEMU) | | ⬜ | +| P2 | DSK 加载 shell.elf 时间 | FAT32 read + PIE load 包围 | < 200ms | | ⬜ | +| P3 | DSK 加载 desktop.elf 时间 | 同上 | < 200ms | | ⬜ | +| P4 | shell `ping 10.0.2.2` RTT | echo request → reply | < 10ms (user-net) | | ⬜ | +| P5 | shell `linux ls` 完整 RTT | IPC exec round-trip | < 100ms (park-and-resume) | | ⬜ | +| P6 | shell `run PING.ELF` 加载时间 | FAT32 read + PIE load | < 150ms | | ⬜ | +| P7 | desktop 窗口拖动帧率 | 帧计数/TSC | 30+ FPS | | ⬜ | +| P8 | `pe HELLO64.EXE` 加载+执行 | pe_service_run 包围 | < 50ms | | ⬜ | +| P9 | `pe HELLO32.EXE` 加载+执行 | x86emu32 解释 | < 200ms | | ⬜ | +| P10 | UTSM VMM self-test | vmm_init → vmm_self_test | < 50ms | | ⬜ | +| P11 | Linux guest 启动到 park | vmlaunch → daemon HLT | < 3s (QEMU WHPX) | | ⬜ | +| P12 | e1000 DHCP 完整耗时 | netman DHCP start → final rc | < 5s | | ⬜ | + +## 自动化测量 + +在 dev_mode run_dev_tests 中追加性能测量代码: + +```c +/* 在 run_dev_tests 开头校准 TSC */ +u64 tsc_freq = calibrate_tsc(); +logl("[shell] TSC freq: "); + +/* 各项测试包围 TSC */ +u64 t0 = rdtsc(); +/* ... 操作 ... */ +u64 t1 = rdtsc(); +u64 ms = (t1 - t0) * 1000 / tsc_freq; +logl("[shell] perf: = ms"); +``` + +输出到串口日志,由 [ISO/run_tests.bat](../../ISO/run_tests.bat) 收集汇总。 + +## 性能回归检测 + +```powershell +# 比较两次测试结果 +$prev = Get-Content ISO/logs/perf-baseline-prev.json | ConvertFrom-Json +$curr = Get-Content ISO/logs/perf-baseline-curr.json | ConvertFrom-Json + +foreach ($m in $curr.metrics) { + $p = $prev.metrics | Where-Object name -eq $m.name + if ($p -and $m.ms -gt $p.ms * 1.5) { + Write-Host "[REGRESSION] $($m.name): $($p.ms)ms -> $($m.ms)ms (>50%)" + } +} +``` + +## 已知限制 + +- QEMU 性能数据不等于真机,仅作回归参考 +- WHPX vs TCG 性能差异巨大,必须固定加速方案 +- 多次测量取中位数,避免单次抖动 diff --git a/RE/test-cases/userapps-matrix.md b/RE/test-cases/userapps-matrix.md new file mode 100644 index 0000000..aef6fc9 --- /dev/null +++ b/RE/test-cases/userapps-matrix.md @@ -0,0 +1,76 @@ +# 用户态程序功能矩阵 + +> 对应计划 Phase C4:FirstInit/login/desktop/shell/cmd + 5 个工具应用 + +## 状态: ⬜ PENDING + +## 程序矩阵 + +| # | 程序 | ELF 路径 | 入口 | 关键命令/功能 | 验证点 | 状态 | Bug# | +|---|---|---|---|---|---|---|---| +| 1 | FirstInit | `system/user/use/FirstInit.elf` | 首启触发 | 计算机名/用户名/密码输入、空字段红字、网络配置 | SHA256 密码、user.conf 写入缓冲 | ⬜ | | +| 2 | login | `system/user/use/login.elf` | 次启触发 | 用户选择、密码输入、错误重试 | USER.CONF 解密、密码匹配 | ⬜ | | +| 3 | desktop | `system/deshab64/desktop.elf` | login 后 | 双击图标、窗口拖动、任务栏、内建 bash | 6 个工具启动、鼠标光标 | ⬜ | | +| 4 | shell | `system/deshab64/shell.elf` | desktop Terminal | ls/cat/cp/mv/rm/echo>/ping/curl/run/linux | dev_mode 自动测试全绿 | ⬜ | | +| 5 | cmd | `system/deshab64/cmd.elf` | desktop CMD | dir/type/copy/del + `pe ` | PE 兼容层调用 | ⬜ | | +| 6 | netman | `system/deshab64/network/netman.elf` | DSK 调度 | 读 conf.conf、应用网络配置 | DHCP/静态 IP | ⬜ | | +| 7 | mouseInit | `system/deshab64/mouse/mouseInit.elf` | DSK 调度 | 安全 stub | 不阻塞启动 | ⬜ | | +| 8 | editor | `system/deshab64/tools/editor.elf` | desktop 双击 | 文件打开/编辑/保存 | FAT32 写盘 | ⬜ | | +| 9 | fileman | `system/deshab64/tools/fileman.elf` | desktop 双击 | 浏览/打开/复制 | 目录遍历 | ⬜ | | +| 10 | browser | `system/deshab64/tools/browser.elf` | desktop 双击 | URL 输入/HTTP GET | net_stack 协议栈 | ⬜ | | +| 11 | curl | `system/deshab64/tools/curl.elf` | desktop 双击 | `curl http://...` | HTTP GET | ⬜ | | +| 12 | ping | `system/deshab64/tools/ping.elf` | desktop 双击 | `ping ` | ICMP echo | ⬜ | | + +## 启动链路 + +``` +首启: utsm → DSK → mouseInit → netman → FirstInit → desktop +次启: utsm → DSK → login → desktop +dev_mode: utsm → DSK → shell(dev_tests) → Esc → login → desktop +desktop → 双击 Terminal → shell.elf +desktop → 双击 CMD → cmd.elf +shell → run FILEMAN.ELF → fileman.elf → Esc 返回 shell +shell → run BROWSER.ELF → browser.elf +shell → run EDITOR.ELF → editor.elf +shell → run PING.ELF → ping.elf +shell → run CURL.ELF → curl.elf +``` + +## 详细测试用例 + +### shell 命令测试 + +| 命令 | 参数 | 预期 | 状态 | +|---|---|---|---| +| `ls` | (无) | 列出根目录文件(DESHAB.ELF, SHELL.ELF 等) | ⬜ | +| `pwd` | (无) | `/` | ⬜ | +| `cat` | `README.TXT` | 显示文件内容 | ⬜ | +| `cp` | `a b` | 复制成功 | ⬜ | +| `mv` | `a b` | 移动成功 | ⬜ | +| `rm` | `a` | 删除成功 | ⬜ | +| `echo>` | `text > file` | 写入文件 | ⬜ | +| `ping` | `10.0.2.2` | 4/4 reply | ⬜ | +| `curl` | `http://10.0.2.2:8000/` | HTTP 200 + body | ⬜ | +| `run` | `PING.ELF` | 加载并执行 ping | ⬜ | +| `linux` | (无) | 显示兼容层状态 | ⬜ | +| `linux` | `ls` | 输出 initramfs 内容 | ⬜ | +| `help` | (无) | 显示命令列表 | ⬜ | +| `ver`/`version` | (无) | 显示版本 | ⬜ | + +### desktop UI 测试 + +| 操作 | 预期 | 状态 | +|---|---|---| +| 双击 Terminal 图标 | 打开 shell 窗口 | ⬜ | +| 双击 CMD 图标 | 打开 cmd 窗口 | ⬜ | +| 双击 Editor 图标 | 打开 editor | ⬜ | +| 双击 File Manager 图标 | 打开 fileman | ⬜ | +| 双击 Browser 图标 | 打开 browser | ⬜ | +| 鼠标移动 | 光标跟随,无闪烁 | ⬜ | +| 窗口拖动 | 流畅,无撕裂 | ⬜ | +| 任务栏点击 | 窗口切换 | ⬜ | + +## 已知问题 + +- fileman.elf 之前触发过 QEMU 退出(访问未支持 boot context 字段),需观察是否复现 +- 真实 TTF 字体未运行时光栅化,使用预渲染位图(已知设计选择,非 bug) diff --git a/RE/test-cases/x86emu32-coverage.md b/RE/test-cases/x86emu32-coverage.md new file mode 100644 index 0000000..d89bb77 --- /dev/null +++ b/RE/test-cases/x86emu32-coverage.md @@ -0,0 +1,86 @@ +# x86emu32 指令覆盖率测试 + +> 对应计划 Phase B3:枚举 x86emu32.c 已实现指令,验证覆盖率 + +## 状态: ⬜ PENDING + +## 已实现指令清单 + +> 待根据 [x86emu32.c](../../CODE/UTSM/pe/x86emu32.c) 实际代码枚举填充 + +### 数据传送 + +| 指令 | 操作码 | 测试程序 | 预期 | 状态 | +|---|---|---|---|---| +| MOV r32, imm32 | B8+rd | `mov eax, 0x12345678` | eax=0x12345678 | ⬜ | +| MOV r32, r/m32 | 8B /r | `mov eax, ebx` | eax=ebx | ⬜ | +| MOV r/m32, r32 | 89 /r | `mov [esi], eax` | [esi]=eax | ⬜ | +| PUSH r32 | 50+rd | `push eax` | [esp]=eax, esp-=4 | ⬜ | +| POP r32 | 58+rd | `pop eax` | eax=[esp], esp+=4 | ⬜ | + +### 算术运算 + +| 指令 | 操作码 | 测试程序 | 预期 | 状态 | +|---|---|---|---|---| +| ADD r32, r/m32 | 03 /r | `add eax, ebx` | eax=eax+ebx | ⬜ | +| SUB r32, r/m32 | 2B /r | `sub eax, ebx` | eax=eax-ebx | ⬜ | +| INC r32 | FF /0 | `inc eax` | eax=eax+1 | ⬜ | +| DEC r32 | FF /1 | `dec eax` | eax=eax-1 | ⬜ | +| MUL r/m32 | F7 /4 | `mul ebx` | edx:eax=eax*ebx | ⬜ | +| DIV r/m32 | F7 /6 | `div ebx` | eax=edx:eax/ebx | ⬜ | + +### 逻辑运算 + +| 指令 | 操作码 | 测试程序 | 预期 | 状态 | +|---|---|---|---|---| +| AND r32, r/m32 | 23 /r | `and eax, ebx` | eax=eax&ebx | ⬜ | +| OR r32, r/m32 | 0B /r | `or eax, ebx` | eax=eax\|ebx | ⬜ | +| XOR r32, r/m32 | 33 /r | `xor eax, ebx` | eax=eax^ebx | ⬜ | +| NOT r/m32 | F7 /2 | `not eax` | eax=~eax | ⬜ | + +### 控制流 + +| 指令 | 操作码 | 测试程序 | 预期 | 状态 | +|---|---|---|---|---| +| JMP rel32 | E9 cw/cd | `jmp label` | rip=label | ⬜ | +| JMP r/m32 | FF /4 | `jmp eax` | rip=eax | ⬜ | +| CALL rel32 | E8 cd | `call func` | push rip, rip=func | ⬜ | +| RET | C3 | `ret` | pop rip | ⬜ | +| Jcc rel32 | 0F 8x cd | `jz label` | 条件跳转 | ⬜ | + +### 字符串操作 + +| 指令 | 操作码 | 测试程序 | 预期 | 状态 | +|---|---|---|---|---| +| MOVSB | A4 | `movsb` | [edi]=[esi], inc/dec | ⬜ | +| STOSB | AA | `stosb` | [edi]=al, inc/dec | ⬜ | +| REP MOVSB | F3 A4 | `rep movsb` | 重复 ecx 次 | ⬜ | +| REP STOSB | F3 AA | `rep stosb` | 重复 ecx 次 | ⬜ | + +### 堆栈与控制 + +| 指令 | 操作码 | 测试程序 | 预期 | 状态 | +|---|---|---|---|---| +| ENTER imm16, imm8 | C8 iw ib | `enter 8, 0` | 建立栈帧 | ⬜ | +| LEAVE | C9 | `leave` | 释放栈帧 | ⬜ | +| NOP | 90 | `nop` | 无操作 | ⬜ | +| INT3 | CC | `int3` | 触发断点 | ⬜ | + +## 测试 PE 程序 + +每个指令类别构造一个最小 PE32 测试程序,在 [CODE/tools/pe-samples/](../../CODE/tools/pe-samples/) 下: + +- `test_arith.exe`:算术运算 +- `test_logic.exe`:逻辑运算 +- `test_control.exe`:控制流 +- `test_string.exe`:字符串操作 + +每个测试程序通过 WriteFile 输出结果到 cmd.elf,与预期对比。 + +## 覆盖率统计 + +``` +已测试指令数 / 已实现指令数 = 覆盖率 +``` + +目标:核心指令(MOV/ADD/SUB/CMP/JMP/CALL/RET/PUSH/POP)覆盖率 100%。 diff --git a/SYSTEM/bin/cmd.exe b/SYSTEM/bin/cmd.exe new file mode 100644 index 0000000..b3ac238 Binary files /dev/null and b/SYSTEM/bin/cmd.exe differ diff --git a/SYSTEM/bin/hello32.exe b/SYSTEM/bin/hello32.exe new file mode 100644 index 0000000..9cd27ea Binary files /dev/null and b/SYSTEM/bin/hello32.exe differ diff --git a/SYSTEM/bin/hello64.exe b/SYSTEM/bin/hello64.exe new file mode 100644 index 0000000..cfb10a8 Binary files /dev/null and b/SYSTEM/bin/hello64.exe differ diff --git a/SYSTEM/boot/utsm.elf b/SYSTEM/boot/utsm.elf index 06933eb..f31d6ee 100644 Binary files a/SYSTEM/boot/utsm.elf and b/SYSTEM/boot/utsm.elf differ diff --git a/SYSTEM/limine/limine.conf b/SYSTEM/limine/limine.conf index a71b13a..937ee34 100644 --- a/SYSTEM/limine/limine.conf +++ b/SYSTEM/limine/limine.conf @@ -57,6 +57,8 @@ verbose: yes # 8. UTSM loads DSK (deshab.elf) → shell.elf → `linux ` command # invokes linux_compat_service->exec() → vmresume → daemon runs prog # + # Linux 兼容层模块(需先通过 WSL 运行 CODE/linux/build.sh 构建产物) + # build.sh 成功后会自动取消下面 4 行注释;若未构建 Linux 内核请保持注释状态 # module_path: boot():/boot/linux-bzImage # module_cmdline: linux:bzImage # module_path: boot():/boot/linux-initrd.img diff --git a/SYSTEM/system/deshab64/cmd.elf b/SYSTEM/system/deshab64/cmd.elf index 97689a0..56f15e8 100644 Binary files a/SYSTEM/system/deshab64/cmd.elf and b/SYSTEM/system/deshab64/cmd.elf differ diff --git a/SYSTEM/system/deshab64/desktop.elf b/SYSTEM/system/deshab64/desktop.elf index 61bf351..c72a862 100644 Binary files a/SYSTEM/system/deshab64/desktop.elf and b/SYSTEM/system/deshab64/desktop.elf differ diff --git a/SYSTEM/system/deshab64/shell.elf b/SYSTEM/system/deshab64/shell.elf index 596fc60..d50f7a5 100644 Binary files a/SYSTEM/system/deshab64/shell.elf and b/SYSTEM/system/deshab64/shell.elf differ diff --git a/SYSTEM/system/user/use/FirstInit.elf b/SYSTEM/system/user/use/FirstInit.elf index 888cbba..9cb972d 100644 Binary files a/SYSTEM/system/user/use/FirstInit.elf and b/SYSTEM/system/user/use/FirstInit.elf differ