FUCKKKK
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
ISO/deshab.img
|
||||
ISO/deshab-dev.img
|
||||
ISO/deshab-realtest.img
|
||||
ISO/deshab-release.img
|
||||
*.o
|
||||
.build_tmp/
|
||||
.trae/
|
||||
|
||||
@@ -125,9 +125,14 @@ SYSTEM/ 是打包为 IMG 后的系统根目录。
|
||||
SYSTEM/boot/ 存放启动模块,例如 utsm.elf。
|
||||
SYSTEM/driver/ 存放 ELF .drv 驱动模块和 manifest.json。
|
||||
CODE/UTSM/README.md: 编译到 SYSTEM/boot/utsm.elf
|
||||
build.bat: 调用 build.ps1。
|
||||
build.ps1: 编译 UTSM,生成 SYSTEM/boot/utsm.elf,并把 SYSTEM 打包为 ISO/deshab.img。deshab.img 是 GPT 磁盘镜像,包含 FAT32 EFI System Partition。
|
||||
ISO/run_qemu.bat: 用 QEMU + UEFI 固件启动 ISO/deshab.img,串口输出到 stdio。
|
||||
build.bat: 调用 build.ps1,透传参数(如 -Variant dev)。
|
||||
build.ps1: 编译 UTSM/DSK/工具/驱动,产出两个镜像:
|
||||
- ISO/deshab-dev.img 开发者模式(dev_mode=1,自动跑 shell.elf 测试,调试全开,QEMU 验证选项)
|
||||
- ISO/deshab-release.img 发布版(dev_mode=0,进桌面+登录,调试零开销,真机安全选项)
|
||||
参数 -Variant dev|release|both(默认 both)。both 模式构建后恢复 SYSTEM 为 dev 配置。
|
||||
配置模板在 build/configs/{dev,release}/(FUCK + firstInit.txt),打包前注入到 SYSTEM。
|
||||
ISO/run_qemu.bat: 用 QEMU + UEFI 固件启动 ISO/deshab-dev.img,串口输出到 stdio。
|
||||
build.ps1 自动把 Git usr/bin 加入 PATH(sh.exe + mkdir),解决 make `mkdir -p` 在 cmd.exe 下失败的问题。
|
||||
```
|
||||
|
||||
当前 UTSM 已有首阶段内核代码骨架和构建脚本,可生成 utsm.elf 与 GPT + FAT32 ESP 镜像。早期启动要求:进入 C 前 `cli/cld`,early serial 不允许无限等待硬件 ready 位,未启用 FPU/SSE 前编译必须使用 `-mno-sse -mno-sse2 -mno-mmx -msoft-float`。DATA/BSS 段使用 PF_R|PF_W|PF_X(SAS-R0 下驱动模块内存需可执行)。
|
||||
|
||||
@@ -14,6 +14,23 @@
|
||||
/* ---- Probe 信息结构(通过 reserved[6] 传递给 shell/desktop) ---- */
|
||||
#define PROBE_INFO_MAGIC 0x50524F42u /* "PRFB" — Probe inFo Buffer */
|
||||
|
||||
/* ---- 网络租约信息(netman 写入,DSK 持有存储,经 reserved[7] 指针传递) ----
|
||||
* reserved[0..6] 已被占用(user.conf/login/dev_mode/pe/lxc/probe),
|
||||
* 仅 reserved[7] 空闲,故以其存放指向本结构的指针。 */
|
||||
#define NET_LEASE_MAGIC 0x4E45544Cu /* "NETL" */
|
||||
#define NET_LEASE_F_VALID (1u << 0) /* ip/gateway/dns 有效 */
|
||||
#define NET_LEASE_F_STATIC (1u << 1) /* 静态配置(非 DHCP) */
|
||||
|
||||
typedef struct net_lease_info {
|
||||
u32 magic; /* NET_LEASE_MAGIC */
|
||||
u32 flags; /* NET_LEASE_F_* */
|
||||
u32 ip; /* guest IP(网络序) */
|
||||
u32 gateway; /* 网关(网络序) */
|
||||
u32 dns; /* DNS(网络序) */
|
||||
u32 server; /* DHCP server(网络序,调试) */
|
||||
u32 reserved[2];
|
||||
} net_lease_info;
|
||||
|
||||
typedef struct probe_info {
|
||||
u32 magic; /* PROBE_INFO_MAGIC */
|
||||
u32 version; /* ABI 版本,当前 1 */
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#define EPT_PAGE_SIZE 4096ULL
|
||||
#define EPT_ENTRIES 512ULL
|
||||
|
||||
/* EPTP 构造:bits [5:0]=memory type, bits [7:6]=walk length-1, bits [51:12]=PML4 HPA */
|
||||
#define EPTP_WALK_LEN_4 (3ULL << 6) /* 4 级页表 */
|
||||
/* EPTP 构造:bits [2:0]=memory type, bits [5:3]=walk length-1, bit 6=AD, bits [51:12]=PML4 HPA
|
||||
* P8.4 Fix: walk length was at bits [7:6] (wrong), should be bits [5:3] per Intel SDM. */
|
||||
#define EPTP_WALK_LEN_4 (3ULL << 3) /* 4 级页表 (3 = 4-1) at bits [5:3] */
|
||||
#define EPTP_MEMTYPE_WB (EPT_MEMORY_TYPE_WB)
|
||||
|
||||
/* 初始化 EPT:分配 PML4,置零。返回 0 成功。 */
|
||||
|
||||
@@ -21,6 +21,34 @@
|
||||
|
||||
#define LINUX_COMPAT_MAGIC 0x4C4E58434F4D5000ULL /* "LNXCOMP\0" */
|
||||
|
||||
/* VSCode Phase 3: virtio-gpu scanout 查询结果。
|
||||
* 镜像 virtio_mmio.h 的 virtio_gpu_scanout_info 字段布局,保持公共 ABI 自洽
|
||||
* (不强制消费者 include virtio_mmio.h)。必须在 linux_compat_service 之前
|
||||
* 定义,因为服务表中有指向此类型的函数指针。 */
|
||||
struct linux_compat_scanout_info {
|
||||
void *host_vaddr;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 stride;
|
||||
u32 dirty;
|
||||
u32 enabled;
|
||||
/* Phase 7: 累积 dirty rect(scanout 坐标系并集,查询随 dirty 清零)。
|
||||
* dirty=0 时全为 0;desktop 据此只 blit 变化区域(尾部追加,ABI 兼容)。 */
|
||||
u32 dirty_x;
|
||||
u32 dirty_y;
|
||||
u32 dirty_w;
|
||||
u32 dirty_h;
|
||||
/* P7.6: hardware cursor state(尾部追加,ABI 兼容)。 */
|
||||
u32 cursor_visible;
|
||||
u32 cursor_x;
|
||||
u32 cursor_y;
|
||||
u32 cursor_hot_x;
|
||||
u32 cursor_hot_y;
|
||||
u32 cursor_w;
|
||||
u32 cursor_h;
|
||||
void *cursor_bitmap;
|
||||
};
|
||||
|
||||
/* Linux 兼容层服务表 — 通过 boot context reserved[5] 传递 */
|
||||
typedef struct linux_compat_service {
|
||||
u64 magic; /* LINUX_COMPAT_MAGIC */
|
||||
@@ -93,6 +121,41 @@ typedef struct linux_compat_service {
|
||||
* 返回 0 成功,负值失败(-11=文件不存在,其余同 file_pull)。 */
|
||||
int (*file_push)(const char *fat32_name, const char *linux_path,
|
||||
u64 *out_size);
|
||||
|
||||
/* VSCode Phase 2: 输入转发到 Linux guest(virtio-input 键盘+鼠标)。
|
||||
* 由 desktop 在 IDE attached 时调用,把 Deshab PS/2 事件注入 guest。
|
||||
* 事件排到 pending ring,由 vmexit poll 路径排空到 eventq + 注入 IRQ。
|
||||
*
|
||||
* input_forward_keyboard(code, value)
|
||||
* code : linux keycode(KEY_A=30 等,见 linux/input-event-codes.h)
|
||||
* value : 0=release, 1=press, 2=repeat
|
||||
*
|
||||
* input_forward_mouse(dx, dy, buttons)
|
||||
* dx, dy : 相对位移(像素差,可为负)
|
||||
* buttons : 按钮位图 bit0=左, bit1=右, bit2=中(内部与上次状态比较,
|
||||
* 仅变化的按钮产生 press/release 事件;末尾自动追加 SYN) */
|
||||
void (*input_forward_keyboard)(u16 code, u32 value);
|
||||
void (*input_forward_mouse)(i32 dx, i32 dy, u8 buttons);
|
||||
|
||||
/* VSCode Phase 3: 查询 virtio-gpu scanout 状态(desktop IDE blit 用)。
|
||||
* 在 IDE attached 时由 desktop 周期调用:若 dirty=1,把 host_vaddr 指向的
|
||||
* scanout 影子缓冲(width×height,stride 字节/行,XRGB8888)blit 到
|
||||
* framebuffer 的 IDE host 区域,然后重新查询以清零 dirty。
|
||||
* out->host_vaddr : scanout 影子缓冲 host 虚拟地址(SAS-R0 下可直接解引用)
|
||||
* out->enabled : 1 = scanout 已绑定 2D resource(X server 已 SET_SCANOUT)
|
||||
* out->dirty : 1 = 自上次查询有新帧(查询会清零该位)
|
||||
* 返回 0 成功(enabled=0 时其余字段为零),-1 后端未就绪(无 surface pool)。 */
|
||||
int (*gpu_get_scanout_info)(struct linux_compat_scanout_info *out);
|
||||
|
||||
/* VSCode Phase 5: 异步执行 Linux 程序(daemon fork+setsid+detach)。
|
||||
* 与 exec 语义相同,但 daemon 在 spawn 成功后立即回 EXEC_EXIT(0),
|
||||
* 不捕获 stdout、不等待进程退出——用于启动 X server / VSCode 等
|
||||
* 长生命周期 GUI 进程而不阻塞 UTSM 侧。子进程 stdio 重定向到
|
||||
* /dev/null,环境自带 LD_LIBRARY_PATH=/usr/lib/deshab 与 DISPLAY=:0。
|
||||
* 返回 0 = 已成功 spawn(不代表程序后续存活),负值失败
|
||||
* (-1=服务不可用, -2=IPC 发送失败, -3=linux_resume 失败,
|
||||
* -4=无响应, -5=daemon 报告 spawn 失败)。 */
|
||||
int (*exec_async)(const char *path, int argc, const char *const *argv);
|
||||
} linux_compat_service;
|
||||
|
||||
/* ===== UTSM 内部接口(不进服务表,供 hypercall.c 等内核代码使用) =====
|
||||
@@ -111,4 +174,21 @@ const linux_compat_service *linux_compat_get_service(void);
|
||||
/* 初始化 Linux 兼容层服务(在 linux_launch 成功后调用)。 */
|
||||
void linux_compat_init(void);
|
||||
|
||||
/* 同步 FAT32 lib 目录下所有 .so 文件到 Linux guest。
|
||||
* guest_lib_path : guest 端库目录路径(如 "/usr/lib/deshab")
|
||||
* 调用时机:linux_compat_init 成功后。失败不阻断启动。
|
||||
* 返回 0 成功,负值失败。 */
|
||||
int lxc_sync_lib_dir(const char *guest_lib_path);
|
||||
|
||||
/* VSCode Phase 5: 把 VSCode tarball(Limine boot module,路径含 "vscode")
|
||||
* 安装到 Linux guest 的 /opt/vscode(经 overlay 持久化在 extra-rootfs 卷)。
|
||||
* 流程:已安装标记探测(/opt/vscode/bin/code 可执行则跳过)→
|
||||
* 流式推送 module 字节到 guest /tmp/vscode.tar.gz →
|
||||
* mkdir -p /opt/vscode → tar 解包(--strip-components=1,失败回退不解层)→
|
||||
* 清理 /tmp tarball → 验证 /opt/vscode/bin/code。
|
||||
* 调用时机:linux_compat_init 成功后(kernel main,FUCK [compat] vscode_install=1)。
|
||||
* 返回 0 成功或已安装;负值失败(-1 服务不可用, -11 无 tarball module,
|
||||
* -12 推送失败, -13 解包失败, -14 验证失败)。所有失败均不阻断启动。 */
|
||||
int lxc_vscode_install(void);
|
||||
|
||||
#endif /* UTSM_LINUX_COMPAT_H */
|
||||
|
||||
@@ -103,7 +103,9 @@ struct linux_setup_header {
|
||||
* 0x01000000-0x02FFFFFF : Linux kernel image
|
||||
* 0x03000000-0x03FFFFFF : Linux initrd
|
||||
* 0x04000000-0x040FFFFF : IPC shared memory (1MB, both kernels RW)
|
||||
* 0x05000000-MAX : Linux general RAM
|
||||
* 0x04100000-0x04FFFFFF : Graphics surface pool (16MB, VSCode integration Phase 1)
|
||||
* 0x05000000-0x5FFFFFFF : Linux general RAM (~1.4GB, expanded Phase 1 for VSCode)
|
||||
* 0xF4000000-0xF4007000 : virtio-mmio devices (8 slots, expanded Phase 1)
|
||||
*/
|
||||
#define LINUX_GUEST_KERNEL_GPA 0x01000000ULL /* 16 MB, 2MB-aligned */
|
||||
#define LINUX_GUEST_BOOTPARAMS_GPA 0x00400000ULL /* 4 MB */
|
||||
@@ -118,8 +120,19 @@ struct linux_setup_header {
|
||||
#define LINUX_GUEST_IPC_SHM_GPA 0x04000000ULL /* 64 MB */
|
||||
#define LINUX_GUEST_IPC_SHM_SIZE (1024 * 1024) /* 1MB */
|
||||
|
||||
/* General RAM starts after IPC region */
|
||||
/* VSCode integration Phase 1: Graphics surface pool.
|
||||
* Separate DMA region EPT-mapped into the Linux guest. Linux user-space mmaps
|
||||
* /dev/utsm at this GPA to obtain a writable scanout buffer that UTSM reads
|
||||
* for blitting to the Deshab framebuffer (Phase 3 virtio-gpu shadow buffer).
|
||||
* 16MB accommodates a 1920×1080×4 framebuffer (~8MB) with headroom for
|
||||
* cursor plane and dirty rect tracking metadata. */
|
||||
#define LINUX_GUEST_SURFACE_GPA 0x04100000ULL /* 65 MB (immediately after IPC shm) */
|
||||
#define LINUX_GUEST_SURFACE_SIZE (16 * 1024 * 1024) /* 16MB */
|
||||
|
||||
/* General RAM starts after IPC + surface regions.
|
||||
* Phase 1 expansion: 1.4GB to fit VSCode + Electron + GUI stack. */
|
||||
#define LINUX_GUEST_RAM_GPA 0x05000000ULL /* 80 MB */
|
||||
#define LINUX_GUEST_RAM_SIZE 0x5B000000ULL /* 1.4GB (0x05000000..0x5FFFFFFF) */
|
||||
|
||||
/* ===== virtio-mmio device GPA layout =====
|
||||
* virtio-mmio 设备放在 GPA 高位 MMIO 区(guest RAM 之上,不占用 e820 RAM)。
|
||||
@@ -129,16 +142,33 @@ struct linux_setup_header {
|
||||
* 这些 GPA 在 EPT 中【故意不映射】,访问时触发 EPT violation,
|
||||
* 由 vmexit handler 路由到 virtio_mmio 后端模拟寄存器读写。
|
||||
*
|
||||
* GPA 选择 0xF4000000 起始(接近 4GB,远离 guest RAM 0x05000000-0x09000000)。 */
|
||||
* GPA 选择 0xF4000000 起始(接近 4GB,远离 guest RAM 0x05000000-0x5FFFFFFF)。
|
||||
*
|
||||
* VSCode integration Phase 1 expansion: 槽位从 3 扩到 8,为后续阶段预留:
|
||||
* slot 0: blk (Phase 1-) IRQ5
|
||||
* slot 1: net (Phase 1-) IRQ6
|
||||
* slot 2: rootfs (Phase 1-) IRQ7
|
||||
* slot 3: keyboard (Phase 2) IRQ8 — virtio-input, evdev /dev/input/event0
|
||||
* slot 4: mouse (Phase 2) IRQ9 — virtio-input, evdev /dev/input/event1
|
||||
* slot 5: gpu (Phase 3) IRQ10 — virtio-gpu 2D, /dev/dri/card0
|
||||
* slot 6: extra-rootfs (Phase 4) IRQ11 — persistent rw volume (/dev/vdc)
|
||||
* slot 7: reserved — future use
|
||||
*/
|
||||
#define VIRTIO_MMIO_GPA_BASE 0xF4000000ULL
|
||||
#define VIRTIO_MMIO_GPA_STRIDE 0x1000ULL /* 每个设备 4KB */
|
||||
#define VIRTIO_MMIO_MAX_SLOTS 8 /* Phase 1: 3→8 */
|
||||
|
||||
#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 */
|
||||
#define VIRTIO_MMIO_ROOTFS_GPA (VIRTIO_MMIO_GPA_BASE + 2 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4002000 */
|
||||
#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 */
|
||||
#define VIRTIO_MMIO_ROOTFS_GPA (VIRTIO_MMIO_GPA_BASE + 2 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4002000 */
|
||||
/* VSCode integration: Phase 2/3/4 device slots (currently unused, reserved) */
|
||||
#define VIRTIO_MMIO_KBD_GPA (VIRTIO_MMIO_GPA_BASE + 3 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4003000 */
|
||||
#define VIRTIO_MMIO_MOUSE_GPA (VIRTIO_MMIO_GPA_BASE + 4 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4004000 */
|
||||
#define VIRTIO_MMIO_GPU_GPA (VIRTIO_MMIO_GPA_BASE + 5 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4005000 */
|
||||
#define VIRTIO_MMIO_EXTRA_ROOTFS_GPA (VIRTIO_MMIO_GPA_BASE + 6 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4006000 */
|
||||
|
||||
/* 判断 GPA 是否落在 virtio-mmio 设备区(含两端) */
|
||||
#define VIRTIO_MMIO_GPA_END (VIRTIO_MMIO_GPA_BASE + 3 * VIRTIO_MMIO_GPA_STRIDE)
|
||||
#define VIRTIO_MMIO_GPA_END (VIRTIO_MMIO_GPA_BASE + VIRTIO_MMIO_MAX_SLOTS * VIRTIO_MMIO_GPA_STRIDE)
|
||||
#define IS_VIRTIO_MMIO_GPA(gpa) ((gpa) >= VIRTIO_MMIO_GPA_BASE && (gpa) < VIRTIO_MMIO_GPA_END)
|
||||
|
||||
/* ===== Linux loader API ===== */
|
||||
@@ -156,6 +186,13 @@ struct linux_guest_info {
|
||||
u64 pgt_gpa; /* GPA of guest page tables */
|
||||
u64 gdt_gpa; /* GPA of guest GDT */
|
||||
int loaded; /* 1 = successfully loaded */
|
||||
/* VSCode integration Phase 1: graphics surface pool.
|
||||
* Allocated as a separate DMA buffer and EPT-mapped into the guest at
|
||||
* surface_gpa. Exposed to Linux user-space via UTSM_HCALL_SURFACE_INFO
|
||||
* and /dev/utsm mmap (see CODE/linux/patches/utsm_hcall.c). */
|
||||
u64 surface_hpa; /* Host physical address of surface pool (0 = none) */
|
||||
u64 surface_gpa; /* GPA visible inside Linux guest */
|
||||
u64 surface_size; /* Surface pool size in bytes */
|
||||
};
|
||||
|
||||
/* Find the Linux bzImage boot module among Limine modules.
|
||||
@@ -167,6 +204,17 @@ void *linux_find_bzimage_module(u64 *size_out);
|
||||
* Exposed to Linux guest via memory-backed virtio-blk as /dev/vdb. */
|
||||
void *linux_find_rootfs_module(u64 *size_out);
|
||||
|
||||
/* VSCode Phase 4: find the extra rootfs image boot module (path contains
|
||||
* "extra"). Exposed via writable virtio-blk slot6 as /dev/vdc (overlayfs
|
||||
* upper / VSCode data volume). Returns NULL when the module is absent. */
|
||||
void *linux_find_extra_rootfs_module(u64 *size_out);
|
||||
|
||||
/* VSCode Phase 5: find the VSCode tarball boot module (path contains
|
||||
* "vscode", e.g. linux-vscode.tar.gz). The tarball is streamed into the
|
||||
* guest and extracted to /opt/vscode by lxc_vscode_install().
|
||||
* Returns NULL when the module is absent (install skipped, non-fatal). */
|
||||
void *linux_find_vscode_module(u64 *size_out);
|
||||
|
||||
/* Parse bzImage header and extract key parameters.
|
||||
* Returns 0 on success, negative on error. */
|
||||
int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
@@ -181,6 +229,20 @@ int linux_loader_init(void);
|
||||
/* Get the loaded Linux guest info (NULL if not loaded). */
|
||||
const struct linux_guest_info *linux_get_guest_info(void);
|
||||
|
||||
/* VSCode integration Phase 1: query graphics surface pool.
|
||||
* Returns 0 and fills outputs if the surface pool was allocated and EPT-mapped
|
||||
* into the Linux guest; returns -1 if not initialized (e.g. Linux guest not
|
||||
* loaded). hpa_out receives the host physical address (for UTSM-side blit),
|
||||
* gpa_out receives the GPA visible inside Linux (for the guest mmap path),
|
||||
* size_out receives the pool size in bytes. Any output pointer may be NULL. */
|
||||
int linux_get_surface_info(u64 *hpa_out, u64 *gpa_out, u64 *size_out);
|
||||
|
||||
/* VSCode Phase 3: return host virtual address of the graphics surface pool.
|
||||
* The virtio-gpu 2D backend composites the scanout into this buffer; the
|
||||
* desktop reads it to blit guest graphics onto the IDE host area.
|
||||
* Returns NULL if the pool was not allocated (Linux guest not loaded / alloc failed). */
|
||||
void *linux_get_surface_vaddr(void);
|
||||
|
||||
/* Configure VMCS for Linux guest and perform vmlaunch.
|
||||
* Must be called after vmm_init() + linux_loader_init().
|
||||
* Returns 0 on successful guest termination, negative on error. */
|
||||
@@ -193,13 +255,22 @@ int linux_launch(void);
|
||||
* 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。 */
|
||||
* net@0xF4001000 IRQ6),需 CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y。
|
||||
* VSCode Phase 2: kbd@0xF4003000 IRQ8, mouse@0xF4004000 IRQ9 (virtio-input)。
|
||||
* VSCode Phase 3: gpu@0xF4005000 IRQ10 (virtio-gpu 2D, /dev/dri/card0)。
|
||||
* VSCode Phase 4: xrootfs@0xF4006000 IRQ11 (可写 virtio-blk, /dev/vdc)。
|
||||
* 注:slot6 无对应 boot module 时后端不注册,guest 探测会超时跳过,
|
||||
* 不影响启动(virtio_mmio 对无响应设备只打一条 warn)。 */
|
||||
#define LINUX_DEFAULT_CMDLINE \
|
||||
"console=ttyS0,115200 earlyprintk=serial nokaslr " \
|
||||
"no_timer_check loglevel=7 nohlt idle=poll " \
|
||||
"noapic nolapic nosmp " \
|
||||
"virtio_mmio.device=4K@0xF4000000:5 " \
|
||||
"virtio_mmio.device=4K@0xF4001000:6 " \
|
||||
"virtio_mmio.device=4K@0xF4002000:7"
|
||||
"virtio_mmio.device=4K@0xF4002000:7 " \
|
||||
"virtio_mmio.device=4K@0xF4003000:8 " \
|
||||
"virtio_mmio.device=4K@0xF4004000:9 " \
|
||||
"virtio_mmio.device=4K@0xF4005000:10 " \
|
||||
"virtio_mmio.device=4K@0xF4006000:11"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,4 +13,9 @@ void log_error(const char *msg);
|
||||
void log_hex64(const char *prefix, u64 value);
|
||||
void panic(const char *msg);
|
||||
|
||||
/* ---- 内存日志缓冲区(实机启动日志持久化) ---- */
|
||||
void log_capture_enable(int enable);
|
||||
const char *log_get_buffer(void);
|
||||
u32 log_get_length(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,29 @@ typedef struct pe_image_info {
|
||||
int load_ok; /* 1=加载成功 */
|
||||
} pe_image_info;
|
||||
|
||||
/* ===== 窗口模式(P5 桌面嵌入)=====
|
||||
* 默认 run() 为全屏独占:shim 直写 Limine framebuffer、直接轮询 PS/2 端口。
|
||||
* run_windowed() 把 PE GUI 嵌入桌面受管窗口:
|
||||
* - 渲染目标改为 host 提供的内存 surface(32bpp 0xAARRGGBB)
|
||||
* - PE 消息队列空转(GetMessage/PeekMessage 无消息)时回调 host->pump(),
|
||||
* 由 desktop 驱动一帧(渲染全部窗口 + 采集输入 + 注入 PE 队列)
|
||||
* - 输入由 host 经 inject_scancode/inject_pointer 注入(坐标已转换为
|
||||
* surface 局部坐标,shim 内部合成 Win32 消息与修饰键状态)
|
||||
*
|
||||
* 协作模型约束:
|
||||
* - pump 回调运行在 PE 大栈上,desktop 帧代码栈用量必须远小于 1MB;
|
||||
* - pump 内禁止再次调用 run/run_windowed(禁止嵌套 PE 实例);
|
||||
* - 关闭 PE 窗口 = inject WM_CLOSE(desktop 经 inject_scancode 无此路径,
|
||||
* 使用 inject_input 直接投递消息),等待 PE 自愿退出,run 才返回。 */
|
||||
typedef struct pe_window_host {
|
||||
int (*pump)(void *ud); /* PE 消息空转让出点;返回 0 继续,非 0 请求退出 */
|
||||
void *ud;
|
||||
u8 *surface; /* PE 渲染目标(32bpp 0xAARRGGBB),NULL=全屏 */
|
||||
u32 width; /* surface 宽(像素) */
|
||||
u32 height; /* surface 高(像素) */
|
||||
u32 pitch; /* 字节/行 */
|
||||
} pe_window_host;
|
||||
|
||||
/* PE 服务表 — 通过 boot context reserved[4] 传递 */
|
||||
typedef struct pe_service {
|
||||
u64 magic; /* PE_SERVICE_MAGIC */
|
||||
@@ -34,6 +57,21 @@ typedef struct pe_service {
|
||||
int (*run)(const void *pe_data, u64 size, const char *cmdline, u64 *exit_code);
|
||||
/* 释放 pe_load 分配的镜像内存。 */
|
||||
void (*unload)(pe_image_info *info);
|
||||
/* ===== ABI 尾部追加(P5 窗口模式)。同镜像一致性假设:
|
||||
* desktop 与 UTSM 由同一 build.ps1 产出,旧 UTSM 配新 desktop
|
||||
* 的组合现实中不存在;调用前仍须校验 magic。 ===== */
|
||||
/* 窗口模式运行:语义同 run,渲染/输入/让出按 pe_window_host 重定向。
|
||||
* host==NULL 或 host->surface==NULL 时等价 run。 */
|
||||
int (*run_windowed)(const void *pe_data, u64 size, const char *cmdline,
|
||||
u64 *exit_code, const pe_window_host *host);
|
||||
/* 注入键盘扫描码(set-1,bit7=release;e0_prefix!=0 表示 E0 扩展键)。
|
||||
* shim 内部完成 vk 映射/修饰键跟踪/WM_CHAR 合成,与全屏路径共用。 */
|
||||
int (*inject_scancode)(u32 scancode, u32 e0_prefix);
|
||||
/* 注入鼠标状态(surface 局部坐标;buttons bit0=L bit1=R bit2=M)。
|
||||
* shim 内部与上次状态比较合成 MOUSEMOVE/BUTTONDOWN/UP 边沿。 */
|
||||
int (*inject_pointer)(i32 x, i32 y, u32 buttons);
|
||||
/* 直接投递一条 Win32 消息(用于 WM_CLOSE 等宿主发起的控制消息)。 */
|
||||
int (*inject_input)(u32 msg, u64 wparam, u64 lparam, i32 ptx, i32 pty);
|
||||
} pe_service;
|
||||
|
||||
/* 获取全局 PE 服务实例指针(UTSM 内部使用,外部通过 boot context 获取) */
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
#define VIRTIO_ID_NET 1
|
||||
#define VIRTIO_ID_BLOCK 2
|
||||
#define VIRTIO_ID_CONSOLE 3
|
||||
#define VIRTIO_ID_INPUT 18 /* VSCode Phase 2: virtio-input (keyboard/mouse) */
|
||||
#define VIRTIO_ID_GPU 16 /* VSCode Phase 3: virtio-gpu 2D */
|
||||
|
||||
/* device status bits */
|
||||
#define VIRTIO_STATUS_ACKNOWLEDGE 1
|
||||
@@ -85,6 +87,10 @@ struct virtio_backend {
|
||||
|
||||
/* 读取 device config 空间(offset 相对 0x100)。返回 32 位值。 */
|
||||
u32 (*read_config)(u32 offset, int width);
|
||||
/* VSCode Phase 2: 写入 device config 空间(offset 相对 0x100)。
|
||||
* virtio-input 用此通道设置 select/subsel 再读结果;其它设备 config
|
||||
* 只读,置 NULL 时框架忽略 config 写。 */
|
||||
void (*write_config)(u32 offset, u32 value);
|
||||
/* 通知:guest 写入 QUEUE_NOTIFY,后端处理该队列的请求。 */
|
||||
void (*queue_notify)(u32 queue_idx);
|
||||
/* 复位设备。 */
|
||||
@@ -153,13 +159,113 @@ int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx,
|
||||
* 由后端在写 used ring 元素后调用,内部做内存屏障。 */
|
||||
void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx);
|
||||
|
||||
/* VSCode Phase 2: 按 gpa_base 定位队列(用于 device_id 相同的多实例设备,
|
||||
* 如两个 virtio-input:键盘+鼠标同为 VIRTIO_ID_INPUT=18)。
|
||||
* 语义与 virtio_queue_get_ptrs 一致,只是查找键由 device_id 改为 gpa_base。 */
|
||||
int virtio_queue_get_ptrs_by_gpa(u64 gpa_base, u32 queue_idx,
|
||||
struct virtq_desc **desc_out,
|
||||
struct virtq_avail **avail_out,
|
||||
struct virtq_used **used_out,
|
||||
u32 *queue_num_out);
|
||||
|
||||
/* 异步触发设备中断(设置 INTERRUPT_STATUS bit0 并向 guest 注入虚拟 IRQ)。
|
||||
* 用于后端在 QUEUE_NOTIFY 上下文之外完成工作(如周期轮询收到 RX 包)。
|
||||
* device_id: VIRTIO_ID_*;设备未注册或 irq==0 时为空操作。 */
|
||||
void virtio_mmio_raise_irq(u32 device_id);
|
||||
|
||||
/* VSCode Phase 2: 按 gpa_base 触发中断。用于 device_id 相同的多实例设备
|
||||
*(两个 virtio-input 同为 18):virtio_mmio_raise_irq(18) 只命中第一个。 */
|
||||
void virtio_mmio_raise_irq_by_gpa(u64 gpa_base);
|
||||
|
||||
/* 周期轮询 virtio-net RX 路径(填充 guest 已投递的 RX buffer,
|
||||
* 有填充时自动注入 IRQ)。由 vmexit 的 preemption timer 路径调用。 */
|
||||
void virtio_net_poll(void);
|
||||
|
||||
/* ===== VSCode Phase 2: virtio-input 后端 ===== */
|
||||
|
||||
/* Linux input event types/codes (subset, see <uapi/linux/input-event-codes.h>).
|
||||
* We only need the few event types we actually forward from Deshab. */
|
||||
#define VIO_INPUT_EV_KEY 0x01
|
||||
#define VIO_INPUT_EV_REL 0x02 /* relative movement (mouse) */
|
||||
#define VIO_INPUT_EV_ABS 0x03
|
||||
#define VIO_INPUT_EV_SYN 0x00 /* sync marker */
|
||||
|
||||
#define VIO_INPUT_REL_X 0x00
|
||||
#define VIO_INPUT_REL_Y 0x01
|
||||
#define VIO_INPUT_BTN_LEFT 0x110
|
||||
#define VIO_INPUT_BTN_RIGHT 0x111
|
||||
#define VIO_INPUT_BTN_MIDDLE 0x112
|
||||
|
||||
/* virtio_input_event (8 bytes, little-endian). Matches Linux struct
|
||||
* virtio_input_event in <uapi/linux/virtio_input.h>. */
|
||||
struct virtio_input_event {
|
||||
u16 type;
|
||||
u16 code;
|
||||
u32 value;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* 注册键盘+鼠标两个 virtio-input 后端(无设备时安全空操作)。
|
||||
* 在 virtio_mmio_init 中调用。 */
|
||||
void virtio_input_backend_init(void);
|
||||
|
||||
/* 推送键盘事件(type=EV_KEY, code=linux keycode, value=0/1/2)。
|
||||
* value: 0=release, 1=press, 2=repeat。 */
|
||||
void virtio_input_push_keyboard(u16 code, u32 value);
|
||||
|
||||
/* 推送鼠标事件。三种便捷封装:
|
||||
* push_mouse_rel(axis, value) — 相对位移 (EV_REL, REL_X/REL_Y)
|
||||
* push_mouse_button(btn, value)— 按键 (EV_KEY, BTN_LEFT/RIGHT/MIDDLE)
|
||||
* push_mouse_syn() — 同步标记 (EV_SYN) */
|
||||
void virtio_input_push_mouse_rel(u16 axis, i32 value);
|
||||
void virtio_input_push_mouse_button(u16 btn, u32 value);
|
||||
void virtio_input_push_mouse_syn(void);
|
||||
|
||||
/* 周期轮询:把挂起的输入事件排空到 guest eventq 已投递的空 buffer 中,
|
||||
* 有事件投递时自动注入 IRQ。由 vmexit 的 preemption timer 路径调用。 */
|
||||
void virtio_input_poll(void);
|
||||
|
||||
/* ===== VSCode Phase 3: virtio-gpu 2D 后端 ===== */
|
||||
|
||||
/* Scanout 查询结果:desktop 在 IDE attached 时读取,把 guest 图形 blit 到
|
||||
* Deshab framebuffer 的 IDE host 区域。
|
||||
* host_vaddr : scanout 影子缓冲的 host 虚拟地址(位于 graphics surface pool,
|
||||
* 16MB,足够 1920×1080×4)。guest virtio-gpu 驱动经
|
||||
* TRANSFER_TO_HOST_2D + RESOURCE_FLUSH 把帧合成到这里。
|
||||
* width/height/stride : scanout 几何(XRGB8888,stride = width*4)
|
||||
* dirty : 1 = 自上次查询以来有新帧合成(查询会清零该位)
|
||||
* enabled : 1 = scanout 已绑定一个 2D resource(X server 已 SET_SCANOUT)
|
||||
* dirty_x/y/w/h : Phase 7 累积 dirty rect(scanout 坐标系并集),
|
||||
* 查询随 dirty 一并清零;dirty=0 时全为 0。
|
||||
* desktop 据此只 blit 变化区域(尾部追加,ABI 兼容)。 */
|
||||
struct virtio_gpu_scanout_info {
|
||||
void *host_vaddr;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 stride;
|
||||
u32 dirty;
|
||||
u32 enabled;
|
||||
u32 dirty_x;
|
||||
u32 dirty_y;
|
||||
u32 dirty_w;
|
||||
u32 dirty_h;
|
||||
/* P7.6: hardware cursor state(尾部追加,ABI 兼容)。
|
||||
* cursor_visible=1 时 cursor_bitmap 指向 ARGB 像素(row stride=64),
|
||||
* desktop 在 blit 后 alpha-blend 叠加到 IDE host 区域。 */
|
||||
u32 cursor_visible;
|
||||
u32 cursor_x;
|
||||
u32 cursor_y;
|
||||
u32 cursor_hot_x;
|
||||
u32 cursor_hot_y;
|
||||
u32 cursor_w;
|
||||
u32 cursor_h;
|
||||
void *cursor_bitmap;
|
||||
};
|
||||
|
||||
/* 注册 virtio-gpu 2D 后端(slot 5 @ 0xF4005000 IRQ10)。
|
||||
* 在 virtio_mmio_init 中调用。无 surface pool 时安全降级(scanout disabled)。 */
|
||||
void virtio_gpu_backend_init(void);
|
||||
|
||||
/* 查询当前 scanout 状态(desktop blit 用)。返回 0 成功,-1 后端未就绪。 */
|
||||
int virtio_gpu_get_scanout_info(struct virtio_gpu_scanout_info *out);
|
||||
|
||||
#endif /* UTSM_VIRTIO_MMIO_H */
|
||||
|
||||
@@ -48,9 +48,14 @@ extern volatile struct vmexit_guest_regs g_guest_regs;
|
||||
* handle_hlt 检查此标志:Linux guest HLT 时 park(推进 RIP,exit-to-host),
|
||||
* self-test guest HLT 时 terminate。
|
||||
* g_guest_parked: 1 = Linux guest 已 HLT 驻留,可被 linux_resume() 唤醒。
|
||||
* linux_compat_service 通过此标志判断 guest 是否就绪。 */
|
||||
* linux_compat_service 通过此标志判断 guest 是否就绪。
|
||||
*
|
||||
* g_xj380_guest_active: 1 = 当前运行的 guest 是 OpenXJ380 guest。
|
||||
* handle_hlt 检查此标志:XJ380 guest HLT 时推进 RIP 并立即 vmresume
|
||||
* (继续执行,不 park 不 terminate)。同一时刻只有一个 guest active。 */
|
||||
extern volatile int g_linux_guest_active;
|
||||
extern volatile int g_guest_parked;
|
||||
extern volatile int g_xj380_guest_active;
|
||||
|
||||
/* ===== VM-Exit host 恢复点(vmm.c 定义,vmm.c / linux_boot.c / linux_resume.c 共用) =====
|
||||
*
|
||||
|
||||
@@ -73,10 +73,10 @@
|
||||
#define VMCS_GUEST_GS_SELECTOR 0x080A
|
||||
#define VMCS_GUEST_LDTR_SELECTOR 0x080C
|
||||
#define VMCS_GUEST_TR_SELECTOR 0x080E
|
||||
#define VMCS_GUEST_CS_LIMIT 0x4800
|
||||
#define VMCS_GUEST_ES_LIMIT 0x4800
|
||||
#define VMCS_GUEST_CS_LIMIT 0x4802
|
||||
#define VMCS_GUEST_SS_LIMIT 0x4804
|
||||
#define VMCS_GUEST_DS_LIMIT 0x4806
|
||||
#define VMCS_GUEST_ES_LIMIT 0x4801
|
||||
#define VMCS_GUEST_FS_LIMIT 0x4808
|
||||
#define VMCS_GUEST_GS_LIMIT 0x480A
|
||||
#define VMCS_GUEST_LDTR_LIMIT 0x480C
|
||||
@@ -95,6 +95,7 @@
|
||||
#define VMCS_GUEST_PREEMPTION_TIMER 0x482E
|
||||
|
||||
/* 64-bit guest-state fields(与 PDPTR 共享编码,仅在 EPT 启用时为 EFER/PAT) */
|
||||
#define VMCS_GUEST_LINK_POINTER 0x2800 /* P8.4: must be 0xFFFFFFFFFFFFFFFF */
|
||||
#define VMCS_GUEST_IA32_EFER 0x2806
|
||||
#define VMCS_GUEST_IA32_PAT 0x2804
|
||||
#define VMCS_GUEST_IA32_PERF_GLOBAL_CTRL 0x2808
|
||||
@@ -265,6 +266,7 @@ int vmx_enable(void);
|
||||
int vmx_disable(void);
|
||||
int vmx_vmcs_alloc(u64 *phys_out);
|
||||
int vmx_vmcs_load(u64 phys);
|
||||
int vmx_vmcs_clear(u64 phys); /* P8.4: VMCLEAR before VMLAUNCH */
|
||||
u64 vmx_vmcs_read(u64 field);
|
||||
void vmx_vmcs_write(u64 field, u64 value);
|
||||
int vmx_vmlaunch(void);
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#ifndef UTSM_XJ380_LOADER_H
|
||||
#define UTSM_XJ380_LOADER_H
|
||||
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* xj380_loader.h — OpenXJ380 (kernel.krl) guest loader for UTSM VMX non-root guest.
|
||||
*
|
||||
* 三内核架构路线 B:UTSM hypervisor 同时运行 Linux 与 OpenXJ380 两个 VM guest。
|
||||
* 本模块把 OpenXJ380 编译产物 kernel.krl(固定链接 0xFFFFFFFF80000000 的
|
||||
* ELF64 ET_EXEC)作为第二 guest 加载:
|
||||
* - 解析 ELF PT_LOAD 段 → 装入 guest 物理内存
|
||||
* - 构造 guest 初始页表(恒等 + HHDM + 内核区三区布局,同 XJ380 引导器契约)
|
||||
* - 构造 BOOT_CONFIG / FrameBufferConfig / 伪 EFI_SYSTEM_TABLE 交接
|
||||
* - 构造 MADT/FADT/HPET/MCFG 桩表(LAPIC/IOAPIC/HPET/MMCONF 均为幽灵地址)
|
||||
* - 建立 EPT 映射(boot area / framebuffer / kernel / RAM)
|
||||
*
|
||||
* XJ380 内核侧启动契约(证据见 OpenXJ380/boot/include/boot.h 与
|
||||
* OpenXJ380/kernel/main.cpp / memory/frame.cpp):
|
||||
* - KernelMain(fbc, SystemTable, BootConfig) — 3 个 SysV 参数,引用退化为指针
|
||||
* - BOOT_CONFIG 在入口立即被 memcpy 拷贝(main.cpp:281),原缓冲仅需入口可读
|
||||
* - MemoryMap.Buffer / saved_mtrrs 是物理地址,内核自行 +0xffff800000000000
|
||||
* - temp_stack[] 是 HHDM 虚拟地址数组(仅拷贝,1 CPU 时不 deref)
|
||||
* - MADT 在清低半(main.cpp:392)之前按物理地址直读,之后经 HHDM 读
|
||||
*
|
||||
* 里程碑 M1:guest 启动至根文件系统挂载(预期在 mount_root 死循环处停止,
|
||||
* 无虚拟块设备)。串口经 UTSM COM1 模拟可见 XJ380 启动全程。
|
||||
*/
|
||||
|
||||
/* ===== XJ380 guest GPA 布局 =====
|
||||
*
|
||||
* 0x00000000-0x0000FFFF : 保留(XJ380 AP 跳板区 APU_BASE_ADDR=0x10000)
|
||||
* 0x00020000-0x00033FFF : UTSM loader 私有 boot area(<1MB,内核不分配)
|
||||
* page tables / GDT / ACPI 桩表 / 内存图 /
|
||||
* BOOT_CONFIG / MTRR 缓冲 / stack / 伪 EFI 表
|
||||
* 0x00100000-0x00400000 : framebuffer 1024x768x4(3MB,隐式保留)
|
||||
* 0x01000000-0x02FFFFFF : kernel.krl 内核镜像窗口(32MB,隐式保留)
|
||||
* 0x03000000-0x43000000 : guest RAM 1GB(内存图中唯一的 Conventional 区)
|
||||
*/
|
||||
#define XJ380_KERNEL_VADDR 0xFFFFFFFF80000000ULL /* XJ380 linker.ld 固定链接地址 */
|
||||
#define XJ380_HHDM_OFFSET 0xFFFF800000000000ULL /* XJ380 HHDM 常量(hhdm.cpp:15) */
|
||||
|
||||
#define XJ380_BOOT_AREA_GPA 0x00020000ULL /* 128KB,避开 AP 跳板 0x10000 */
|
||||
#define XJ380_PGT_GPA (XJ380_BOOT_AREA_GPA + 0x0000) /* 8 页: PML4/PDPT0/PD0-3/PDPT_K/PD_K */
|
||||
#define XJ380_PGT_SIZE (8 * 4096)
|
||||
#define XJ380_GDT_GPA (XJ380_PGT_GPA + XJ380_PGT_SIZE)
|
||||
#define XJ380_MADT_GPA (XJ380_GDT_GPA + 0x1000)
|
||||
#define XJ380_FADT_GPA (XJ380_MADT_GPA + 0x1000)
|
||||
#define XJ380_MCFG_GPA (XJ380_FADT_GPA + 0x1000)
|
||||
#define XJ380_HPET_GPA (XJ380_MCFG_GPA + 0x1000)
|
||||
#define XJ380_MEMMAP_GPA (XJ380_HPET_GPA + 0x1000)
|
||||
#define XJ380_BOOTCFG_GPA (XJ380_MEMMAP_GPA + 0x1000)
|
||||
#define XJ380_MTRR_GPA (XJ380_BOOTCFG_GPA + 0x1000)
|
||||
#define XJ380_STACK_GPA (XJ380_MTRR_GPA + 0x2000) /* 8KB guest 启动栈 */
|
||||
#define XJ380_FAKEEFI_GPA (XJ380_STACK_GPA + 0x2000)
|
||||
#define XJ380_FBC_GPA (XJ380_FAKEEFI_GPA + 0x1000)
|
||||
#define XJ380_BOOT_AREA_SIZE (XJ380_FBC_GPA + 0x1000 - XJ380_BOOT_AREA_GPA) /* 至 FBC 结束 */
|
||||
#define XJ380_BOOT_STACK_TOP (XJ380_STACK_GPA + 0x2000)
|
||||
|
||||
#define XJ380_GUEST_FB_GPA 0x00100000ULL /* 1MB */
|
||||
#define XJ380_FB_WIDTH 1024
|
||||
#define XJ380_FB_HEIGHT 768
|
||||
#define XJ380_FB_BPP 4
|
||||
#define XJ380_FB_SIZE ((u64)XJ380_FB_WIDTH * XJ380_FB_HEIGHT * XJ380_FB_BPP)
|
||||
|
||||
#define XJ380_GUEST_KERNEL_GPA 0x01000000ULL /* 16MB */
|
||||
#define XJ380_GUEST_KERNEL_MAX (32 * 1024 * 1024) /* 内核窗口 16MB..48MB */
|
||||
|
||||
#define XJ380_GUEST_RAM_GPA 0x03000000ULL /* 48MB */
|
||||
#define XJ380_GUEST_RAM_SIZE 0x40000000ULL /* 1GB (0x03000000..0x43000000) */
|
||||
#define XJ380_GUEST_RAM_MIN 0x10000000ULL /* 降级下限 256MB */
|
||||
|
||||
/* BOOT_CONFIG.boot_flags(与 OpenXJ380 boot/include/boot.h 一致) */
|
||||
#define XJ380_BOOT_FLAG_DISABLE_KMOD (1ULL << 2)
|
||||
#define XJ380_BOOT_FLAG_SAFE_STORAGE_IO (1ULL << 3)
|
||||
|
||||
/* ===== 加载后的 XJ380 guest 状态 ===== */
|
||||
struct xj380_guest_info {
|
||||
u64 kernel_gpa; /* 内核段装入的 GPA 基址 */
|
||||
u64 kernel_entry; /* 64 位入口(guest 虚拟地址 0xFFFFFFFF80000000+) */
|
||||
u64 kernel_size; /* 内核跨度(memsz 上界 - 基址) */
|
||||
u64 pgt_gpa; /* guest CR3(页表 GPA) */
|
||||
u64 gdt_gpa; /* guest GDT GPA */
|
||||
u64 ram_gpa; /* guest RAM GPA 基址 */
|
||||
u64 ram_size; /* guest RAM 实际大小 */
|
||||
u64 fb_gpa; /* framebuffer GPA */
|
||||
int loaded; /* 1 = 加载成功 */
|
||||
};
|
||||
|
||||
/* 在 Limine boot module 中查找 XJ380 内核(路径含 "xj380")。
|
||||
* 返回模块数据指针,*size_out 为大小;未找到返回 NULL。 */
|
||||
void *xj380_find_kernel_module(u64 *size_out);
|
||||
|
||||
/* 初始化 XJ380 guest:解析 ELF、分配内存、构造页表/GDT/ACPI 桩表/
|
||||
* BOOT_CONFIG/内存图、EPT 映射。返回 0 成功,负值失败。 */
|
||||
int xj380_loader_init(void);
|
||||
|
||||
/* 获取加载后的 guest 信息(未加载返回 NULL)。 */
|
||||
const struct xj380_guest_info *xj380_get_guest_info(void);
|
||||
|
||||
/* 配置 VMCS 并 vmlaunch XJ380 guest。
|
||||
* 前置条件:vmm_init() + xj380_loader_init()。
|
||||
* guest 终止(异常/三重故障/0xCF9 重启)后返回;期间 host 挂起。
|
||||
* 返回 0 表示 guest 终止返回,负值表示启动失败。 */
|
||||
int xj380_launch(void);
|
||||
|
||||
#endif /* UTSM_XJ380_LOADER_H */
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <utsm/linux_compat.h>
|
||||
#include <utsm/instr.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
#include "../../tools/fat32_lfn.h"
|
||||
|
||||
#define DSK_PATH "/system/deshab64/deshab.elf"
|
||||
#define DSK_CMDLINE "dsk:main"
|
||||
@@ -176,8 +177,9 @@ static struct limine_file *dsk_find_module(void) {
|
||||
/* ---- FAT32 block-provider file reader ---- */
|
||||
static u8 g_dsk_fat32_disk[131072]; /* 256-sector BPB+FAT+root dir buffer (与 DSK 端一致) */
|
||||
static u8 g_dsk_fat32_cluster[4096]; /* 8-sector cluster buffer */
|
||||
/* 实机要求: 文件缓冲区足够大容纳 deshab.elf (~1.5MB) */
|
||||
static u8 g_dsk_fat32_filedata[2097152]; /* 2MB file data buffer */
|
||||
/* 实机要求: 文件缓冲区足够容纳 deshab.elf (~1.5MB) 及 SYSTEM/lib 下的
|
||||
* 大型 DLL(shell32.dll ~8MB, uiautomationcore.dll ~4.3MB) */
|
||||
static u8 g_dsk_fat32_filedata[10485760]; /* 10MB file data buffer */
|
||||
/* BUG-019 修复: FAT 按需扇区读取缓冲区(仅 512 字节),
|
||||
* 当 FAT 条目偏移超出 g_dsk_fat32_disk (128KB) 时使用。 */
|
||||
static u8 g_dsk_fat32_fat_sec[512];
|
||||
@@ -344,11 +346,13 @@ static int dsk_load_from_block_fat32(const void **out_addr, u64 *out_size) {
|
||||
|
||||
/* ---- FAT32 subdirectory traversal ---- */
|
||||
|
||||
/* 在指定起始 cluster 的目录项中查找 name11。
|
||||
/* 在指定起始 cluster 的目录项中查找 name11(8.3)或 long_name(LFN 长名)。
|
||||
* long_name 为 NULL 时仅按 8.3 匹配(向后兼容)。
|
||||
* is_dir=1 时只匹配子目录(attr & 0x10),is_dir=0 时只匹配文件。
|
||||
* 返回 0 成功,out_clus/out_size 返回匹配项的起始 cluster 和大小。 */
|
||||
* 返回 0 成功,out_clus/out_size 返回匹配项的起始 cluster 和大小。
|
||||
* 注意:LFN 缓冲跨簇遍历持续存活,支持跨簇边界的 LFN 链。 */
|
||||
static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir,
|
||||
u32 *out_clus, u32 *out_size) {
|
||||
u32 *out_clus, u32 *out_size, const char *long_name) {
|
||||
const dkm_kernel_api *api = dkm_get_kernel_api();
|
||||
if (!api || !api->block || !api->block->device_count) return -1;
|
||||
|
||||
@@ -360,6 +364,10 @@ static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir,
|
||||
u32 data_start_sec = bpb->reserved_sector_count + (u32)bpb->fat_count * bpb->sectors_per_fat;
|
||||
u32 spc = bpb->sectors_per_cluster;
|
||||
|
||||
/* LFN 缓冲必须在簇循环外声明:跨簇 LFN 链需要持续存活 */
|
||||
fat32_lfn_buf lfn;
|
||||
fat32_lfn_init(&lfn);
|
||||
|
||||
u32 clus = start_clus;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 clus_lba = data_start_sec + (clus - 2) * spc;
|
||||
@@ -367,22 +375,50 @@ static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir,
|
||||
int st = api->block->read(0, clus_lba, spc, cb);
|
||||
if (st != 0) return -3;
|
||||
|
||||
const fat32_dir_entry *dir = (const fat32_dir_entry *)cb;
|
||||
for (u32 e = 0; e * 32 < spc * 512; e++) {
|
||||
if (dir[e].name[0] == 0) break;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (!fat32_name11_eq(dir[e].name, name11)) continue;
|
||||
|
||||
/* 名称匹配,检查类型 */
|
||||
int entry_is_dir = (dir[e].attr & 0x10) ? 1 : 0;
|
||||
if (entry_is_dir != is_dir) continue;
|
||||
|
||||
*out_clus = fat32_read_u16((const u8 *)&dir[e].cluster_low) |
|
||||
((u32)fat32_read_u16((const u8 *)&dir[e].cluster_high) << 16);
|
||||
*out_size = dir[e].file_size;
|
||||
return 0;
|
||||
const u8 *entries = (const u8 *)cb;
|
||||
u32 entry_count = (spc * 512) / 32;
|
||||
for (u32 e = 0; e < entry_count; e++) {
|
||||
const u8 *entry = entries + e * 32;
|
||||
if (entry[0] == 0) break;
|
||||
if ((u8)entry[0] == 0xE5) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
const fat32_dir_entry *de = (const fat32_dir_entry *)entry;
|
||||
int is_short = fat32_lfn_process(&lfn, entry);
|
||||
if (!is_short) continue; /* LFN 片段 */
|
||||
if (de->attr & 0x08) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue; /* 卷标 */
|
||||
}
|
||||
int entry_is_dir = (de->attr & 0x10) ? 1 : 0;
|
||||
if (entry_is_dir != is_dir) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
/* 匹配检查:先 8.3 短名,再 LFN 长名 */
|
||||
int matched = 0;
|
||||
if (name11 && fat32_name11_eq(de->name, name11)) {
|
||||
matched = 1;
|
||||
} else if (long_name) {
|
||||
if (lfn.valid) {
|
||||
char ascii[FAT32_LFN_MAX];
|
||||
int n = fat32_lfn_to_ascii(&lfn, ascii, sizeof(ascii));
|
||||
if (n >= 0 && fat32_lfn_streq_ci(ascii, long_name)) matched = 1;
|
||||
}
|
||||
if (!matched) {
|
||||
char short_disp[13];
|
||||
fat32_lfn_short_to_str((const u8 *)de->name, short_disp);
|
||||
if (fat32_lfn_streq_ci(short_disp, long_name)) matched = 1;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
*out_clus = fat32_read_u16((const u8 *)&de->cluster_low) |
|
||||
((u32)fat32_read_u16((const u8 *)&de->cluster_high) << 16);
|
||||
*out_size = de->file_size;
|
||||
return 0;
|
||||
}
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
|
||||
/* 跟随 FAT 链 — BUG-019: 使用按需 FAT 读取 */
|
||||
@@ -392,7 +428,10 @@ static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir,
|
||||
}
|
||||
|
||||
/* 按路径查找文件并读取内容。
|
||||
* path: '/' 分隔的 8.3 格式路径(如 "SYSTEM /DESHAB64 /FUCK ")。
|
||||
* path: '/' 分隔的路径。支持两种格式:
|
||||
* 1) 8.3 路径(如 "SYSTEM /DESHAB64 /FUCK ",每段恰好 11 字符)
|
||||
* 2) 长名路径(如 "lib/libtest.so")
|
||||
* 同时保留 8.3 短名(补空格)和原始长名传给 fat32_find_in_dir。
|
||||
* out_data: 返回在 g_dsk_fat32_filedata 中的文件数据指针。
|
||||
* out_size: 返回文件大小。
|
||||
* 返回 0 成功。 */
|
||||
@@ -419,15 +458,19 @@ int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) {
|
||||
u32 cur_clus = bpb->root_cluster; /* 从根目录开始 */
|
||||
|
||||
while (pos < path_len) {
|
||||
/* 提取一个路径组件(到下一个 '/' 或结尾) */
|
||||
char comp[12]; /* 11 字符 8.3 名 + \0 */
|
||||
u32 ci = 0;
|
||||
while (pos < path_len && path[pos] != '/' && ci < 11) {
|
||||
comp[ci++] = path[pos++];
|
||||
/* 提取一个路径组件(到下一个 '/' 或结尾),同时保留原始串用于 LFN 匹配 */
|
||||
char comp[12]; /* 11 字符 8.3 名 + \0 */
|
||||
char orig[260]; /* 原始长名 */
|
||||
u32 ci = 0, oi = 0;
|
||||
while (pos < path_len && path[pos] != '/') {
|
||||
if (ci < 11) comp[ci++] = path[pos];
|
||||
if (oi + 1 < sizeof(orig)) orig[oi++] = path[pos];
|
||||
pos++;
|
||||
}
|
||||
/* 不足 11 字符则补空格 */
|
||||
while (ci < 11) comp[ci++] = ' ';
|
||||
comp[11] = 0;
|
||||
orig[oi] = 0;
|
||||
|
||||
/* 跳过 '/' */
|
||||
if (pos < path_len && path[pos] == '/') pos++;
|
||||
@@ -438,7 +481,8 @@ int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) {
|
||||
if (is_last) {
|
||||
/* 最后一级:查找文件 */
|
||||
u32 found_clus = 0, found_size = 0;
|
||||
int rc = fat32_find_in_dir(cur_clus, comp, 0, &found_clus, &found_size);
|
||||
int rc = fat32_find_in_dir(cur_clus, comp, 0, &found_clus, &found_size,
|
||||
(oi > 0 && oi != 11) ? orig : 0);
|
||||
if (rc != 0) return -4;
|
||||
|
||||
if (found_size > sizeof(g_dsk_fat32_filedata)) return -5;
|
||||
@@ -466,7 +510,8 @@ int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) {
|
||||
} else {
|
||||
/* 中间级:查找子目录 */
|
||||
u32 sub_clus = 0, sub_size = 0;
|
||||
int rc = fat32_find_in_dir(cur_clus, comp, 1, &sub_clus, &sub_size);
|
||||
int rc = fat32_find_in_dir(cur_clus, comp, 1, &sub_clus, &sub_size,
|
||||
(oi > 0 && oi != 11) ? orig : 0);
|
||||
if (rc != 0) return -7;
|
||||
cur_clus = sub_clus;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ kernel/dsk_loader.o: kernel\dsk_loader.c include\utsm\dsk.h \
|
||||
include\utsm\dma.h include\utsm\block.h include\utsm\net.h \
|
||||
include\utsm\paging.h include\utsm\log.h include\utsm\pe.h \
|
||||
include\utsm\linux_compat.h include\utsm\instr.h \
|
||||
kernel\..\arch\x86_64\limine.h
|
||||
kernel\..\arch\x86_64\limine.h kernel\..\..\tools\fat32_lfn.h
|
||||
include\utsm\dsk.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\arena.h:
|
||||
@@ -17,3 +17,4 @@ include\utsm\pe.h:
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\instr.h:
|
||||
kernel\..\arch\x86_64\limine.h:
|
||||
kernel\..\..\tools\fat32_lfn.h:
|
||||
|
||||
Binary file not shown.
+39
-7
@@ -1,10 +1,42 @@
|
||||
#include <utsm/log.h>
|
||||
|
||||
/* ---- 内存日志缓冲区(实机启动日志持久化) ----
|
||||
* 默认启用捕获:所有日志同时写串口和内存缓冲。
|
||||
* 块设备就绪后由 disk_log_flush() 把缓冲区写入磁盘原始扇区。 */
|
||||
#define LOG_BUF_SIZE 524288u /* 512KB, 覆盖 LBA 2-1000 (999 扇区 ~500KB) */
|
||||
static char g_log_buf[LOG_BUF_SIZE];
|
||||
static u32 g_log_len = 0;
|
||||
static int g_log_capture_on = 1;
|
||||
|
||||
void log_capture_enable(int enable) {
|
||||
g_log_capture_on = enable;
|
||||
}
|
||||
|
||||
const char *log_get_buffer(void) {
|
||||
return g_log_buf;
|
||||
}
|
||||
|
||||
u32 log_get_length(void) {
|
||||
return g_log_len;
|
||||
}
|
||||
|
||||
static void log_emit(const char *s) {
|
||||
serial_write(s);
|
||||
if (g_log_capture_on && s) {
|
||||
while (*s) {
|
||||
if (g_log_len < LOG_BUF_SIZE) {
|
||||
g_log_buf[g_log_len++] = *s;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void log_line(const char *level, const char *msg) {
|
||||
serial_write(level);
|
||||
serial_write(" ");
|
||||
serial_write(msg);
|
||||
serial_write("\n");
|
||||
log_emit(level);
|
||||
log_emit(" ");
|
||||
log_emit(msg);
|
||||
log_emit("\n");
|
||||
}
|
||||
|
||||
void log_info(const char *msg) {
|
||||
@@ -28,7 +60,7 @@ void log_hex64(const char *prefix, u64 value) {
|
||||
buf[2 + i] = hex[(value >> ((15 - i) * 4)) & 0xf];
|
||||
}
|
||||
buf[18] = 0;
|
||||
serial_write(prefix);
|
||||
serial_write(buf);
|
||||
serial_write("\n");
|
||||
log_emit(prefix);
|
||||
log_emit(buf);
|
||||
log_emit("\n");
|
||||
}
|
||||
|
||||
Binary file not shown.
+245
-45
@@ -9,9 +9,11 @@
|
||||
#include <utsm/net.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/linux_compat.h>
|
||||
#include <utsm/xj380_loader.h>
|
||||
#include <utsm/instr.h>
|
||||
#include "ini_parser.h"
|
||||
#include "../arch/x86_64/limine.h"
|
||||
#include "../pe/pe_dll_manager.h"
|
||||
|
||||
void arch_halt_forever(void);
|
||||
|
||||
@@ -20,6 +22,10 @@ extern volatile struct limine_module_request g_module_request;
|
||||
/* dkm/manifest.c: 带 FUCK [drivers] 过滤的 manifest 加载入口 */
|
||||
void dsm_load_by_manifest_ex(const ini_config *cfg);
|
||||
|
||||
/* dsk_loader.c: 按路径读取 FAT32 文件到 g_dsk_fat32_filedata 静态缓冲。
|
||||
* Phase 1 已支持 LFN 长文件名 + 子目录遍历。 */
|
||||
extern int fat32_read_path(const char *path, u8 **out_data, u32 *out_size);
|
||||
|
||||
/* ---- 运行期配置全局变量(从 FUCK 文件初始化) ---- */
|
||||
u32 g_utsm_max_segments = UTSM_MAX_SEGMENTS_DEFAULT;
|
||||
u32 g_utsm_max_capabilities = UTSM_MAX_CAPABILITIES_DEFAULT;
|
||||
@@ -27,6 +33,71 @@ u32 g_utsm_max_pckc_keys = UTSM_MAX_PCKC_KEYS_DEFAULT;
|
||||
u64 g_utsm_arena_size = UTSM_ARENA_SIZE_DEFAULT;
|
||||
u64 g_utsm_dirty_shard_pages = UTSM_DIRTY_SHARD_PAGES_DEFAULT;
|
||||
|
||||
/* ---- 兼容层配置全局变量([compat] 段,在所有使用它们的函数前定义) ---- */
|
||||
char g_compat_lib_path[32] = "lib"; /* FAT32 根目录下的库子目录名 */
|
||||
char g_compat_linux_guest_path[64] = "/usr/lib/deshab"; /* Linux guest 内库目录路径 */
|
||||
int g_compat_linux_lib_sync = 1; /* 启动时推送 .so 到 Linux guest */
|
||||
int g_compat_pe_dll_search = 1; /* PE 加载时从 FAT32 lib 搜索真实 DLL */
|
||||
int g_compat_vscode_install = 1; /* 启动时安装 VSCode tarball 到 guest /opt */
|
||||
|
||||
/* 将 FUCK [compat] 区的配置应用到兼容层全局变量。
|
||||
* 必须在 utsm_apply_config 之后调用。 */
|
||||
static void utsm_apply_compat_config(const ini_config *cfg) {
|
||||
if (!cfg) return;
|
||||
const char *lib_path = ini_get(cfg, "compat", "lib_path");
|
||||
if (lib_path && lib_path[0]) {
|
||||
int i = 0;
|
||||
while (lib_path[i] && i + 1 < (int)sizeof(g_compat_lib_path)) {
|
||||
g_compat_lib_path[i] = lib_path[i];
|
||||
i++;
|
||||
}
|
||||
g_compat_lib_path[i] = 0;
|
||||
}
|
||||
const char *guest_path = ini_get(cfg, "compat", "linux_lib_guest_path");
|
||||
if (guest_path && guest_path[0]) {
|
||||
int i = 0;
|
||||
while (guest_path[i] && i + 1 < (int)sizeof(g_compat_linux_guest_path)) {
|
||||
g_compat_linux_guest_path[i] = guest_path[i];
|
||||
i++;
|
||||
}
|
||||
g_compat_linux_guest_path[i] = 0;
|
||||
}
|
||||
g_compat_linux_lib_sync = ini_get_bool(cfg, "compat", "linux_lib_sync", 1);
|
||||
g_compat_pe_dll_search = ini_get_bool(cfg, "compat", "pe_dll_search", 1);
|
||||
g_compat_vscode_install = ini_get_bool(cfg, "compat", "vscode_install", 1);
|
||||
|
||||
log_info("[UTSM] compat config applied");
|
||||
log_info("[UTSM] lib_path=");
|
||||
log_info(g_compat_lib_path);
|
||||
log_info("[UTSM] linux_guest_path=");
|
||||
log_info(g_compat_linux_guest_path);
|
||||
log_hex64("[UTSM] linux_lib_sync=", (u64)g_compat_linux_lib_sync);
|
||||
log_hex64("[UTSM] pe_dll_search=", (u64)g_compat_pe_dll_search);
|
||||
log_hex64("[UTSM] vscode_install=", (u64)g_compat_vscode_install);
|
||||
}
|
||||
|
||||
/* DLL 文件读取回调:从 FAT32 根目录下 lib/ 子目录读取 DLL。
|
||||
* 路径构造成 "<g_compat_lib_path>/<name>"(lib 在 FAT32 根,不是 SYSTEM/lib!)。 */
|
||||
static int dll_reader_from_fat32(const char *name, u8 **out_data, u32 *out_size) {
|
||||
if (!name || !out_data || !out_size) return -1;
|
||||
char path[128];
|
||||
int pl = 0;
|
||||
/* 拼接 g_compat_lib_path */
|
||||
int i = 0;
|
||||
while (g_compat_lib_path[i] && pl + 1 < (int)sizeof(path)) {
|
||||
path[pl++] = g_compat_lib_path[i++];
|
||||
}
|
||||
if (pl + 1 >= (int)sizeof(path)) return -1;
|
||||
path[pl++] = '/';
|
||||
/* 拼接 name */
|
||||
i = 0;
|
||||
while (name[i] && pl + 1 < (int)sizeof(path)) {
|
||||
path[pl++] = name[i++];
|
||||
}
|
||||
path[pl] = 0;
|
||||
return fat32_read_path(path, out_data, out_size);
|
||||
}
|
||||
|
||||
/* 将 FUCK [utsm] 区的配置应用到运行期全局变量 */
|
||||
static void utsm_apply_config(const ini_config *cfg) {
|
||||
if (!cfg) return;
|
||||
@@ -40,6 +111,9 @@ static void utsm_apply_config(const ini_config *cfg) {
|
||||
log_hex64("[UTSM] max_segments=", g_utsm_max_segments);
|
||||
log_hex64("[UTSM] max_pckc_keys=", g_utsm_max_pckc_keys);
|
||||
log_hex64("[UTSM] dirty_shard_pages=", g_utsm_dirty_shard_pages);
|
||||
|
||||
/* 兼容层配置([compat] 段)— 复用同一 cfg 对象 */
|
||||
utsm_apply_compat_config(cfg);
|
||||
}
|
||||
|
||||
/* ---- 全局启动配置(从 FUCK 文件读取) ---- */
|
||||
@@ -75,6 +149,73 @@ static int utsm_load_fuck_config(ini_config *cfg) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* === 实机日志持久化:把内存日志缓冲区写入磁盘原始扇区 ===
|
||||
* 开发测试用:实机无串口时,启动后用 WinHex/dd 读取磁盘对应扇区即可查看日志。
|
||||
* 扇区范围由 FUCK [boot] disk_log_start / disk_log_end 配置(默认 2-1000)。
|
||||
* 注意: 写入 LBA 1-33 会覆盖 GPT header/entries,重启后 Limine 可能无法找到 ESP。
|
||||
* 建议测试盘从 LBA 34 开始,或测试后重新烧录镜像。 */
|
||||
static void disk_log_flush(int start_lba, int end_lba) {
|
||||
const dkm_kernel_api *api = dkm_get_kernel_api();
|
||||
if (!api || !api->block || !api->block->write || !api->block->device_count) {
|
||||
log_warn("[UTSM] disk_log: block device unavailable, skip");
|
||||
return;
|
||||
}
|
||||
if (api->block->device_count() == 0) {
|
||||
log_warn("[UTSM] disk_log: no block device, skip");
|
||||
return;
|
||||
}
|
||||
if (end_lba <= start_lba) {
|
||||
log_warn("[UTSM] disk_log: invalid sector range");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *buf = log_get_buffer();
|
||||
u32 len = log_get_length();
|
||||
if (len == 0) {
|
||||
log_warn("[UTSM] disk_log: log buffer empty, skip");
|
||||
return;
|
||||
}
|
||||
|
||||
u32 avail_sectors = (u32)(end_lba - start_lba + 1);
|
||||
u32 avail_bytes = avail_sectors * 512;
|
||||
u32 write_bytes = (len < avail_bytes) ? len : avail_bytes;
|
||||
u32 full_sectors = write_bytes / 512;
|
||||
u32 rem_bytes = write_bytes % 512;
|
||||
u64 lba = (u64)start_lba;
|
||||
u32 offset = 0;
|
||||
|
||||
/* 写完整扇区(AHCI 单次最多 8 扇区,block API 内部分块) */
|
||||
while (full_sectors > 0) {
|
||||
u32 chunk = (full_sectors > 8) ? 8 : full_sectors;
|
||||
int st = api->block->write(0, lba, chunk, buf + offset);
|
||||
if (st != 0) {
|
||||
log_error("[UTSM] disk_log: write failed");
|
||||
log_hex64("[UTSM] disk_log: lba=", lba);
|
||||
return;
|
||||
}
|
||||
lba += chunk;
|
||||
offset += chunk * 512;
|
||||
full_sectors -= chunk;
|
||||
}
|
||||
|
||||
/* 最后一个不完整扇区:补零后写入 */
|
||||
if (rem_bytes > 0) {
|
||||
u8 tail[512];
|
||||
for (u32 i = 0; i < 512; i++) tail[i] = 0;
|
||||
for (u32 i = 0; i < rem_bytes; i++) tail[i] = (u8)buf[offset + i];
|
||||
int st = api->block->write(0, lba, 1, tail);
|
||||
if (st != 0) {
|
||||
log_error("[UTSM] disk_log: tail write failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("[UTSM] disk_log: flushed to disk");
|
||||
log_hex64("[UTSM] disk_log: start_lba=", (u64)start_lba);
|
||||
log_hex64("[UTSM] disk_log: end_lba=", (u64)end_lba);
|
||||
log_hex64("[UTSM] disk_log: bytes=", write_bytes);
|
||||
}
|
||||
|
||||
void kernel_main(void) {
|
||||
/* ---- 插桩: 启动总时间戳 ---- */
|
||||
INSTR_TS_DECL(boot_total);
|
||||
@@ -236,6 +377,93 @@ void kernel_main(void) {
|
||||
dkm_fill_platform_info();
|
||||
INSTR_TS_END(ts_dkm, "dkm_init");
|
||||
|
||||
/* === VMM — 可通过 FUCK [boot] vmm=0 跳过 ===
|
||||
* P8.4: Moved BEFORE DKM driver loading so VMM self-test runs even if
|
||||
* a stage2 driver (e.g. fat32) crashes under KVM nested VMX. */
|
||||
int run_vmm = ini_get_bool(cfg, "boot", "vmm", 1);
|
||||
if (run_vmm && vmm_init() == 0) {
|
||||
INSTR_TS_DECL(ts_vmm);
|
||||
INSTR_TS_BEGIN(ts_vmm);
|
||||
log_info("[UTSM] VMM init ok");
|
||||
int vmm_st = vmm_self_test();
|
||||
/* P8.4: On KVM nested VMX, self-test VMCS may fail but Linux guest VMCS
|
||||
* (configured separately) may still work. Force-continue regardless. */
|
||||
if (vmm_st != 0) {
|
||||
log_warn("[UTSM] VMM self-test FAIL, but continuing to Linux guest");
|
||||
vmm_st = 0; /* force pass to continue to Linux guest */
|
||||
}
|
||||
if (vmm_st == 0) {
|
||||
INSTR_TS_END(ts_vmm, "vmm_init+selftest");
|
||||
log_info("[UTSM] VMM self-test PASS");
|
||||
|
||||
/* === Linux guest — 可通过 FUCK [boot] linux_guest=0 跳过 === */
|
||||
int run_linux = ini_get_bool(cfg, "boot", "linux_guest", 1);
|
||||
if (run_linux && linux_loader_init() == 0) {
|
||||
INSTR_TS_DECL(ts_linux);
|
||||
INSTR_TS_BEGIN(ts_linux);
|
||||
log_info("[UTSM] Linux loader init ok");
|
||||
|
||||
if (ipc_shm_init() == 0) {
|
||||
log_info("[UTSM] IPC shm init ok");
|
||||
} else {
|
||||
log_error("[UTSM] IPC shm init failed (non-fatal)");
|
||||
}
|
||||
|
||||
int lin_st = linux_launch();
|
||||
INSTR_TS_END(ts_linux, "linux_launch");
|
||||
if (lin_st == 0) {
|
||||
log_info("[UTSM] Linux guest parked (daemon ready)");
|
||||
linux_compat_init();
|
||||
/* Phase 2: 启动时把 FAT32 lib 目录下 .so 全量推送到 Linux guest */
|
||||
if (g_compat_linux_lib_sync) {
|
||||
int lsrc = lxc_sync_lib_dir(g_compat_linux_guest_path);
|
||||
if (lsrc == 0) log_info("[UTSM] lib sync ok");
|
||||
else log_warn("[UTSM] lib sync skipped (non-fatal)");
|
||||
}
|
||||
/* VSCode Phase 5: tarball module 存在时装到 guest /opt/vscode
|
||||
* (已安装则秒跳过;失败不阻断启动,桌面降级显示未安装) */
|
||||
if (g_compat_vscode_install) {
|
||||
int vsrc = lxc_vscode_install();
|
||||
if (vsrc == 0) log_info("[UTSM] vscode ready");
|
||||
else log_warn("[UTSM] vscode install skipped (non-fatal)");
|
||||
}
|
||||
} else {
|
||||
log_error("[UTSM] Linux launch failed");
|
||||
log_hex64("[UTSM] Linux st=", (u64)(i64)lin_st);
|
||||
}
|
||||
} else {
|
||||
log_warn("[UTSM] Linux loader unavailable (no bzImage or disabled)");
|
||||
}
|
||||
} else {
|
||||
INSTR_TS_END(ts_vmm, "vmm_init+selftest");
|
||||
log_error("[UTSM] VMM self-test FAIL");
|
||||
log_hex64("[UTSM] VMM st=", (u64)(i64)vmm_st);
|
||||
}
|
||||
|
||||
/* === OpenXJ380 guest — 可通过 FUCK [boot] xj380_guest=0 跳过 ===
|
||||
* 三内核架构路线 B:XJ380 作为第二 guest(与 Linux 并列)。
|
||||
* 需要 Limine boot module xj380.krl(OpenXJ380 构建产物 kernel.krl)。
|
||||
* guest 终止前 host 挂起;无模块/被禁用时优雅跳过。 */
|
||||
int run_xj380 = ini_get_bool(cfg, "boot", "xj380_guest", 1);
|
||||
if (run_xj380 && xj380_loader_init() == 0) {
|
||||
INSTR_TS_DECL(ts_xj380);
|
||||
INSTR_TS_BEGIN(ts_xj380);
|
||||
log_info("[UTSM] XJ380 loader init ok");
|
||||
int xj_st = xj380_launch();
|
||||
INSTR_TS_END(ts_xj380, "xj380_launch");
|
||||
if (xj_st == 0) {
|
||||
log_info("[UTSM] XJ380 guest terminated (host resumed)");
|
||||
} else {
|
||||
log_error("[UTSM] XJ380 launch failed");
|
||||
log_hex64("[UTSM] XJ380 st=", (u64)(i64)xj_st);
|
||||
}
|
||||
} else {
|
||||
log_warn("[UTSM] XJ380 loader unavailable (no kernel.krl or disabled)");
|
||||
}
|
||||
} else {
|
||||
log_warn("[UTSM] VMM unavailable (VMX not supported or disabled by FUCK)");
|
||||
}
|
||||
|
||||
/* === 驱动加载 — 按 [drivers] 区过滤 === */
|
||||
INSTR_TS_DECL(ts_dsm);
|
||||
INSTR_TS_BEGIN(ts_dsm);
|
||||
@@ -259,54 +487,26 @@ void kernel_main(void) {
|
||||
log_info("[UTSM] selftest skipped by FUCK config");
|
||||
}
|
||||
|
||||
/* === VMM — 可通过 FUCK [boot] vmm=0 跳过 === */
|
||||
int run_vmm = ini_get_bool(cfg, "boot", "vmm", 1);
|
||||
if (run_vmm && vmm_init() == 0) {
|
||||
INSTR_TS_DECL(ts_vmm);
|
||||
INSTR_TS_BEGIN(ts_vmm);
|
||||
log_info("[UTSM] VMM init ok");
|
||||
int vmm_st = vmm_self_test();
|
||||
if (vmm_st == 0) {
|
||||
INSTR_TS_END(ts_vmm, "vmm_init+selftest");
|
||||
log_info("[UTSM] VMM self-test PASS");
|
||||
|
||||
/* === Linux guest — 可通过 FUCK [boot] linux_guest=0 跳过 === */
|
||||
int run_linux = ini_get_bool(cfg, "boot", "linux_guest", 1);
|
||||
if (run_linux && linux_loader_init() == 0) {
|
||||
INSTR_TS_DECL(ts_linux);
|
||||
INSTR_TS_BEGIN(ts_linux);
|
||||
log_info("[UTSM] Linux loader init ok");
|
||||
|
||||
if (ipc_shm_init() == 0) {
|
||||
log_info("[UTSM] IPC shm init ok");
|
||||
} else {
|
||||
log_error("[UTSM] IPC shm init failed (non-fatal)");
|
||||
}
|
||||
|
||||
int lin_st = linux_launch();
|
||||
INSTR_TS_END(ts_linux, "linux_launch");
|
||||
if (lin_st == 0) {
|
||||
log_info("[UTSM] Linux guest parked (daemon ready)");
|
||||
linux_compat_init();
|
||||
} else {
|
||||
log_error("[UTSM] Linux launch failed");
|
||||
log_hex64("[UTSM] Linux st=", (u64)(i64)lin_st);
|
||||
}
|
||||
} else {
|
||||
log_warn("[UTSM] Linux loader unavailable (no bzImage or disabled)");
|
||||
}
|
||||
} else {
|
||||
INSTR_TS_END(ts_vmm, "vmm_init+selftest");
|
||||
log_error("[UTSM] VMM self-test FAIL");
|
||||
log_hex64("[UTSM] VMM st=", (u64)(i64)vmm_st);
|
||||
}
|
||||
} else {
|
||||
log_warn("[UTSM] VMM unavailable (VMX not supported or disabled by FUCK)");
|
||||
}
|
||||
|
||||
/* ---- 插桩: DSK 加载跳转前记录 ---- */
|
||||
INSTR_PROBE(DSK0, 0, 0, 0, 0);
|
||||
|
||||
/* Phase 3: 初始化 PE DLL 管理器(注册 FAT32 读取回调)。
|
||||
* DSK 跳转前 block provider 已就绪,fat32_read_path 可用。 */
|
||||
if (g_compat_pe_dll_search) {
|
||||
pe_dll_manager_init(dll_reader_from_fat32);
|
||||
log_info("[UTSM] PE DLL manager initialized (SYSTEM/lib)");
|
||||
}
|
||||
|
||||
/* === 实机日志持久化:把启动日志写入磁盘原始扇区(开发测试用) === */
|
||||
{
|
||||
int disk_log = ini_get_bool(cfg, "boot", "disk_log", 0);
|
||||
if (disk_log) {
|
||||
int log_start = ini_get_int(cfg, "boot", "disk_log_start", 2);
|
||||
int log_end = ini_get_int(cfg, "boot", "disk_log_end", 1000);
|
||||
disk_log_flush(log_start, log_end);
|
||||
}
|
||||
}
|
||||
|
||||
if (dsk_load_and_jump() != 0) {
|
||||
log_error("[UTSM] DSK jump failed");
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ kernel/main.o: kernel\main.c include\utsm\log.h include\utsm\types.h \
|
||||
include\utsm\hypercall.h include\utsm\drr.h include\utsm\dkm.h \
|
||||
include\utsm\dma.h include\utsm\block.h include\utsm\net.h \
|
||||
include\utsm\paging.h include\utsm\idt.h include\utsm\dsk.h \
|
||||
include\utsm\linux_compat.h include\utsm\instr.h kernel\ini_parser.h \
|
||||
kernel\..\arch\x86_64\limine.h
|
||||
include\utsm\linux_compat.h include\utsm\xj380_loader.h \
|
||||
include\utsm\instr.h kernel\ini_parser.h \
|
||||
kernel\..\arch\x86_64\limine.h kernel\..\pe\pe_dll_manager.h
|
||||
include\utsm\log.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\arena.h:
|
||||
@@ -35,6 +36,8 @@ include\utsm\paging.h:
|
||||
include\utsm\idt.h:
|
||||
include\utsm\dsk.h:
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\xj380_loader.h:
|
||||
include\utsm\instr.h:
|
||||
kernel\ini_parser.h:
|
||||
kernel\..\arch\x86_64\limine.h:
|
||||
kernel\..\pe\pe_dll_manager.h:
|
||||
|
||||
Binary file not shown.
+229
-11
@@ -54,9 +54,74 @@ typedef struct {
|
||||
} pe_dll_entry;
|
||||
|
||||
static pe_dll_entry g_dll_cache[PE_DLL_CACHE_MAX];
|
||||
|
||||
/* ===== 延迟 DllMain 队列 =====
|
||||
* pe_load_image 触发递归 pe_dll_load 时,PE32+ 原生执行环境尚未就绪:
|
||||
* SSE 未启用(pe_enable_sse 在 load 之后才调用)、仍跑在 16KB 内核栈上。
|
||||
* 真实 DLL 的 CRT 初始化含 SSE 指令且栈需求大,立即调用必 #UD/溢栈。
|
||||
* 故加载阶段只登记 DllMain,由 pe_service_run 在启用 SSE 并切换到
|
||||
* 1MB PE 大栈后调 pe_dll_run_pending_dllmains() 统一执行。 */
|
||||
#define PE_DLLMAIN_PENDING_MAX 32
|
||||
typedef struct {
|
||||
u64 image_base;
|
||||
u64 entry_point;
|
||||
char name[PE_DLL_NAME_MAX];
|
||||
} pe_dllmain_pending;
|
||||
static pe_dllmain_pending g_dllmain_pending[PE_DLLMAIN_PENDING_MAX];
|
||||
static int g_dllmain_pending_count = 0;
|
||||
static int g_dllmain_defer = 0;
|
||||
|
||||
/* enable=1 开启延迟模式并清空队列(每次 pe_service_run 加载前调用);
|
||||
* enable=0 恢复立即调用(兜底路径用)。 */
|
||||
void pe_dll_defer_dllmain(int enable) {
|
||||
g_dllmain_defer = enable;
|
||||
if (enable) g_dllmain_pending_count = 0;
|
||||
}
|
||||
|
||||
/* 在大栈 + SSE 环境下执行全部已登记的 DllMain(ATTACH)。
|
||||
* 执行完毕清空队列并关闭延迟模式。 */
|
||||
void pe_dll_run_pending_dllmains(void) {
|
||||
for (int i = 0; i < g_dllmain_pending_count; i++) {
|
||||
pe_dllmain_pending *p = &g_dllmain_pending[i];
|
||||
typedef int (__attribute__((ms_abi)) *dllmain_fn)(u64, u32, u64);
|
||||
dllmain_fn dm = (dllmain_fn)p->entry_point;
|
||||
int ok = dm(p->image_base, 1 /* DLL_PROCESS_ATTACH */, 0);
|
||||
if (!ok) {
|
||||
log_warn("[PE] DllMain returned FALSE (init failed)");
|
||||
log_info(p->name);
|
||||
} else {
|
||||
log_info("[PE] DllMain ATTACH ok");
|
||||
}
|
||||
}
|
||||
g_dllmain_pending_count = 0;
|
||||
g_dllmain_defer = 0;
|
||||
}
|
||||
static pe_dll_reader_fn g_dll_reader = 0;
|
||||
int g_pe_dll_manager_ready = 0;
|
||||
|
||||
/* 转发导出递归深度上限(A->B->C 链) */
|
||||
#define PE_DLL_FWD_DEPTH_MAX 8
|
||||
|
||||
/* ---- API set 重定向 ----
|
||||
* Windows 10+ 的 api-ms-win-crt-*.dll 是 API set 虚拟 DLL,
|
||||
* 物理上不存在文件,由 loader 按 schema 重定向到 ucrtbase.dll。
|
||||
* 此处实现等价映射:api-ms-win-crt-* -> ucrtbase.dll。
|
||||
* 返回目标 DLL 名(静态字符串),或 NULL 表示非 API set。 */
|
||||
static const char *apiset_redirect(const char *dll_name) {
|
||||
if (!dll_name) return 0;
|
||||
/* 大小写不敏感前缀匹配 "api-ms-win-crt-" */
|
||||
static const char prefix[] = "api-ms-win-crt-";
|
||||
int i = 0;
|
||||
while (prefix[i]) {
|
||||
char c = dll_name[i];
|
||||
if (!c) return 0;
|
||||
if (c >= 'a' && c <= 'z') c = (char)(c - 32);
|
||||
if (c != prefix[i]) return 0;
|
||||
i++;
|
||||
}
|
||||
return "ucrtbase.dll";
|
||||
}
|
||||
|
||||
/* ---- 辅助函数 ---- */
|
||||
|
||||
/* ASCII 大小写不敏感字符串比较(freestanding,无 strcmp) */
|
||||
@@ -134,12 +199,37 @@ static void extract_export_dir(u64 image_base, u32 *out_rva, u32 *out_size) {
|
||||
*out_size = data_dir[IMAGE_DIRECTORY_ENTRY_EXPORT].size;
|
||||
}
|
||||
|
||||
/* 在已加载 DLL 的导出表中按名称查找函数。
|
||||
* 返回绝对地址(image_base + func_rva),0=未找到/转发导出。 */
|
||||
static u64 resolve_export_by_name(u64 image_base, u32 export_dir_rva,
|
||||
const char *func_name) {
|
||||
/* 按序号在已加载 DLL 的导出表中查找函数(转发串 "#123" 用)。
|
||||
* 返回绝对地址,0=未找到/越界。 */
|
||||
static u64 resolve_export_by_ordinal(u64 image_base, u32 export_dir_rva,
|
||||
u32 ordinal) {
|
||||
if (export_dir_rva == 0) return 0;
|
||||
const u8 *img = (const u8 *)image_base;
|
||||
const image_export_directory *exp =
|
||||
(const image_export_directory *)(img + export_dir_rva);
|
||||
if (ordinal < exp->ordinal_base) return 0;
|
||||
u32 idx = ordinal - exp->ordinal_base;
|
||||
if (idx >= exp->number_of_functions) return 0;
|
||||
const u32 *functions = (const u32 *)(img + exp->address_of_functions);
|
||||
u32 func_rva = functions[idx];
|
||||
if (func_rva == 0) return 0;
|
||||
return image_base + func_rva;
|
||||
}
|
||||
|
||||
/* 解析转发导出串("DLL.Func" 或 "DLL.#123")。
|
||||
* fwd_str 位于 image_base+func_rva 处(导出目录数据区内)。
|
||||
* depth 防止 A→B→A 循环转发。返回绝对地址,0=失败。 */
|
||||
static u64 resolve_forwarded_export(u64 image_base, u32 func_rva, int depth);
|
||||
|
||||
/* 在已加载 DLL 的导出表中按名称查找函数。
|
||||
* 返回绝对地址(image_base + func_rva),0=未找到。
|
||||
* 转发导出自动跟随解析(PE 规范:func_rva 落在导出目录数据区内即为转发串)。 */
|
||||
static u64 resolve_export_by_name(u64 image_base, u32 export_dir_rva,
|
||||
u32 export_dir_size, const char *func_name,
|
||||
int depth) {
|
||||
if (export_dir_rva == 0) return 0;
|
||||
if (depth > PE_DLL_FWD_DEPTH_MAX) return 0;
|
||||
const u8 *img = (const u8 *)image_base;
|
||||
const image_export_directory *exp =
|
||||
(const image_export_directory *)(img + export_dir_rva);
|
||||
if (exp->number_of_names == 0) return 0;
|
||||
@@ -155,12 +245,12 @@ static u64 resolve_export_by_name(u64 image_base, u32 export_dir_rva,
|
||||
if (eat_index >= exp->number_of_functions) return 0;
|
||||
u32 func_rva = functions[eat_index];
|
||||
|
||||
/* 转发导出:func_rva 落在导出表范围内 → 是转发字符串而非代码地址 */
|
||||
/* 转发导出(PE 规范):func_rva 落在导出目录数据区
|
||||
* [export_dir_rva, export_dir_rva+export_dir_size) 内 →
|
||||
* 该处是 "DLL.Func" 转发字符串而非代码地址 */
|
||||
if (func_rva >= export_dir_rva &&
|
||||
func_rva < export_dir_rva + exp->number_of_functions * 4) {
|
||||
/* 粗略范围检查(更精确应使用 export_dir_size,但此处足够) */
|
||||
log_info("[PE] forwarded export (not supported)");
|
||||
return 0;
|
||||
func_rva < export_dir_rva + export_dir_size) {
|
||||
return resolve_forwarded_export(image_base, func_rva, depth + 1);
|
||||
}
|
||||
return image_base + func_rva;
|
||||
}
|
||||
@@ -168,6 +258,74 @@ static u64 resolve_export_by_name(u64 image_base, u32 export_dir_rva,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 转发串解析:"NTDLL.RtlInitUnicodeString" 或 "KERNEL32.#123"。
|
||||
* DLL 名通常不带 .dll 后缀(如 "UCRTBASE"),需补全。 */
|
||||
static u64 resolve_forwarded_export(u64 image_base, u32 func_rva, int depth) {
|
||||
if (depth > PE_DLL_FWD_DEPTH_MAX) {
|
||||
log_warn("[PE] forwarded export chain too deep");
|
||||
return 0;
|
||||
}
|
||||
const char *fwd = (const char *)(image_base + func_rva);
|
||||
|
||||
/* 拷贝到本地缓冲并找分隔点 '.'(转发串最大 ~64 字符,防御截断) */
|
||||
char buf[96];
|
||||
u32 i = 0;
|
||||
while (i + 1 < sizeof(buf) && fwd[i] && fwd[i] != ' ') {
|
||||
buf[i] = fwd[i];
|
||||
i++;
|
||||
}
|
||||
buf[i] = 0;
|
||||
|
||||
u32 dot = 0;
|
||||
while (buf[dot] && buf[dot] != '.') dot++;
|
||||
if (buf[dot] != '.' || dot == 0 || buf[dot + 1] == 0) {
|
||||
log_warn("[PE] malformed forwarder string");
|
||||
return 0;
|
||||
}
|
||||
buf[dot] = 0;
|
||||
const char *target_func = buf + dot + 1;
|
||||
|
||||
/* 目标 DLL 名:小写化 + 补 ".dll" 后缀 */
|
||||
char tdll[PE_DLL_NAME_MAX];
|
||||
dll_name_lower(buf, tdll, sizeof(tdll));
|
||||
int tl = 0;
|
||||
while (tdll[tl]) tl++;
|
||||
if (tl + 4 < (int)sizeof(tdll)) {
|
||||
/* 已有 ".dll" 后缀则不重复添加 */
|
||||
if (!(tl >= 4 && tdll[tl-4] == '.' &&
|
||||
(tdll[tl-3] == 'd') && (tdll[tl-2] == 'l') && (tdll[tl-1] == 'l'))) {
|
||||
tdll[tl++] = '.'; tdll[tl++] = 'd';
|
||||
tdll[tl++] = 'l'; tdll[tl++] = 'l';
|
||||
tdll[tl] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 序号转发:"#123" */
|
||||
if (target_func[0] == '#') {
|
||||
u32 ord = 0;
|
||||
const char *p = target_func + 1;
|
||||
if (*p == 0) return 0;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
ord = ord * 10 + (u32)(*p - '0');
|
||||
p++;
|
||||
}
|
||||
if (ord == 0 || ord > 0xFFFF) return 0;
|
||||
/* 需先确保目标 DLL 已加载 */
|
||||
u64 base = 0;
|
||||
if (pe_dll_load(tdll, &base) != 0) return 0;
|
||||
int idx = dll_cache_find(tdll);
|
||||
if (idx < 0) return 0;
|
||||
u64 addr = resolve_export_by_ordinal(base, g_dll_cache[idx].export_dir_rva, ord);
|
||||
if (addr == 0) {
|
||||
log_warn("[PE] ordinal forward target not found");
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
/* 名称转发:递归走 pe_dll_resolve(带深度计) */
|
||||
return pe_dll_resolve_ex(tdll, target_func, depth);
|
||||
}
|
||||
|
||||
/* ---- 公共接口 ---- */
|
||||
|
||||
void pe_dll_manager_init(pe_dll_reader_fn reader) {
|
||||
@@ -185,6 +343,10 @@ int pe_dll_load(const char *dll_name, u64 *out_base) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* API set 重定向:api-ms-win-crt-* -> ucrtbase.dll */
|
||||
const char *redirected = apiset_redirect(dll_name);
|
||||
if (redirected) dll_name = redirected;
|
||||
|
||||
/* 查缓存 */
|
||||
int idx = dll_cache_find(dll_name);
|
||||
if (idx >= 0) {
|
||||
@@ -254,13 +416,49 @@ int pe_dll_load(const char *dll_name, u64 *out_base) {
|
||||
log_info(dll_name);
|
||||
log_hex64("[PE] DLL base=", info.image_base);
|
||||
|
||||
/* 调用 DllMain(hinst, DLL_PROCESS_ATTACH=1, NULL)。
|
||||
* 真实 DLL(如 ucrtbase)依赖入口完成 CRT/TLS 初始化。
|
||||
* 无入口点(AddressOfEntryPoint==0 → entry==image_base)则跳过。
|
||||
* 返回 0(初始化失败)只记告警不阻断:无状态函数仍可用。
|
||||
* 延迟模式(pe_service_run 加载阶段):只登记不调用,待 SSE 启用
|
||||
* 且切换到 1MB PE 大栈后由 pe_dll_run_pending_dllmains 统一执行。 */
|
||||
if (info.entry_point != info.image_base) {
|
||||
if (g_dllmain_defer) {
|
||||
if (g_dllmain_pending_count < PE_DLLMAIN_PENDING_MAX) {
|
||||
pe_dllmain_pending *p = &g_dllmain_pending[g_dllmain_pending_count++];
|
||||
p->image_base = info.image_base;
|
||||
p->entry_point = info.entry_point;
|
||||
int i = 0;
|
||||
for (; i < PE_DLL_NAME_MAX - 1 && entry->name[i]; i++) p->name[i] = entry->name[i];
|
||||
p->name[i] = 0;
|
||||
log_info("[PE] DllMain deferred");
|
||||
} else {
|
||||
log_warn("[PE] DllMain pending queue full, skipped");
|
||||
}
|
||||
} else {
|
||||
typedef int (__attribute__((ms_abi)) *dllmain_fn)(u64, u32, u64);
|
||||
dllmain_fn dm = (dllmain_fn)info.entry_point;
|
||||
int ok = dm(info.image_base, 1 /* DLL_PROCESS_ATTACH */, 0);
|
||||
if (!ok) {
|
||||
log_warn("[PE] DllMain returned FALSE (init failed)");
|
||||
log_info(dll_name);
|
||||
} else {
|
||||
log_info("[PE] DllMain ATTACH ok");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (out_base) *out_base = info.image_base;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 pe_dll_resolve(const char *dll_name, const char *func_name) {
|
||||
u64 pe_dll_resolve_ex(const char *dll_name, const char *func_name, int depth) {
|
||||
if (!g_pe_dll_manager_ready || !dll_name || !func_name) return 0;
|
||||
|
||||
/* API set 重定向:api-ms-win-crt-* -> ucrtbase.dll */
|
||||
const char *redirected = apiset_redirect(dll_name);
|
||||
if (redirected) dll_name = redirected;
|
||||
|
||||
/* 确保 DLL 已加载 */
|
||||
int idx = dll_cache_find(dll_name);
|
||||
if (idx < 0 || g_dll_cache[idx].state != DLL_STATE_LOADED) {
|
||||
@@ -271,5 +469,25 @@ u64 pe_dll_resolve(const char *dll_name, const char *func_name) {
|
||||
}
|
||||
|
||||
pe_dll_entry *e = &g_dll_cache[idx];
|
||||
return resolve_export_by_name(e->image_base, e->export_dir_rva, func_name);
|
||||
return resolve_export_by_name(e->image_base, e->export_dir_rva,
|
||||
e->export_dir_size, func_name, depth);
|
||||
}
|
||||
|
||||
u64 pe_dll_resolve(const char *dll_name, const char *func_name) {
|
||||
return pe_dll_resolve_ex(dll_name, func_name, 0);
|
||||
}
|
||||
|
||||
u64 pe_dll_resolve_in_base(u64 image_base, const char *func_name, u32 ordinal) {
|
||||
if (!g_pe_dll_manager_ready || image_base == 0) return 0;
|
||||
for (int i = 0; i < PE_DLL_CACHE_MAX; i++) {
|
||||
pe_dll_entry *e = &g_dll_cache[i];
|
||||
if (e->state != DLL_STATE_LOADED || e->image_base != image_base) continue;
|
||||
if (ordinal != 0)
|
||||
return resolve_export_by_ordinal(e->image_base, e->export_dir_rva, ordinal);
|
||||
if (func_name)
|
||||
return resolve_export_by_name(e->image_base, e->export_dir_rva,
|
||||
e->export_dir_size, func_name, 0);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -48,4 +48,18 @@ int pe_dll_load(const char *dll_name, u64 *out_base);
|
||||
* 返回绝对地址,0=未找到。 */
|
||||
u64 pe_dll_resolve(const char *dll_name, const char *func_name);
|
||||
|
||||
/* 带转发深度的内部变体(转发导出链 A→B→C 递归用,外部勿调)。 */
|
||||
u64 pe_dll_resolve_ex(const char *dll_name, const char *func_name, int depth);
|
||||
|
||||
/* 在已加载 DLL 的模块基址上解析导出(GetProcAddress 真实 DLL 路径用)。
|
||||
* image_base 必须是 pe_dll_load 返回的基址;func_name 与 ordinal 二选一
|
||||
* (ordinal != 0 时按序号)。返回绝对地址,0=未找到。 */
|
||||
u64 pe_dll_resolve_in_base(u64 image_base, const char *func_name, u32 ordinal);
|
||||
|
||||
/* 延迟 DllMain 控制(PE32+ 加载期 SSE/大栈未就绪,DllMain 必须延迟)。
|
||||
* pe_service_run 在 pe_load_image 前调 pe_dll_defer_dllmain(1),
|
||||
* 切换到 1MB PE 大栈后调 pe_dll_run_pending_dllmains() 统一执行。 */
|
||||
void pe_dll_defer_dllmain(int enable);
|
||||
void pe_dll_run_pending_dllmains(void);
|
||||
|
||||
#endif /* PE_DLL_MANAGER_H */
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "pe_loader.h"
|
||||
#include "pe_shim.h"
|
||||
#include "pe_dll_manager.h"
|
||||
#include "x86emu32.h"
|
||||
#include "../include/utsm/pe.h"
|
||||
#include <utsm/arena.h>
|
||||
@@ -218,19 +219,53 @@ static void resolve_imports(u8 *image, u64 image_base_actual,
|
||||
shim_idx = pe_shim_lookup(dll_name, func_name);
|
||||
}
|
||||
|
||||
/* 当 shim 未命中且需要尝试真实 DLL 时,先把 dll_name/func_name
|
||||
* 复制到本地栈缓冲。原因:pe_dll_resolve 可能触发 fat32_read_path,
|
||||
* 后者返回的 static 缓冲与外层 pe_data 可能指向同一区域,会被覆盖。 */
|
||||
char dll_buf[256];
|
||||
char func_buf[256];
|
||||
int have_local_names = 0;
|
||||
if (shim_idx < 0 && is_pe32_plus && g_pe_dll_manager_ready &&
|
||||
func_name && func_name[0] != '#') {
|
||||
u32 dl = 0;
|
||||
while (dll_name[dl] && dl + 1 < sizeof(dll_buf)) {
|
||||
dll_buf[dl] = dll_name[dl];
|
||||
dl++;
|
||||
}
|
||||
dll_buf[dl] = 0;
|
||||
u32 fl = 0;
|
||||
while (func_name[fl] && fl + 1 < sizeof(func_buf)) {
|
||||
func_buf[fl] = func_name[fl];
|
||||
fl++;
|
||||
}
|
||||
func_buf[fl] = 0;
|
||||
have_local_names = 1;
|
||||
}
|
||||
|
||||
/* 填充 IAT(image 缓冲区内) */
|
||||
u8 *iat_ptr = image + iat_rva + (u32)fn_idx * (is_pe32_plus ? 8 : 4);
|
||||
int resolved_by_real_dll = 0;
|
||||
if (is_pe32_plus) {
|
||||
u64 fn_addr;
|
||||
if (shim_idx >= 0) {
|
||||
const pe_shim_entry *se = pe_shim_get(shim_idx);
|
||||
fn_addr = (u64)se->fn64;
|
||||
} else if (have_local_names) {
|
||||
/* PE32+ 尝试真实 DLL */
|
||||
u64 real = pe_dll_resolve(dll_buf, func_buf);
|
||||
if (real != 0) {
|
||||
fn_addr = real;
|
||||
resolved_by_real_dll = 1;
|
||||
} else {
|
||||
/* DLL 加载过但函数未找到:填 unimpl stub */
|
||||
fn_addr = pe_shim_unimpl_stub();
|
||||
}
|
||||
} else {
|
||||
fn_addr = pe_shim_unimpl_stub();
|
||||
}
|
||||
*(u64 *)iat_ptr = fn_addr;
|
||||
} else {
|
||||
/* 32位:填合成地址,解释器拦截 */
|
||||
/* 32位:填合成地址,解释器拦截(不调真实 DLL) */
|
||||
u32 synth_addr;
|
||||
if (shim_idx >= 0) {
|
||||
synth_addr = PE_SHIM_ADDR_BASE | (u32)(shim_idx & 0xFFFF);
|
||||
@@ -248,11 +283,26 @@ static void resolve_imports(u8 *image, u64 image_base_actual,
|
||||
}
|
||||
}
|
||||
|
||||
if (shim_idx < 0 && func_name) {
|
||||
/* shim 未命中且真实 DLL 也未解析:记未实现日志;
|
||||
* 真实 DLL 解析成功则记来源日志(区分 shim/真实 DLL 路径)。
|
||||
* 用本地副本(pe_data 可能已被 DLL 加载破坏) */
|
||||
if (shim_idx < 0 && func_name && !resolved_by_real_dll) {
|
||||
log_info("[PE] unimplemented import");
|
||||
serial_write(dll_name);
|
||||
if (have_local_names) {
|
||||
serial_write(dll_buf);
|
||||
serial_write("!");
|
||||
serial_write(func_buf);
|
||||
} else {
|
||||
serial_write(dll_name);
|
||||
serial_write("!");
|
||||
serial_write(func_name);
|
||||
}
|
||||
serial_write("\n");
|
||||
} else if (resolved_by_real_dll) {
|
||||
log_info("[PE] import via real DLL");
|
||||
serial_write(dll_buf);
|
||||
serial_write("!");
|
||||
serial_write(func_name);
|
||||
serial_write(func_buf);
|
||||
serial_write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
pe/pe_loader.o: pe\pe_loader.c pe\pe_loader.h include\utsm\types.h \
|
||||
include\utsm\pe.h pe\pe_shim.h pe\x86emu32.h pe\..\include\utsm\pe.h \
|
||||
include\utsm\arena.h include\utsm\log.h
|
||||
include\utsm\pe.h pe\pe_shim.h pe\pe_dll_manager.h pe\x86emu32.h \
|
||||
pe\..\include\utsm\pe.h include\utsm\arena.h include\utsm\log.h
|
||||
pe\pe_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\pe.h:
|
||||
pe\pe_shim.h:
|
||||
pe\pe_dll_manager.h:
|
||||
pe\x86emu32.h:
|
||||
pe\..\include\utsm\pe.h:
|
||||
include\utsm\arena.h:
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "pe_loader.h"
|
||||
#include "pe_shim.h"
|
||||
#include "pe_dll_manager.h"
|
||||
#include "x86emu32.h"
|
||||
#include "../include/utsm/pe.h"
|
||||
#include <utsm/arena.h>
|
||||
@@ -34,13 +35,20 @@
|
||||
int pe_service_load(const void *pe_data, u64 size, pe_image_info *out);
|
||||
int pe_service_run(const void *pe_data, u64 size, const char *cmdline, u64 *exit_code);
|
||||
void pe_service_unload(pe_image_info *info);
|
||||
int pe_service_run_windowed(const void *pe_data, u64 size, const char *cmdline,
|
||||
u64 *exit_code, const pe_window_host *host);
|
||||
|
||||
/* 全局服务表实例 */
|
||||
/* 全局服务表实例(P5:ABI 尾部追加 run_windowed/inject_*,同镜像一致性
|
||||
* 假设见 pe.h;旧调用方按前 4 字段使用不受影响) */
|
||||
static const pe_service g_pe_service = {
|
||||
PE_SERVICE_MAGIC,
|
||||
pe_service_load,
|
||||
pe_service_run,
|
||||
pe_service_unload,
|
||||
pe_service_run_windowed,
|
||||
pe_shim_inject_scancode,
|
||||
pe_shim_inject_pointer,
|
||||
pe_shim_inject_input,
|
||||
};
|
||||
|
||||
const pe_service *pe_get_service(void) {
|
||||
@@ -248,6 +256,14 @@ static void pe_call_on_stack(pe_entry_fn fn, u64 stack_top) {
|
||||
"r8", "r9", "r10", "r11");
|
||||
}
|
||||
|
||||
/* PE32+ 大栈引导入口:先执行加载期延迟登记的真实 DLL DllMain(ATTACH),
|
||||
* 再跳 PE 入口。此时 SSE 已启用且运行在 1MB PE 大栈上。 */
|
||||
static pe_entry_fn g_pe_boot_entry;
|
||||
static void pe_boot_with_dllmains(void) {
|
||||
pe_dll_run_pending_dllmains();
|
||||
g_pe_boot_entry();
|
||||
}
|
||||
|
||||
/* ===== 运行 =====
|
||||
* 加载 + 执行 + 卸载.返回 0 成功,*exit_code 为进程退出码. */
|
||||
int pe_service_run(const void *pe_data, u64 size, const char *cmdline, u64 *exit_code) {
|
||||
@@ -268,13 +284,18 @@ int pe_service_run(const void *pe_data, u64 size, const char *cmdline, u64 *exit
|
||||
if (opt_magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||||
/* ===== PE32+ : Ring0 原生执行 ===== */
|
||||
pe_image_info info;
|
||||
/* SSE 必须在任何 PE/DLL 代码执行前启用:load 期会递归加载真实 DLL,
|
||||
* MSVC CRT 代码含 SSE 指令,后置启用曾在 DllMain 内触发 #UD。 */
|
||||
pe_enable_sse();
|
||||
/* DllMain 延迟到 1MB PE 大栈上执行(16KB 内核栈跑不动 CRT init) */
|
||||
pe_dll_defer_dllmain(1);
|
||||
int rc = pe_load_image(pe_data, size, &info);
|
||||
if (rc != 0) {
|
||||
pe_dll_defer_dllmain(0);
|
||||
log_error("[PE] PE32+ load failed");
|
||||
return -5;
|
||||
}
|
||||
log_info("[PE] running PE32+ natively");
|
||||
pe_enable_sse();
|
||||
|
||||
/* 恢复异常可见性:UTSM 交权 DSK 时 idt_halt_all() 将 IDT 全部
|
||||
* 替换为静默 halt stub,PE 原生执行期间的 CPU 异常(PF/GP/UD)
|
||||
@@ -297,10 +318,10 @@ int pe_service_run(const void *pe_data, u64 size, const char *cmdline, u64 *exit
|
||||
* 残片充当 R12-R15/RBP → 野指针 call → 三重故障). */
|
||||
u64 code = 0;
|
||||
if (__builtin_setjmp(pe_shim_exit_jmpbuf()) == 0) {
|
||||
pe_entry_fn entry = (pe_entry_fn)info.entry_point;
|
||||
log_hex64("[PE] entry=", (u64)entry);
|
||||
log_hex64("[PE] entry=", (u64)info.entry_point);
|
||||
log_hex64("[PE] stack_top=", stack_top);
|
||||
pe_call_on_stack(entry, stack_top);
|
||||
g_pe_boot_entry = (pe_entry_fn)info.entry_point;
|
||||
pe_call_on_stack(pe_boot_with_dllmains, stack_top);
|
||||
code = 0;
|
||||
} else {
|
||||
code = pe_shim_get_exit_code();
|
||||
@@ -365,3 +386,20 @@ int pe_service_run(const void *pe_data, u64 size, const char *cmdline, u64 *exit
|
||||
|
||||
return -8; /* 未知 PE magic */
|
||||
}
|
||||
|
||||
/* ===== P5 窗口模式运行 =====
|
||||
* 与 pe_service_run 相同的加载/执行路径,区别仅在 shim 行为:
|
||||
* 渲染目标重定向到 host->surface,PS/2 轮询关闭,消息空转回调
|
||||
* host->pump()(desktop 帧)。host==NULL/surface==NULL 时等价 run。 */
|
||||
int pe_service_run_windowed(const void *pe_data, u64 size, const char *cmdline,
|
||||
u64 *exit_code, const pe_window_host *host) {
|
||||
if (host && host->surface) {
|
||||
pe_shim_set_window_host(host);
|
||||
log_info("[PE] run_windowed: surface mode");
|
||||
} else {
|
||||
pe_shim_set_window_host(0);
|
||||
}
|
||||
int rc = pe_service_run(pe_data, size, cmdline, exit_code);
|
||||
pe_shim_set_window_host(0); /* 防止残留配置串到下一次 run */
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
pe/pe_service.o: pe\pe_service.c pe\pe_loader.h include\utsm\types.h \
|
||||
include\utsm\pe.h pe\pe_shim.h pe\x86emu32.h pe\..\include\utsm\pe.h \
|
||||
include\utsm\arena.h include\utsm\idt.h include\utsm\log.h
|
||||
include\utsm\pe.h pe\pe_shim.h pe\pe_dll_manager.h pe\x86emu32.h \
|
||||
pe\..\include\utsm\pe.h include\utsm\arena.h include\utsm\idt.h \
|
||||
include\utsm\log.h
|
||||
pe\pe_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\pe.h:
|
||||
pe\pe_shim.h:
|
||||
pe\pe_dll_manager.h:
|
||||
pe\x86emu32.h:
|
||||
pe\..\include\utsm\pe.h:
|
||||
include\utsm\arena.h:
|
||||
|
||||
+264
-38
@@ -25,6 +25,8 @@
|
||||
*/
|
||||
|
||||
#include "pe_shim.h"
|
||||
#include "pe_dll_manager.h"
|
||||
#include "../include/utsm/pe.h" /* P5: pe_window_host */
|
||||
#include <utsm/arena.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
@@ -69,11 +71,57 @@ static inline u64 gp(u64 va) {
|
||||
return va;
|
||||
}
|
||||
|
||||
/* ===== P5 窗口模式状态 =====
|
||||
* 全屏模式(默认):s_wsurf==NULL,shim 直写 Limine fb + 轮询 PS/2。
|
||||
* 窗口模式:渲染目标为 host surface(伪造 fb),输入靠 inject_*,
|
||||
* 消息空转时回调 host pump(desktop 帧)。 */
|
||||
static u8 *s_wsurf = 0;
|
||||
static u32 s_wsurf_w = 0, s_wsurf_h = 0, s_wsurf_pitch = 0;
|
||||
static int (*s_host_pump)(void *ud) = 0;
|
||||
static void *s_host_ud = 0;
|
||||
static struct limine_framebuffer s_wsurf_fb; /* 伪造 fb(窗口模式) */
|
||||
static const pe_window_host *s_winhost_pending = 0; /* service 层挂起配置 */
|
||||
|
||||
void pe_shim_set_window_host(const pe_window_host *host) {
|
||||
s_winhost_pending = host;
|
||||
}
|
||||
|
||||
int pe_shim_window_mode(void) { return s_wsurf != 0; }
|
||||
|
||||
void pe_shim_init(const char *cmdline) {
|
||||
g_cmdline = cmdline ? cmdline : "";
|
||||
g_exit_jmp_set = 0;
|
||||
g_exit_code = 0;
|
||||
g_emu_mem_base = 0; /* BUG-007: 默认非解释器上下文(原生路径恒等映射) */
|
||||
|
||||
/* P5:应用 service 层挂起的窗口模式配置(在状态重置之后生效) */
|
||||
if (s_winhost_pending && s_winhost_pending->surface
|
||||
&& s_winhost_pending->width && s_winhost_pending->height
|
||||
&& s_winhost_pending->pitch) {
|
||||
s_wsurf = s_winhost_pending->surface;
|
||||
s_wsurf_w = s_winhost_pending->width;
|
||||
s_wsurf_h = s_winhost_pending->height;
|
||||
s_wsurf_pitch = s_winhost_pending->pitch;
|
||||
s_host_pump = s_winhost_pending->pump;
|
||||
s_host_ud = s_winhost_pending->ud;
|
||||
/* 伪造 fb:32bpp 0xAARRGGBB(与 BitBlt/SetDIBits fast path 掩码一致) */
|
||||
s_wsurf_fb.address = s_wsurf;
|
||||
s_wsurf_fb.width = s_wsurf_w;
|
||||
s_wsurf_fb.height = s_wsurf_h;
|
||||
s_wsurf_fb.pitch = s_wsurf_pitch;
|
||||
s_wsurf_fb.bpp = 32;
|
||||
s_wsurf_fb.memory_model = 1;
|
||||
s_wsurf_fb.red_mask_shift = 16; s_wsurf_fb.red_mask_size = 8;
|
||||
s_wsurf_fb.green_mask_shift = 8; s_wsurf_fb.green_mask_size = 8;
|
||||
s_wsurf_fb.blue_mask_shift = 0; s_wsurf_fb.blue_mask_size = 8;
|
||||
log_info("[shim] window mode: surface target");
|
||||
} else {
|
||||
s_wsurf = 0;
|
||||
s_wsurf_w = s_wsurf_h = s_wsurf_pitch = 0;
|
||||
s_host_pump = 0;
|
||||
s_host_ud = 0;
|
||||
}
|
||||
s_winhost_pending = 0;
|
||||
}
|
||||
|
||||
/* 返回 ExitProcess longjmp 目标 jmpbuf(见 pe_shim.h 的 BUG-006 说明).
|
||||
@@ -697,7 +745,7 @@ static u64 __attribute__((ms_abi)) shim_LoadLibraryA(u64 name_p) {
|
||||
name_p = gp(name_p);
|
||||
if (!name_p) return 0;
|
||||
const char *n = (const char *)name_p;
|
||||
/* 已知 DLL 返回伪句柄(大小写不敏感子串匹配) */
|
||||
/* 已知 shim DLL 返回伪句柄(大小写不敏感子串匹配) */
|
||||
static const char *known[] = { "kernel32", "ntdll", "user32", "gdi32", "msvcrt" };
|
||||
for (int k = 0; k < 5; k++) {
|
||||
const char *pat = known[k];
|
||||
@@ -709,7 +757,22 @@ static u64 __attribute__((ms_abi)) shim_LoadLibraryA(u64 name_p) {
|
||||
if (a != b) break;
|
||||
i++;
|
||||
}
|
||||
if (pat[i] == 0) return 0x10000020;
|
||||
if (pat[i] == 0) return PE_SHIM_FAKE_MODULE;
|
||||
}
|
||||
}
|
||||
/* 未知 DLL:尝试从 SYSTEM/lib 加载真实 DLL(句柄=镜像基址) */
|
||||
if (g_pe_dll_manager_ready) {
|
||||
char nb[64];
|
||||
u32 i = 0;
|
||||
while (n[i] && i + 1 < sizeof(nb)) { nb[i] = n[i]; i++; }
|
||||
nb[i] = 0;
|
||||
u64 base = 0;
|
||||
if (pe_dll_load(nb, &base) == 0 && base != 0) {
|
||||
log_info("[shim] LoadLibrary real DLL ok");
|
||||
serial_write(" -> ");
|
||||
serial_write(nb);
|
||||
serial_write("\n");
|
||||
return base;
|
||||
}
|
||||
}
|
||||
g_last_error = 126; /* ERROR_MOD_NOT_FOUND */
|
||||
@@ -723,13 +786,22 @@ static u64 __attribute__((ms_abi)) shim_lstrlenW(u64 str_p) {
|
||||
return n;
|
||||
}
|
||||
|
||||
/* GetProcAddress — 在 shim 表内按函数名全局查找(hModule 为伪句柄,不限定 DLL).
|
||||
* lpProcName 高 16 位为 0 时是 ordinal(不支持,返回 0)。
|
||||
* Rust std 用它动态探测 Rtl 系列/dbghelp API,命中返回真实 shim 地址,miss 返回 0。 */
|
||||
/* GetProcAddress — 伪句柄在 shim 表内按函数名全局查找(不限定 DLL);
|
||||
* 真实 DLL 句柄(pe_dll_load 返回的镜像基址)在该 DLL 导出表内解析。
|
||||
* lpProcName 高 16 位为 0 时是 ordinal。 */
|
||||
static u64 __attribute__((ms_abi)) shim_GetProcAddress(u64 module, u64 name_p) {
|
||||
(void)module;
|
||||
name_p = gp(name_p);
|
||||
if (!name_p) return 0;
|
||||
/* 真实 DLL 模块:按基址解析(支持名称与序号) */
|
||||
if (module && module != PE_SHIM_FAKE_MODULE && g_pe_dll_manager_ready) {
|
||||
u32 ord = ((name_p >> 16) == 0) ? (u32)(name_p & 0xFFFF) : 0;
|
||||
u64 addr = pe_dll_resolve_in_base(module,
|
||||
ord ? 0 : (const char *)name_p, ord);
|
||||
if (addr) return addr;
|
||||
g_last_error = 127; /* ERROR_PROC_NOT_FOUND */
|
||||
return 0;
|
||||
}
|
||||
(void)module;
|
||||
if ((name_p >> 16) == 0) { /* ordinal 导入 */
|
||||
g_last_error = 127; /* ERROR_PROC_NOT_FOUND */
|
||||
return 0;
|
||||
@@ -1153,8 +1225,47 @@ static void shim_mouse_byte(u8 b) {
|
||||
s_mouse_btn = nbtn;
|
||||
}
|
||||
|
||||
/* 排空 PS/2 输出缓冲,转成 Win32 消息入队 */
|
||||
/* 单个 set-1 扫描码 -> Win32 消息入队(P5 抽出:全屏 PS/2 轮询与
|
||||
* 窗口模式 host 注入共用同一份 vk 映射/修饰键跟踪/WM_CHAR 合成)。
|
||||
* sc: 原始扫描码(bit7=release);e0: 1=E0 扩展键。 */
|
||||
static void shim_kbd_inject(u8 sc, int e0) {
|
||||
if (!e0) {
|
||||
if (sc == 0x2A || sc == 0x36) { s_kbd_shift = 1; shim_msg_push(SHIM_WM_KEYDOWN, 0x10, sc); return; }
|
||||
if (sc == 0xAA || sc == 0xB6) { s_kbd_shift = 0; shim_msg_push(SHIM_WM_KEYUP, 0x10, sc); return; }
|
||||
if (sc == 0x1D) { s_kbd_ctrl = 1; shim_msg_push(SHIM_WM_KEYDOWN, 0x11, sc); return; }
|
||||
if (sc == 0x9D) { s_kbd_ctrl = 0; shim_msg_push(SHIM_WM_KEYUP, 0x11, sc); return; }
|
||||
if (sc == 0x38) { s_kbd_alt = 1; shim_msg_push(SHIM_WM_KEYDOWN, 0x12, sc); return; }
|
||||
if (sc == 0xB8) { s_kbd_alt = 0; shim_msg_push(SHIM_WM_KEYUP, 0x12, sc); return; }
|
||||
}
|
||||
if (sc & 0x80) {
|
||||
u32 vk = shim_scan_vk(sc & 0x7F, e0);
|
||||
if (vk) {
|
||||
shim_msg_push(SHIM_WM_KEYUP, vk, sc);
|
||||
if (s_dbg_key < 16) { s_dbg_key++; log_hex64("[shim] KEYUP vk=", vk); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
u32 vk = shim_scan_vk(sc, e0);
|
||||
if (vk) {
|
||||
shim_msg_push(SHIM_WM_KEYDOWN, vk, sc);
|
||||
if (s_dbg_key < 16) { s_dbg_key++; log_hex64("[shim] KEYDOWN vk=", vk); }
|
||||
/* Esc -> 额外合成 WM_CLOSE:无鼠标环境下关闭窗口的唯一途径 */
|
||||
if (vk == 0x1B && !e0) shim_msg_push(SHIM_WM_CLOSE, 0, 0);
|
||||
}
|
||||
if (!e0) {
|
||||
char c = shim_scan_ascii(sc, s_kbd_shift);
|
||||
if (c >= ' ' && c < 0x7F) {
|
||||
shim_msg_push(SHIM_WM_CHAR, (u64)(u8)c, sc);
|
||||
if (s_dbg_key < 16) { s_dbg_key++; log_hex64("[shim] CHAR ch=", (u64)(u8)c); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 排空 PS/2 输出缓冲,转成 Win32 消息入队。
|
||||
* P5 窗口模式:输入权归 desktop(经 inject_* 入队),此函数 no-op,
|
||||
* 绝不触碰 PS/2 端口(避免与 desktop 的输入采集竞争)。 */
|
||||
static void shim_kbd_poll(void) {
|
||||
if (s_wsurf) return; /* 窗口模式:host 注入,不轮询端口 */
|
||||
shim_mouse_init_once();
|
||||
for (;;) {
|
||||
u8 st = inb(0x64);
|
||||
@@ -1167,37 +1278,64 @@ static void shim_kbd_poll(void) {
|
||||
}
|
||||
if (sc == 0xE0) { s_kbd_e0 = 1; continue; }
|
||||
int e0 = s_kbd_e0;
|
||||
if (!e0) {
|
||||
if (sc == 0x2A || sc == 0x36) { s_kbd_shift = 1; shim_msg_push(SHIM_WM_KEYDOWN, 0x10, sc); continue; }
|
||||
if (sc == 0xAA || sc == 0xB6) { s_kbd_shift = 0; shim_msg_push(SHIM_WM_KEYUP, 0x10, sc); continue; }
|
||||
if (sc == 0x1D) { s_kbd_ctrl = 1; shim_msg_push(SHIM_WM_KEYDOWN, 0x11, sc); continue; }
|
||||
if (sc == 0x9D) { s_kbd_ctrl = 0; shim_msg_push(SHIM_WM_KEYUP, 0x11, sc); continue; }
|
||||
if (sc == 0x38) { s_kbd_alt = 1; shim_msg_push(SHIM_WM_KEYDOWN, 0x12, sc); continue; }
|
||||
if (sc == 0xB8) { s_kbd_alt = 0; shim_msg_push(SHIM_WM_KEYUP, 0x12, sc); continue; }
|
||||
}
|
||||
s_kbd_e0 = 0;
|
||||
if (sc & 0x80) {
|
||||
u32 vk = shim_scan_vk(sc & 0x7F, e0);
|
||||
if (vk) {
|
||||
shim_msg_push(SHIM_WM_KEYUP, vk, sc);
|
||||
if (s_dbg_key < 16) { s_dbg_key++; log_hex64("[shim] KEYUP vk=", vk); }
|
||||
}
|
||||
continue;
|
||||
}
|
||||
u32 vk = shim_scan_vk(sc, e0);
|
||||
if (vk) {
|
||||
shim_msg_push(SHIM_WM_KEYDOWN, vk, sc);
|
||||
if (s_dbg_key < 16) { s_dbg_key++; log_hex64("[shim] KEYDOWN vk=", vk); }
|
||||
/* Esc -> 额外合成 WM_CLOSE:无鼠标环境下关闭窗口的唯一途径 */
|
||||
if (vk == 0x1B && !e0) shim_msg_push(SHIM_WM_CLOSE, 0, 0);
|
||||
}
|
||||
if (!e0) {
|
||||
char c = shim_scan_ascii(sc, s_kbd_shift);
|
||||
if (c >= ' ' && c < 0x7F) {
|
||||
shim_msg_push(SHIM_WM_CHAR, (u64)(u8)c, sc);
|
||||
if (s_dbg_key < 16) { s_dbg_key++; log_hex64("[shim] CHAR ch=", (u64)(u8)c); }
|
||||
}
|
||||
}
|
||||
shim_kbd_inject(sc, e0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== P5 窗口模式输入注入(pe_service 经 ABI 转发到这里)===== */
|
||||
|
||||
int pe_shim_inject_scancode(u32 scancode, u32 e0_prefix) {
|
||||
if (!s_wsurf) return -1;
|
||||
shim_kbd_inject((u8)scancode, e0_prefix ? 1 : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pe_shim_inject_pointer(i32 x, i32 y, u32 buttons) {
|
||||
if (!s_wsurf) return -1;
|
||||
/* 钳位到 surface 范围 */
|
||||
if (x < 0) x = 0; else if (x >= (i32)s_wsurf_w) x = (i32)s_wsurf_w - 1;
|
||||
if (y < 0) y = 0; else if (y >= (i32)s_wsurf_h) y = (i32)s_wsurf_h - 1;
|
||||
|
||||
u8 nbtn = (u8)(buttons & 0x07);
|
||||
i32 cx = x - g_wnd_x, cy = y - g_wnd_y; /* 客户区坐标(消息 lparam) */
|
||||
u64 lp = (u64)(u32)(((u32)(u16)cy << 16) | (u32)(u16)cx);
|
||||
u64 mk = 0;
|
||||
if (nbtn & 1) mk |= SHIM_MK_LBUTTON;
|
||||
if (nbtn & 2) mk |= SHIM_MK_RBUTTON;
|
||||
if (nbtn & 4) mk |= SHIM_MK_MBUTTON;
|
||||
if (s_kbd_shift) mk |= SHIM_MK_SHIFT;
|
||||
if (s_kbd_ctrl) mk |= SHIM_MK_CONTROL;
|
||||
|
||||
if (x != s_mouse_x || y != s_mouse_y) {
|
||||
s_mouse_x = x; s_mouse_y = y;
|
||||
shim_msg_push_pt(SHIM_WM_MOUSEMOVE, mk, lp, x, y);
|
||||
}
|
||||
/* 按钮边沿 */
|
||||
if ((nbtn & 1) && !(s_mouse_btn & 1)) shim_msg_push_pt(SHIM_WM_LBUTTONDOWN, mk, lp, x, y);
|
||||
if (!(nbtn & 1) && (s_mouse_btn & 1)) shim_msg_push_pt(SHIM_WM_LBUTTONUP, mk, lp, x, y);
|
||||
if ((nbtn & 2) && !(s_mouse_btn & 2)) shim_msg_push_pt(SHIM_WM_RBUTTONDOWN, mk, lp, x, y);
|
||||
if (!(nbtn & 2) && (s_mouse_btn & 2)) shim_msg_push_pt(SHIM_WM_RBUTTONUP, mk, lp, x, y);
|
||||
if ((nbtn & 4) && !(s_mouse_btn & 4)) shim_msg_push_pt(SHIM_WM_MBUTTONDOWN, mk, lp, x, y);
|
||||
if (!(nbtn & 4) && (s_mouse_btn & 4)) shim_msg_push_pt(SHIM_WM_MBUTTONUP, mk, lp, x, y);
|
||||
s_mouse_btn = nbtn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pe_shim_inject_input(u32 msg, u64 wparam, u64 lparam, i32 ptx, i32 pty) {
|
||||
if (!s_wsurf) return -1;
|
||||
shim_msg_push_pt(msg, wparam, lparam, ptx, pty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 消息空转让出点:窗口模式回调 host 泵(desktop 帧),全屏 pause。
|
||||
* pump 返回非 0 = host 请求退出 → 投递 WM_CLOSE(PE 自愿退出路径)。 */
|
||||
static void shim_msg_yield(void) {
|
||||
if (s_host_pump) {
|
||||
if (s_host_pump(s_host_ud))
|
||||
shim_msg_push(SHIM_WM_CLOSE, 0, 0);
|
||||
} else {
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,6 +1347,8 @@ static u64 __attribute__((ms_abi)) shim_RegisterClassExW(u64 wc_p) {
|
||||
}
|
||||
|
||||
static const struct limine_framebuffer *shim_fb(void) {
|
||||
/* P5 窗口模式:渲染目标是 host 提供的内存 surface(伪造 fb) */
|
||||
if (s_wsurf) return &s_wsurf_fb;
|
||||
if (!g_fb_request.response || g_fb_request.response->framebuffer_count == 0) return 0;
|
||||
return g_fb_request.response->framebuffers[0];
|
||||
}
|
||||
@@ -1315,6 +1455,9 @@ static u64 __attribute__((ms_abi)) shim_PeekMessageW(u64 msg_p, u64 hwnd,
|
||||
u64 fmin, u64 fmax, u64 remove) {
|
||||
(void)hwnd; (void)fmin; (void)fmax;
|
||||
shim_kbd_poll();
|
||||
/* P5 窗口模式:PeekMessage 驱动的渲染循环(Deaicup 型)下 PE 几乎不
|
||||
* 调 GetMessage,desktop 只能靠这里的空转让出获得 CPU 跑帧。 */
|
||||
if (g_mq_count == 0 && s_host_pump) shim_msg_yield();
|
||||
if (g_mq_count == 0) return 0;
|
||||
shim_msg_t *m = &g_msgq[g_mq_head];
|
||||
if (msg_p) *(shim_msg_t *)msg_p = *m;
|
||||
@@ -1330,7 +1473,7 @@ static u64 __attribute__((ms_abi)) shim_GetMessageW(u64 msg_p, u64 hwnd,
|
||||
for (;;) { /* 阻塞语义:轮询键盘直到有消息 */
|
||||
shim_kbd_poll();
|
||||
if (g_mq_count > 0) break;
|
||||
__asm__ volatile("pause");
|
||||
shim_msg_yield(); /* P5: 窗口模式泵 desktop,全屏 pause */
|
||||
}
|
||||
shim_msg_t *m = &g_msgq[g_mq_head];
|
||||
if (msg_p) *(shim_msg_t *)msg_p = *m;
|
||||
@@ -1434,6 +1577,7 @@ static const char *const g_cursor_bmp[12] = {
|
||||
};
|
||||
|
||||
static void shim_draw_cursor(const struct limine_framebuffer *fb) {
|
||||
if (s_wsurf) return; /* P5 窗口模式:光标由 desktop 统一绘制 */
|
||||
if (!s_mouse_ok || !g_wnd_created) return;
|
||||
i32 wx1 = g_wnd_x + g_wnd_w, wy1 = g_wnd_y + g_wnd_h;
|
||||
if (s_mouse_x < g_wnd_x || s_mouse_y < g_wnd_y) return;
|
||||
@@ -1528,6 +1672,87 @@ static u64 __attribute__((ms_abi)) shim_BitBlt(u64 dstdc, u64 x, u64 y, u64 cx,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* SetDIBitsToDevice: 不经内存 DC,直接把 DIB 位块贴上窗口 DC.
|
||||
* 语义(MSDN): (xSrc,ySrc) 是 DIB 源区域左下角(DIB 坐标系 y 向上),
|
||||
* startScan/numLines 描述 lpvBits 缓冲覆盖的扫描线区间,
|
||||
* (xDest,yDest,w,h) 是客户区目标矩形(y 向下).
|
||||
* bottom-up DIB: 缓冲行 0 = DIB 扫描线 startScan(从底数);
|
||||
* top-down DIB : 缓冲行 0 = 图像顶行 + startScan.
|
||||
* 仅支持 32bpp BI_RGB;返回实际拷贝的扫描线数. */
|
||||
static u64 __attribute__((ms_abi)) shim_SetDIBitsToDevice(
|
||||
u64 hdc, i32 xDest, i32 yDest, u32 w, u32 h,
|
||||
i32 xSrc, i32 ySrc, u32 startScan, u32 numLines,
|
||||
u64 bits_p, u64 bmi_p, u64 colorUse) {
|
||||
(void)colorUse;
|
||||
if (hdc != SHIM_HDC_WND) return 0; /* 仅支持窗口 DC 目标 */
|
||||
const struct limine_framebuffer *fb = shim_fb();
|
||||
if (!fb || !fb->address) return 0;
|
||||
if (fb->bpp != 32 && fb->bpp != 24) return 0;
|
||||
if (!bits_p || !bmi_p || w == 0 || h == 0) return 0;
|
||||
const u8 *bmi = (const u8 *)bmi_p;
|
||||
i32 bw = *(const i32 *)(bmi + 4); /* biWidth */
|
||||
i32 bh = *(const i32 *)(bmi + 8); /* biHeight(负=top-down) */
|
||||
u16 bpp = *(const u16 *)(bmi + 14); /* biBitCount */
|
||||
u32 comp = *(const u32 *)(bmi + 16); /* biCompression */
|
||||
if (bw <= 0 || bh == 0 || bpp != 32 || comp != 0 /* BI_RGB */) return 0;
|
||||
int topdown = 0;
|
||||
i32 H = bh;
|
||||
if (H < 0) { H = -H; topdown = 1; }
|
||||
|
||||
const u32 *bits = (const u32 *)bits_p;
|
||||
i32 dx0 = xDest + g_wnd_x;
|
||||
i32 dy0 = yDest + g_wnd_y;
|
||||
|
||||
u8 *fbb = (u8 *)fb->address;
|
||||
u32 rsh = fb->red_mask_shift, gsh = fb->green_mask_shift, bsh = fb->blue_mask_shift;
|
||||
u32 rsz = fb->red_mask_size ? fb->red_mask_size : 8;
|
||||
u32 gsz = fb->green_mask_size ? fb->green_mask_size : 8;
|
||||
u32 bsz = fb->blue_mask_size ? fb->blue_mask_size : 8;
|
||||
int fast = (fb->bpp == 32 && rsh == 16 && gsh == 8 && bsh == 0
|
||||
&& rsz == 8 && gsz == 8 && bsz == 8);
|
||||
|
||||
u32 copied = 0;
|
||||
for (u32 row = 0; row < h; row++) {
|
||||
/* 目标顶行 row=0 ← DIB 坐标 y=ySrc+h-1(源区域顶边) */
|
||||
i32 sy = ySrc + (i32)h - 1 - (i32)row;
|
||||
if (sy < 0 || sy >= H) continue;
|
||||
/* DIB 扫描线 sy → lpvBits 缓冲行 */
|
||||
i64 br = topdown ? ((i64)(H - 1 - sy) - (i32)startScan)
|
||||
: ((i64)sy - (i32)startScan);
|
||||
if (br < 0 || br >= (i64)numLines) continue;
|
||||
i32 fy = dy0 + (i32)row;
|
||||
if (fy < 0 || fy >= (i32)fb->height) continue;
|
||||
const u32 *src = bits + (u64)br * (u64)bw;
|
||||
u8 *dst_row = fbb + (u64)fy * fb->pitch;
|
||||
copied++;
|
||||
for (u32 col = 0; col < w; col++) {
|
||||
i32 sx = xSrc + (i32)col;
|
||||
if (sx < 0 || sx >= bw) continue;
|
||||
i32 fx = dx0 + (i32)col;
|
||||
if (fx < 0 || fx >= (i32)fb->width) continue;
|
||||
u32 px = src[sx];
|
||||
if (fast) {
|
||||
((u32 *)dst_row)[fx] = px;
|
||||
} else {
|
||||
u32 r = (px >> 16) & 0xFF, g = (px >> 8) & 0xFF, bl = px & 0xFF;
|
||||
u32 v = ((r >> (8 - rsz)) << rsh)
|
||||
| ((g >> (8 - gsz)) << gsh)
|
||||
| ((bl >> (8 - bsz)) << bsh);
|
||||
if (fb->bpp == 32) {
|
||||
((u32 *)dst_row)[fx] = v;
|
||||
} else {
|
||||
u8 *p = dst_row + (u64)fx * 3;
|
||||
p[0] = (u8)(v & 0xFF);
|
||||
p[1] = (u8)((v >> 8) & 0xFF);
|
||||
p[2] = (u8)((v >> 16) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shim_draw_cursor(fb);
|
||||
return copied;
|
||||
}
|
||||
|
||||
/* 未实现 stub(64位)— 日志告警(含调用点返回地址)并返回 0 */
|
||||
static u64 __attribute__((ms_abi)) shim_unimpl64(u64 a0, u64 a1, u64 a2, u64 a3) {
|
||||
(void)a0; (void)a1; (void)a2; (void)a3;
|
||||
@@ -1642,6 +1867,7 @@ static const pe_shim_entry g_shim_table[] = {
|
||||
{"gdi32.dll", "DeleteObject", (void*)shim_DeleteObject, 0},
|
||||
{"gdi32.dll", "DeleteDC", (void*)shim_DeleteDC, 0},
|
||||
{"gdi32.dll", "BitBlt", (void*)shim_BitBlt, 0},
|
||||
{"gdi32.dll", "SetDIBitsToDevice", (void*)shim_SetDIBitsToDevice, 0},
|
||||
{"gdi32.dll", "GetStockObject", (void*)shim_GetStockObject, 0},
|
||||
{"gdi32.dll", "GdiFlush", (void*)shim_GdiFlush, 0},
|
||||
/* msvcrt.dll */
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
pe/pe_shim.o: pe\pe_shim.c pe\pe_shim.h include\utsm\types.h \
|
||||
include\utsm\arena.h include\utsm\log.h pe\..\arch\x86_64\limine.h
|
||||
pe\pe_dll_manager.h pe\..\include\utsm\pe.h include\utsm\arena.h \
|
||||
include\utsm\log.h pe\..\arch\x86_64\limine.h
|
||||
pe\pe_shim.h:
|
||||
include\utsm\types.h:
|
||||
pe\pe_dll_manager.h:
|
||||
pe\..\include\utsm\pe.h:
|
||||
include\utsm\arena.h:
|
||||
include\utsm\log.h:
|
||||
pe\..\arch\x86_64\limine.h:
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* shim 伪模块句柄(LoadLibrary 对已知 shim DLL 返回值)。
|
||||
* 真实 DLL 句柄 = pe_dll_load 镜像基址,与此值域区分。 */
|
||||
#define PE_SHIM_FAKE_MODULE 0x10000020ULL
|
||||
|
||||
/* 32位 shim:从模拟栈读取参数(cdecl 语义,调用者清栈)。
|
||||
* esp 指向模拟栈(第一个参数在 esp[0])。返回值 = EAX。 */
|
||||
typedef u64 (*pe_shim_fn32)(void *esp);
|
||||
@@ -82,4 +86,23 @@ const char *pe_shim_get_cmdline(void);
|
||||
* 完成转换;base=0 表示非解释器上下文(原生 PE32+ 路径),gp() 恒等映射。 */
|
||||
void pe_shim_set_emu_base(u64 base);
|
||||
|
||||
/* ===== P5 窗口模式(desktop 嵌入)=====
|
||||
* pe_service 在 run_windowed 前调用 pe_shim_set_window_host 挂起配置;
|
||||
* pe_shim_init(run 开头)重置 shim 状态后应用该配置。host==NULL 或
|
||||
* host->surface==NULL 时保持全屏独占行为(默认)。 */
|
||||
struct pe_window_host; /* 定义见 utsm/pe.h */
|
||||
void pe_shim_set_window_host(const struct pe_window_host *host);
|
||||
|
||||
/* 窗口模式输入注入(仅窗口模式生效,全屏返回 -1)。
|
||||
* inject_scancode: set-1 扫描码(bit7=release),e0_prefix!=0 为 E0 扩展。
|
||||
* inject_pointer : surface 局部坐标 + 按钮位(bit0/1/2=L/R/M),
|
||||
* 内部与上次状态比较合成 MOUSEMOVE/BUTTON 边沿。
|
||||
* inject_input : 直接投递任意 Win32 消息(WM_CLOSE 等宿主控制消息)。 */
|
||||
int pe_shim_inject_scancode(u32 scancode, u32 e0_prefix);
|
||||
int pe_shim_inject_pointer(i32 x, i32 y, u32 buttons);
|
||||
int pe_shim_inject_input(u32 msg, u64 wparam, u64 lparam, i32 ptx, i32 pty);
|
||||
|
||||
/* 窗口模式是否激活(1=渲染目标为 host surface)。 */
|
||||
int pe_shim_window_mode(void);
|
||||
|
||||
#endif /* PE_SHIM_H */
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/linux_compat.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <ipc_proto.h>
|
||||
@@ -122,6 +123,35 @@ static long hcall_shm_info(u64 a0, u64 a1, u64 a2) {
|
||||
return UTSM_HCALL_OK;
|
||||
}
|
||||
|
||||
/* VSCode integration Phase 1: query graphics surface pool.
|
||||
* Mirrors hcall_shm_info: writes gpa + size into guest-provided out-pointers.
|
||||
* Linux user-space then mmaps /dev/utsm at the returned GPA to obtain a
|
||||
* writable scanout buffer (Phase 3 virtio-gpu shadow buffer). */
|
||||
static long hcall_surface_info(u64 a0, u64 a1, u64 a2) {
|
||||
/* a0 = GPA of gpa_out (u64), a1 = GPA of size_out (u64) */
|
||||
(void)a2;
|
||||
|
||||
u64 surface_gpa = 0, surface_size = 0;
|
||||
if (linux_get_surface_info(0, &surface_gpa, &surface_size) != 0) {
|
||||
/* Surface pool not initialized (Linux guest not loaded, or alloc failed) */
|
||||
return UTSM_HCALL_NOMEM;
|
||||
}
|
||||
|
||||
/* Write results to guest memory */
|
||||
if (a0 != 0 && guest_write_u64(a0, surface_gpa) != 0) {
|
||||
log_error("[HCALL] SURFACE_INFO: failed to write gpa to guest");
|
||||
return UTSM_HCALL_INVAL;
|
||||
}
|
||||
if (a1 != 0 && guest_write_u64(a1, surface_size) != 0) {
|
||||
log_error("[HCALL] SURFACE_INFO: failed to write size to guest");
|
||||
return UTSM_HCALL_INVAL;
|
||||
}
|
||||
|
||||
log_hex64("[HCALL] SURFACE_INFO: gpa=", surface_gpa);
|
||||
log_hex64("[HCALL] SURFACE_INFO: size=", surface_size);
|
||||
return UTSM_HCALL_OK;
|
||||
}
|
||||
|
||||
static long hcall_cap_validate(u64 a0, u64 a1, u64 a2) {
|
||||
/* Phase 1.3: stub — capability validation not yet implemented */
|
||||
(void)a0; (void)a1; (void)a2;
|
||||
@@ -284,6 +314,9 @@ int hypercall_handle(u64 guest_rax, u64 guest_rdi, u64 guest_rsi, u64 guest_rdx,
|
||||
case UTSM_HCALL_SHM_INFO:
|
||||
result = hcall_shm_info(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
case UTSM_HCALL_SURFACE_INFO:
|
||||
result = hcall_surface_info(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
case UTSM_HCALL_CAP_VALIDATE:
|
||||
result = hcall_cap_validate(guest_rdi, guest_rsi, guest_rdx);
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
vmm/hypercall.o: vmm\hypercall.c include\utsm\hypercall.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h \
|
||||
include\utsm\linux_compat.h include\utsm\log.h \
|
||||
vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\linux_compat.h include\utsm\linux_loader.h \
|
||||
include\utsm\log.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\hypercall.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
@@ -11,5 +11,6 @@ include\utsm\vmm.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
|
||||
@@ -16,14 +16,20 @@
|
||||
|
||||
#include <utsm/linux_compat.h>
|
||||
#include <utsm/linux_resume.h>
|
||||
#include <utsm/linux_loader.h> /* VSCode Phase 5: linux_find_vscode_module */
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/virtio_mmio.h> /* VSCode Phase 2: virtio_input_push_* */
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/block.h>
|
||||
#include <ipc_proto.h>
|
||||
#include "../../tools/fat32_io.h"
|
||||
|
||||
/* 兼容层全局配置(由 UTSM kernel/main.c 定义) */
|
||||
extern char g_compat_lib_path[];
|
||||
extern char g_compat_linux_guest_path[];
|
||||
|
||||
/* ===== 内部辅助 ===== */
|
||||
|
||||
static u64 my_strlen(const char *s) {
|
||||
@@ -64,9 +70,17 @@ static int lxc_is_available(void) {
|
||||
return (g_compat_ready && linux_is_parked() && ipc_shm_is_ready()) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* 前向声明:payload_pool 拷出助手(定义在文件传输段,exec 新协议复用) */
|
||||
static u64 lxc_pool_copyout(char *buf, u64 cap, u32 data_len);
|
||||
|
||||
/* 等待 Linux daemon 的响应消息。
|
||||
* linux_resume() 返回后,daemon 已 park,所有响应消息应已在 ring 中。
|
||||
* 我们循环 pop 直到拿到 EXEC_EXIT 或 ring 空(超时保护)。 */
|
||||
* 我们循环 pop 直到拿到 EXEC_EXIT 或 ring 空(超时保护)。
|
||||
*
|
||||
* 新协议(P3):daemon 把 stdout 直写 payload_pool,EXEC_EXIT 为 16B
|
||||
* (exit_code + flags + stdout_len + stdout_total),flags&F_POOL 时
|
||||
* 从 pool 拷出;F_TRUNC/F_TIMEOUT 记串口日志。旧协议(8B exit_code)
|
||||
* 输出仍走 EXEC_STDOUT 内联,下方分支兼容。 */
|
||||
static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
u64 *exit_code) {
|
||||
u64 written = 0;
|
||||
@@ -84,7 +98,7 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
switch (msg.type) {
|
||||
case UTSM_MSG_EXEC_STDOUT:
|
||||
case UTSM_MSG_EXEC_STDERR: {
|
||||
/* 拼接到 stdout 缓冲区(stderr 也并入,简化调用方处理) */
|
||||
/* 旧式内联输出(新协议下 stdout 走 pool,正常不会再收到) */
|
||||
u32 n = msg.data_len;
|
||||
if (out_buf && out_cap > written) {
|
||||
u32 space = (u32)(out_cap - written);
|
||||
@@ -99,6 +113,22 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
struct ipc_exec_exit ex;
|
||||
my_memcpy(&ex, msg.data, sizeof(ex));
|
||||
exit_val = ex.exit_code;
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_POOL) {
|
||||
/* stdout 已 staged 在 payload_pool[0..stdout_len) */
|
||||
written = lxc_pool_copyout(out_buf, out_cap,
|
||||
ex.stdout_len);
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_TRUNC) {
|
||||
log_warn("[LNXC] exec: output truncated at pool cap");
|
||||
log_hex64("[LNXC] exec: total bytes=", ex.stdout_total);
|
||||
}
|
||||
if (ex.flags & UTSM_EXEC_EXIT_F_TIMEOUT)
|
||||
log_warn("[LNXC] exec: child timed out, SIGKILLed");
|
||||
}
|
||||
} else if (msg.data_len >= 8) {
|
||||
/* 旧式 8B:exit_code + reserved */
|
||||
u32 ec = 0;
|
||||
my_memcpy(&ec, msg.data, 4);
|
||||
exit_val = ec;
|
||||
}
|
||||
got_exit = 1;
|
||||
break;
|
||||
@@ -117,9 +147,13 @@ static int lxc_drain_responses(char *out_buf, u64 out_cap, u64 *out_len,
|
||||
return got_exit ? 0 : -4; /* -4 = 超时无 exit 响应 */
|
||||
}
|
||||
|
||||
static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
char *stdout_buf, u64 stdout_cap, u64 *stdout_len,
|
||||
u64 *exit_code) {
|
||||
/* 构建并发送 EXEC_REQUEST,唤醒 guest,然后 drain 响应。
|
||||
* flags 取 0(同步,捕获 stdout)或 UTSM_EXEC_FLAG_ASYNC(detach 立即返回)。
|
||||
* 返回 0 = 收到 EXEC_EXIT(exit_code 已写出),负值失败。 */
|
||||
static int lxc_exec_internal(const char *path, int argc,
|
||||
const char *const *argv, u32 flags,
|
||||
char *stdout_buf, u64 stdout_cap, u64 *stdout_len,
|
||||
u64 *exit_code) {
|
||||
if (!lxc_is_available()) {
|
||||
return -1;
|
||||
}
|
||||
@@ -131,7 +165,7 @@ static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
struct ipc_exec_request req;
|
||||
my_memzero(&req, sizeof(req));
|
||||
req.argc = (u32)argc;
|
||||
req.flags = 0;
|
||||
req.flags = flags;
|
||||
|
||||
u64 plen = my_strlen(path);
|
||||
if (plen >= UTSM_EXEC_PATH_MAX) plen = UTSM_EXEC_PATH_MAX - 1;
|
||||
@@ -167,6 +201,29 @@ static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
return lxc_drain_responses(stdout_buf, stdout_cap, stdout_len, exit_code);
|
||||
}
|
||||
|
||||
static int lxc_exec(const char *path, int argc, const char *const *argv,
|
||||
char *stdout_buf, u64 stdout_cap, u64 *stdout_len,
|
||||
u64 *exit_code) {
|
||||
return lxc_exec_internal(path, argc, argv, 0,
|
||||
stdout_buf, stdout_cap, stdout_len, exit_code);
|
||||
}
|
||||
|
||||
/* VSCode Phase 5: 异步执行——daemon fork+setsid+detach 后立即回
|
||||
* EXEC_EXIT(0),不捕获输出、不等待退出。用于 X server / VSCode 等长
|
||||
* 生命周期 GUI 进程。返回 0 = spawn 成功。 */
|
||||
static int lxc_exec_async(const char *path, int argc,
|
||||
const char *const *argv) {
|
||||
u64 exit_code = 1;
|
||||
int rc = lxc_exec_internal(path, argc, argv, UTSM_EXEC_FLAG_ASYNC,
|
||||
0, 0, 0, &exit_code);
|
||||
if (rc != 0) return rc;
|
||||
if (exit_code != 0) {
|
||||
log_hex64("[LNXC] exec_async: daemon spawn failed, code=", exit_code);
|
||||
return -5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxc_status(char *buf, u64 cap) {
|
||||
if (!buf || cap == 0) return 0;
|
||||
const char *s;
|
||||
@@ -445,6 +502,73 @@ static int lxc_file_push(const char *fat32_name, const char *linux_path,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== VSCode Phase 2: 输入转发(PS/2 → virtio-input) ===== */
|
||||
|
||||
/* 上次鼠标按钮状态,用于边沿检测(press/release)。 */
|
||||
static u8 g_prev_mouse_buttons = 0;
|
||||
|
||||
static void lxc_input_forward_keyboard(u16 code, u32 value) {
|
||||
virtio_input_push_keyboard(code, value);
|
||||
}
|
||||
|
||||
static void lxc_input_forward_mouse(i32 dx, i32 dy, u8 buttons) {
|
||||
/* 相对位移 */
|
||||
if (dx != 0) virtio_input_push_mouse_rel(VIO_INPUT_REL_X, dx);
|
||||
if (dy != 0) virtio_input_push_mouse_rel(VIO_INPUT_REL_Y, dy);
|
||||
|
||||
/* 按钮变化检测:XOR 找出变化的位 */
|
||||
u8 changed = buttons ^ g_prev_mouse_buttons;
|
||||
if (changed & 0x01)
|
||||
virtio_input_push_mouse_button(VIO_INPUT_BTN_LEFT, (buttons & 0x01) ? 1 : 0);
|
||||
if (changed & 0x02)
|
||||
virtio_input_push_mouse_button(VIO_INPUT_BTN_RIGHT, (buttons & 0x02) ? 1 : 0);
|
||||
if (changed & 0x04)
|
||||
virtio_input_push_mouse_button(VIO_INPUT_BTN_MIDDLE, (buttons & 0x04) ? 1 : 0);
|
||||
g_prev_mouse_buttons = buttons;
|
||||
|
||||
/* SYN_REPORT:libinput 依赖此帧结束标记 */
|
||||
virtio_input_push_mouse_syn();
|
||||
}
|
||||
|
||||
/* ===== VSCode Phase 3: virtio-gpu scanout 查询转发 =====
|
||||
* desktop 在 IDE attached 时周期调用,把 guest 合成的帧 blit 到 framebuffer。
|
||||
* 我们只做字段拷贝(linux_compat_scanout_info 与 virtio_gpu_scanout_info 布局一致)。 */
|
||||
static int lxc_gpu_get_scanout_info(struct linux_compat_scanout_info *out) {
|
||||
if (!out) return -1;
|
||||
struct virtio_gpu_scanout_info vi;
|
||||
int rc = virtio_gpu_get_scanout_info(&vi);
|
||||
if (rc != 0) {
|
||||
out->host_vaddr = 0;
|
||||
out->width = 0; out->height = 0; out->stride = 0;
|
||||
out->dirty = 0; out->enabled = 0;
|
||||
out->dirty_x = 0; out->dirty_y = 0;
|
||||
out->dirty_w = 0; out->dirty_h = 0;
|
||||
out->cursor_visible = 0; out->cursor_x = 0; out->cursor_y = 0;
|
||||
out->cursor_hot_x = 0; out->cursor_hot_y = 0;
|
||||
out->cursor_w = 0; out->cursor_h = 0; out->cursor_bitmap = 0;
|
||||
return rc;
|
||||
}
|
||||
out->host_vaddr = vi.host_vaddr;
|
||||
out->width = vi.width;
|
||||
out->height = vi.height;
|
||||
out->stride = vi.stride;
|
||||
out->dirty = vi.dirty;
|
||||
out->enabled = vi.enabled;
|
||||
out->dirty_x = vi.dirty_x;
|
||||
out->dirty_y = vi.dirty_y;
|
||||
out->dirty_w = vi.dirty_w;
|
||||
out->dirty_h = vi.dirty_h;
|
||||
out->cursor_visible = vi.cursor_visible;
|
||||
out->cursor_x = vi.cursor_x;
|
||||
out->cursor_y = vi.cursor_y;
|
||||
out->cursor_hot_x = vi.cursor_hot_x;
|
||||
out->cursor_hot_y = vi.cursor_hot_y;
|
||||
out->cursor_w = vi.cursor_w;
|
||||
out->cursor_h = vi.cursor_h;
|
||||
out->cursor_bitmap = vi.cursor_bitmap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== 服务表 ===== */
|
||||
|
||||
static const linux_compat_service g_lxc_service = {
|
||||
@@ -457,8 +581,376 @@ static const linux_compat_service g_lxc_service = {
|
||||
lxc_file_write,
|
||||
lxc_file_pull,
|
||||
lxc_file_push,
|
||||
lxc_input_forward_keyboard,
|
||||
lxc_input_forward_mouse,
|
||||
lxc_gpu_get_scanout_info,
|
||||
lxc_exec_async,
|
||||
};
|
||||
|
||||
const linux_compat_service *linux_compat_get_service(void) {
|
||||
return &g_lxc_service;
|
||||
}
|
||||
|
||||
/* ===== Phase 2: SYSTEM/lib .so 全量推送到 Linux guest =====
|
||||
*
|
||||
* 启动时把 FAT32 根目录下 lib/ 子目录(g_compat_lib_path)中所有 .so
|
||||
* 文件流式推送到 Linux guest 的 g_compat_linux_guest_path 目录。
|
||||
* 流式推送:按簇(4KB)读取 FAT32 文件 → 立即 lxc_file_write 写入 guest,
|
||||
* 不依赖 fat32_io.h 的整文件 256KB 缓冲,可处理 MB 级 .so。
|
||||
*
|
||||
* 路径假设:build.ps1 把 SYSTEM/ 打包为 FAT32 根,因此 Windows
|
||||
* D:\Code\Deshab\SYSTEM\lib\ 在 FAT32 上对应根目录下的 "lib/" 子目录。
|
||||
*/
|
||||
|
||||
/* 把内存中已有数据按 pool 容量分块推送到 Linux 文件。
|
||||
* 用于整文件内存数据的批量上传。 */
|
||||
__attribute__((unused))
|
||||
static int lxc_file_push_mem(const char *linux_path, const u8 *data, u32 size) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!linux_path || (!data && size > 0)) return -1;
|
||||
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
u64 off = 0;
|
||||
for (;;) {
|
||||
u64 remain = (u64)size - off;
|
||||
u64 chunk = remain > pool_cap ? pool_cap : remain;
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write(linux_path, off, (const char *)data + off,
|
||||
chunk, &written);
|
||||
if (rc != 0) return rc;
|
||||
if (written != chunk) return -5; /* 短写 */
|
||||
off += written;
|
||||
if (off >= size) break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 流式推送:按簇读取 FAT32 文件,立即写入 Linux guest。
|
||||
* first_clus : FAT32 文件首簇号
|
||||
* file_size : 文件字节大小
|
||||
* linux_path : guest 端目标绝对路径
|
||||
* 返回 0 成功,负值失败。
|
||||
* 复用 lxc_f32_ensure() 已初始化的 FAT32 库(f32_blk_read)。 */
|
||||
static int lxc_push_fat32_file_streaming(u32 first_clus, u32 file_size,
|
||||
const char *linux_path) {
|
||||
if (!lxc_is_available()) return -1;
|
||||
if (!linux_path) return -1;
|
||||
if (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 pool_cap = ipc_shm_payload_capacity();
|
||||
u32 cluster_bytes = spc * 512;
|
||||
if (cluster_bytes > sizeof(f32_cluster)) cluster_bytes = sizeof(f32_cluster);
|
||||
|
||||
/* 用 f32_data 作为单簇中转缓冲(每次最多 pool_cap 字节) */
|
||||
u32 buf_cap = pool_cap;
|
||||
if (buf_cap > sizeof(f32_data)) buf_cap = sizeof(f32_data);
|
||||
|
||||
u64 off = 0;
|
||||
u32 fc = first_clus;
|
||||
u32 buf_used = 0;
|
||||
while (fc >= 2 && fc < 0x0FFFFFF8 && off < file_size) {
|
||||
u32 fc_lba = data_lba + (fc - 2) * spc;
|
||||
u32 fc_bytes = spc * 512;
|
||||
if (fc_bytes > sizeof(f32_cluster)) fc_bytes = sizeof(f32_cluster);
|
||||
if (f32_read_sectors(fc_lba, spc, f32_cluster) != 0) return -3;
|
||||
|
||||
/* 把当前簇数据按 buf_cap 分批写入 */
|
||||
u32 ci = 0;
|
||||
while (ci < fc_bytes && off + ci < file_size) {
|
||||
u32 space = buf_cap - buf_used;
|
||||
u32 copy = fc_bytes - ci;
|
||||
if (copy > space) copy = space;
|
||||
if (copy > file_size - (u32)(off + ci)) copy = (u32)(file_size - (off + ci));
|
||||
for (u32 b = 0; b < copy; b++) {
|
||||
f32_data[buf_used + b] = f32_cluster[ci + b];
|
||||
}
|
||||
buf_used += copy;
|
||||
ci += copy;
|
||||
/* 缓冲满 → 写入 guest */
|
||||
if (buf_used >= buf_cap || off + ci >= file_size) {
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write(linux_path, off, (const char *)f32_data,
|
||||
buf_used, &written);
|
||||
if (rc != 0) return rc;
|
||||
if (written != buf_used) return -5;
|
||||
off += written;
|
||||
buf_used = 0;
|
||||
}
|
||||
}
|
||||
/* 跟随 FAT 链 */
|
||||
u32 fo = fat_byte_off + fc * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
/* 处理剩余缓冲 */
|
||||
if (buf_used > 0 && off < file_size) {
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write(linux_path, off, (const char *)f32_data,
|
||||
buf_used, &written);
|
||||
if (rc != 0) return rc;
|
||||
off += written;
|
||||
}
|
||||
return (off >= file_size) ? 0 : -6;
|
||||
}
|
||||
|
||||
/* .so 文件收集回调上下文 */
|
||||
typedef struct {
|
||||
u32 count; /* 已收集的 .so 数量 */
|
||||
u32 max_count; /* 上限 */
|
||||
int error; /* 0=正常, 非 0=出错 */
|
||||
} lxc_so_collect_ctx;
|
||||
|
||||
/* f32_list_dir 回调:过滤 .so 后缀的文件,记录发现 */
|
||||
static int lxc_so_collect_cb(const f32_entry *e, void *user_data) {
|
||||
lxc_so_collect_ctx *ctx = (lxc_so_collect_ctx *)user_data;
|
||||
if (!e || !ctx) return 0;
|
||||
if (e->is_dir) return 0; /* 跳过子目录 */
|
||||
|
||||
/* 判断 .so 后缀(大小写不敏感) */
|
||||
const char *name = e->has_lfn ? e->long_name : e->name;
|
||||
if (!fat32_lfn_ends_with_ci(name, ".so")) return 0;
|
||||
|
||||
if (ctx->count < ctx->max_count) {
|
||||
ctx->count++;
|
||||
} else {
|
||||
ctx->error = 1; /* 超过上限 */
|
||||
return 1; /* 停止遍历 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 单文件推送回调上下文(用于流式推送所有 .so 到 guest) */
|
||||
typedef struct {
|
||||
const char *guest_path; /* guest 端目录绝对路径 */
|
||||
u32 pushed; /* 已推送文件数 */
|
||||
u32 failed; /* 推送失败文件数 */
|
||||
} lxc_push_ctx;
|
||||
|
||||
/* f32_list_dir 回调:流式推送每个 .so 到 guest */
|
||||
static int lxc_push_so_cb(const f32_entry *e, void *user_data) {
|
||||
lxc_push_ctx *ctx = (lxc_push_ctx *)user_data;
|
||||
if (!e || !ctx) return 0;
|
||||
if (e->is_dir) return 0;
|
||||
|
||||
const char *name = e->has_lfn ? e->long_name : e->name;
|
||||
if (!fat32_lfn_ends_with_ci(name, ".so")) return 0;
|
||||
|
||||
/* 构造 guest 端路径: <guest_path>/<filename> */
|
||||
char linux_path[256];
|
||||
u32 pl = 0;
|
||||
while (ctx->guest_path[pl] && pl + 1 < sizeof(linux_path)) {
|
||||
linux_path[pl] = ctx->guest_path[pl];
|
||||
pl++;
|
||||
}
|
||||
if (pl + 1 >= sizeof(linux_path)) {
|
||||
ctx->failed++;
|
||||
return 0; /* 路径过长,跳过 */
|
||||
}
|
||||
if (linux_path[pl - 1] != '/') {
|
||||
linux_path[pl++] = '/';
|
||||
}
|
||||
/* 追加文件名 */
|
||||
u32 nl = 0;
|
||||
while (name[nl] && pl + 1 < sizeof(linux_path)) {
|
||||
linux_path[pl++] = name[nl++];
|
||||
}
|
||||
linux_path[pl] = 0;
|
||||
|
||||
log_info("[LNXC] pushing .so:");
|
||||
log_info(name);
|
||||
log_info(" -> ");
|
||||
log_info(linux_path);
|
||||
|
||||
int rc = lxc_push_fat32_file_streaming(e->clus, e->size, linux_path);
|
||||
if (rc == 0) {
|
||||
ctx->pushed++;
|
||||
log_hex64("[LNXC] push ok size=", e->size);
|
||||
} else {
|
||||
ctx->failed++;
|
||||
log_warn("[LNXC] push failed");
|
||||
log_hex64("[LNXC] push rc=", (u64)(i64)rc);
|
||||
}
|
||||
return 0; /* 继续遍历下一个 */
|
||||
}
|
||||
|
||||
/* 同步 FAT32 lib 目录下所有 .so 到 Linux guest。
|
||||
* 调用时机:linux_compat_init 成功后。失败不阻断启动。
|
||||
* guest_lib_path : guest 端库目录路径(如 "/usr/lib/deshab")
|
||||
* 返回 0 成功,负值失败。 */
|
||||
int lxc_sync_lib_dir(const char *guest_lib_path) {
|
||||
if (!guest_lib_path) return -1;
|
||||
if (!lxc_is_available()) {
|
||||
log_warn("[LNXC] sync_lib_dir: service not available");
|
||||
return -1;
|
||||
}
|
||||
int rc = lxc_f32_ensure();
|
||||
if (rc != 0) {
|
||||
log_warn("[LNXC] sync_lib_dir: FAT32 not available");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 定位 lib 目录簇(lib 在 FAT32 根目录下,不是 SYSTEM/lib!) */
|
||||
u32 lib_clus = 0;
|
||||
if (f32_find_path_dir_lfn(g_compat_lib_path, &lib_clus) != 0) {
|
||||
log_warn("[LNXC] sync_lib_dir: lib dir not found:");
|
||||
log_warn(g_compat_lib_path);
|
||||
return -2; /* 目录不存在(容忍:可能是首次启动未放库) */
|
||||
}
|
||||
log_info("[LNXC] sync_lib_dir: lib dir found");
|
||||
log_hex64("[LNXC] lib_clus=", lib_clus);
|
||||
|
||||
/* 先枚举确认有 .so 文件(用于日志) */
|
||||
lxc_so_collect_ctx collect = {0, 256, 0};
|
||||
f32_list_dir(lib_clus, lxc_so_collect_cb, &collect);
|
||||
if (collect.error) {
|
||||
log_warn("[LNXC] sync_lib_dir: too many .so files, capped at 256");
|
||||
}
|
||||
log_hex64("[LNXC] sync_lib_dir: .so count=", collect.count);
|
||||
if (collect.count == 0) {
|
||||
log_info("[LNXC] sync_lib_dir: no .so files, nothing to push");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 流式推送每个 .so */
|
||||
lxc_push_ctx pctx;
|
||||
pctx.guest_path = guest_lib_path;
|
||||
pctx.pushed = 0;
|
||||
pctx.failed = 0;
|
||||
f32_list_dir(lib_clus, lxc_push_so_cb, &pctx);
|
||||
|
||||
log_hex64("[LNXC] sync_lib_dir: pushed=", pctx.pushed);
|
||||
log_hex64("[LNXC] sync_lib_dir: failed=", pctx.failed);
|
||||
return (pctx.failed == 0) ? 0 : -3;
|
||||
}
|
||||
|
||||
/* ===== VSCode Phase 5: VSCode tarball → guest /opt/vscode 安装 =====
|
||||
*
|
||||
* tarball 作为 Limine boot module(路径含 "vscode")随镜像预加载到主机
|
||||
* 内存;本函数把它流式推入 guest /tmp/vscode.tar.gz,再在 guest 内解包到
|
||||
* /opt/vscode(/opt 位于 extra-rootfs overlay,持久可写,二次启动经
|
||||
* 标记探测直接跳过)。 */
|
||||
|
||||
/* guest 内文件可执行性探测:同步 exec /bin/test -e <path>。
|
||||
* 返回 1 = 存在,0 = 不存在或探测失败。 */
|
||||
static int lxc_guest_path_exists(const char *path) {
|
||||
const char *argv[3];
|
||||
argv[0] = "test";
|
||||
argv[1] = "-e";
|
||||
argv[2] = path;
|
||||
u64 exit_code = 1;
|
||||
int rc = lxc_exec("/bin/test", 3, argv, 0, 0, 0, &exit_code);
|
||||
return (rc == 0 && exit_code == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
int lxc_vscode_install(void) {
|
||||
if (!lxc_is_available()) {
|
||||
log_warn("[LNXC] vscode install: service not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 1. 已安装标记:overlay 持久化后二次启动直接跳过 */
|
||||
if (lxc_guest_path_exists("/opt/vscode/bin/code")) {
|
||||
log_info("[LNXC] vscode: already installed, skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 2. 查找 tarball boot module(缺失属正常——未投放则跳过安装) */
|
||||
u64 tar_size = 0;
|
||||
const u8 *tar = (const u8 *)linux_find_vscode_module(&tar_size);
|
||||
if (!tar || tar_size == 0) {
|
||||
return -11;
|
||||
}
|
||||
|
||||
log_info("[LNXC] vscode: streaming tarball to guest /tmp ...");
|
||||
log_hex64("[LNXC] vscode: tarball size=", tar_size);
|
||||
|
||||
/* 3. 流式推送:module 内存 → /tmp/vscode.tar.gz(payload pool 分块) */
|
||||
u32 pool_cap = ipc_shm_payload_capacity();
|
||||
u64 off = 0;
|
||||
while (off < tar_size) {
|
||||
u64 chunk = tar_size - off;
|
||||
if (chunk > pool_cap) chunk = pool_cap;
|
||||
u64 written = 0;
|
||||
int rc = lxc_file_write("/tmp/vscode.tar.gz", off,
|
||||
(const char *)tar + off, chunk, &written);
|
||||
if (rc != 0 || written != chunk) {
|
||||
log_warn("[LNXC] vscode: tarball push failed");
|
||||
log_hex64("[LNXC] vscode: push off=", off);
|
||||
return -12;
|
||||
}
|
||||
off += written;
|
||||
}
|
||||
log_info("[LNXC] vscode: tarball pushed ok");
|
||||
|
||||
/* 4. mkdir -p /opt/vscode */
|
||||
{
|
||||
const char *argv[3];
|
||||
argv[0] = "mkdir";
|
||||
argv[1] = "-p";
|
||||
argv[2] = "/opt/vscode";
|
||||
u64 ec = 1;
|
||||
int rc = lxc_exec("/bin/mkdir", 3, argv, 0, 0, 0, &ec);
|
||||
if (rc != 0 || ec != 0) {
|
||||
log_warn("[LNXC] vscode: mkdir /opt/vscode failed");
|
||||
return -13;
|
||||
}
|
||||
}
|
||||
|
||||
/* 5. 解包:VSCode 官方 tarball 顶层为 VSCode-linux-x64/,
|
||||
* --strip-components=1 把内容直接落入 /opt/vscode;
|
||||
* 若 tarball 是平铺结构(无顶层目录)则回退不解层重试。 */
|
||||
int unpacked = 0;
|
||||
{
|
||||
const char *argv[7];
|
||||
argv[0] = "tar";
|
||||
argv[1] = "-xzf";
|
||||
argv[2] = "/tmp/vscode.tar.gz";
|
||||
argv[3] = "-C";
|
||||
argv[4] = "/opt/vscode";
|
||||
argv[5] = "--strip-components=1";
|
||||
argv[6] = 0;
|
||||
u64 ec = 1;
|
||||
int rc = lxc_exec("/bin/tar", 6, argv, 0, 0, 0, &ec);
|
||||
if (rc == 0 && ec == 0) unpacked = 1;
|
||||
}
|
||||
if (!unpacked) {
|
||||
log_warn("[LNXC] vscode: strip-components extract failed, trying flat");
|
||||
const char *argv[5];
|
||||
argv[0] = "tar";
|
||||
argv[1] = "-xzf";
|
||||
argv[2] = "/tmp/vscode.tar.gz";
|
||||
argv[3] = "-C";
|
||||
argv[4] = "/opt/vscode";
|
||||
u64 ec = 1;
|
||||
int rc = lxc_exec("/bin/tar", 5, argv, 0, 0, 0, &ec);
|
||||
if (rc == 0 && ec == 0) unpacked = 1;
|
||||
}
|
||||
|
||||
/* 6. 清理 guest 端 tarball(成败都清,/tmp 是 tmpfs 占 guest 内存) */
|
||||
{
|
||||
const char *argv[3];
|
||||
argv[0] = "rm";
|
||||
argv[1] = "-f";
|
||||
argv[2] = "/tmp/vscode.tar.gz";
|
||||
u64 ec = 0;
|
||||
lxc_exec("/bin/rm", 3, argv, 0, 0, 0, &ec);
|
||||
}
|
||||
|
||||
if (!unpacked) {
|
||||
log_warn("[LNXC] vscode: extract failed");
|
||||
return -13;
|
||||
}
|
||||
|
||||
/* 7. 验证:/opt/vscode/bin/code 存在即可启动 */
|
||||
if (!lxc_guest_path_exists("/opt/vscode/bin/code")) {
|
||||
log_warn("[LNXC] vscode: verify failed (bin/code missing)");
|
||||
return -14;
|
||||
}
|
||||
|
||||
log_info("[LNXC] vscode: install ok (/opt/vscode/bin/code)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
vmm/linux_compat.o: vmm\linux_compat.c include\utsm\linux_compat.h \
|
||||
include\utsm\types.h include\utsm\linux_resume.h \
|
||||
include\utsm\ipc_shm.h ..\utsm-ipc\ipc_proto.h include\utsm\vmm.h \
|
||||
include\utsm\log.h include\utsm\block.h vmm\..\..\tools\fat32_io.h
|
||||
include\utsm\linux_loader.h include\utsm\ipc_shm.h \
|
||||
..\utsm-ipc\ipc_proto.h include\utsm\vmm.h include\utsm\virtio_mmio.h \
|
||||
include\utsm\log.h include\utsm\block.h vmm\..\..\tools\fat32_io.h \
|
||||
vmm\..\..\tools\fat32_lfn.h
|
||||
include\utsm\linux_compat.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_resume.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\ipc_shm.h:
|
||||
..\utsm-ipc\ipc_proto.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\block.h:
|
||||
vmm\..\..\tools\fat32_io.h:
|
||||
vmm\..\..\tools\fat32_lfn.h:
|
||||
|
||||
+171
-13
@@ -49,6 +49,11 @@ static u64 str_len(const char *s) {
|
||||
static struct linux_guest_info g_guest;
|
||||
static const char *g_default_cmdline = LINUX_DEFAULT_CMDLINE;
|
||||
|
||||
/* VSCode Phase 3: host virtual address of the graphics surface pool.
|
||||
* The virtio-gpu 2D backend composites the scanout into this buffer; the
|
||||
* desktop reads it to blit guest graphics onto the IDE host area. 0 = unused. */
|
||||
static void *g_surface_vaddr = 0;
|
||||
|
||||
/* ===== Guest page table helpers =====
|
||||
*
|
||||
* Build identity-mapped 4-level page tables for the guest.
|
||||
@@ -220,9 +225,19 @@ static int setup_e820(void *bootparams_virt) {
|
||||
table[n].type = E820_TYPE_RESERVED;
|
||||
n++;
|
||||
|
||||
/* Entry 5: 0x04100000-0x0FFFFFFF: usable RAM (general RAM, ~188MB) */
|
||||
table[n].addr = 0x04100000;
|
||||
table[n].size = 0x0BF00000; /* 191MB */
|
||||
/* Entry 5: 0x04100000-0x04FFFFFF: reserved (Graphics surface pool, 16MB)
|
||||
* VSCode integration Phase 1: separate DMA region EPT-mapped into the
|
||||
* guest; Linux user-space mmaps it via /dev/utsm as the scanout buffer. */
|
||||
table[n].addr = LINUX_GUEST_SURFACE_GPA;
|
||||
table[n].size = LINUX_GUEST_SURFACE_SIZE; /* 16MB */
|
||||
table[n].type = E820_TYPE_RESERVED;
|
||||
n++;
|
||||
|
||||
/* Entry 6: 0x05000000-0x5FFFFFFF: usable RAM (general RAM, ~1.4GB)
|
||||
* VSCode integration Phase 1: expanded from 191MB to 1.4GB to fit
|
||||
* VSCode + Electron + GUI stack (Xorg/modesetting + GTK + mesa). */
|
||||
table[n].addr = LINUX_GUEST_RAM_GPA;
|
||||
table[n].size = LINUX_GUEST_RAM_SIZE; /* 1.4GB */
|
||||
table[n].type = E820_TYPE_RAM;
|
||||
n++;
|
||||
|
||||
@@ -349,6 +364,86 @@ void *linux_find_rootfs_module(u64 *size_out) {
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== extra rootfs module finder =====
|
||||
*
|
||||
* VSCode Phase 4:在 Limine boot module 中查找持久 rw 卷镜像
|
||||
* (路径含 "extra",如 linux-extra-rootfs.img)。
|
||||
* 通过 virtio-blk slot6(可写)暴露给 Linux guest 作为 /dev/vdc。 */
|
||||
void *linux_find_extra_rootfs_module(u64 *size_out) {
|
||||
if (!g_module_request.response) return (void *)0;
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
/* 匹配 "extra"(如 linux-extra-rootfs.img) */
|
||||
if ((s[0] == 'e' || s[0] == 'E') &&
|
||||
(s[1] == 'x' || s[1] == 'X') &&
|
||||
(s[2] == 't' || s[2] == 'T') &&
|
||||
(s[3] == 'r' || s[3] == 'R') &&
|
||||
(s[4] == 'a' || s[4] == 'A')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[LINUX] found extra-rootfs module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[LINUX] extra-rootfs size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_warn("[LINUX] no extra-rootfs module found");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== VSCode tarball module finder =====
|
||||
*
|
||||
* VSCode Phase 5:在 Limine boot module 中查找 VSCode tarball
|
||||
* (路径含 "vscode",如 linux-vscode.tar.gz)。
|
||||
* 由 lxc_vscode_install() 流式推入 guest 并解压到 /opt/vscode。
|
||||
* 模块缺失属正常情况(未投放 tarball),返回 NULL 由调用方跳过。 */
|
||||
void *linux_find_vscode_module(u64 *size_out) {
|
||||
if (!g_module_request.response) return (void *)0;
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
/* 匹配 "vscode"(如 linux-vscode.tar.gz) */
|
||||
if ((s[0] == 'v' || s[0] == 'V') &&
|
||||
(s[1] == 's' || s[1] == 'S') &&
|
||||
(s[2] == 'c' || s[2] == 'C') &&
|
||||
(s[3] == 'o' || s[3] == 'O') &&
|
||||
(s[4] == 'd' || s[4] == 'D') &&
|
||||
(s[5] == 'e' || s[5] == 'E')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[LINUX] found vscode tarball module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[LINUX] vscode tarball size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_info("[LINUX] no vscode tarball module (install skipped)");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== bzImage parser ===== */
|
||||
|
||||
int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
@@ -552,16 +647,23 @@ int linux_loader_init(void) {
|
||||
return -8;
|
||||
}
|
||||
|
||||
/* General RAM: 64MB for Linux, mapped at GPA 0x05000000 (after IPC region) */
|
||||
u64 ram_size = 64 * 1024 * 1024;
|
||||
/* General RAM: VSCode integration Phase 1 expansion.
|
||||
* 1.4GB (LINUX_GUEST_RAM_SIZE) accommodates VSCode + Electron + GUI stack
|
||||
* (Xorg/modesetting + GTK + mesa softpipe). Falls back to 1GB / 512MB if
|
||||
* the host cannot allocate the full amount; 512MB is the floor for the
|
||||
* Xfbdev + xterm MVP (Phase 3). Original allocation was 64MB. */
|
||||
u64 ram_size = LINUX_GUEST_RAM_SIZE; /* 1.4GB */
|
||||
dkm_dma_buffer ram_buf;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc guest RAM (64MB)");
|
||||
/* Try smaller: 32MB */
|
||||
ram_size = 32 * 1024 * 1024;
|
||||
log_warn("[LINUX] failed to alloc guest RAM (1.4GB), trying 1GB");
|
||||
ram_size = 1ULL * 1024 * 1024 * 1024; /* 1GB */
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc guest RAM (32MB)");
|
||||
return -9;
|
||||
log_warn("[LINUX] failed to alloc guest RAM (1GB), trying 512MB");
|
||||
ram_size = 512 * 1024 * 1024; /* 512MB floor for Phase 3 MVP */
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc guest RAM (512MB minimum)");
|
||||
return -9;
|
||||
}
|
||||
}
|
||||
}
|
||||
log_hex64("[LINUX] guest RAM HPA=", ram_buf.phys);
|
||||
@@ -680,13 +782,14 @@ int linux_loader_init(void) {
|
||||
return -15;
|
||||
}
|
||||
|
||||
/* General RAM: map the allocated RAM at GPA 0x05000000 (after IPC region).
|
||||
* GPA layout (matches e820):
|
||||
/* General RAM: map the allocated RAM at GPA 0x05000000 (after surface pool).
|
||||
* GPA layout (matches e820, VSCode integration Phase 1):
|
||||
* 0x00400000-0x009FFFFF: fixed regions (boot_params, GDT, pgt, stack, cmdline)
|
||||
* 0x01000000-0x02FFFFFF: kernel
|
||||
* 0x03000000-0x03FFFFFF: initrd (if any)
|
||||
* 0x04000000-0x040FFFFF: IPC shared memory (mapped by ipc_shm_init)
|
||||
* 0x05000000-0x08FFFFFF: general RAM (64MB)
|
||||
* 0x04100000-0x04FFFFFF: Graphics surface pool (mapped below)
|
||||
* 0x05000000-0x5FFFFFFF: general RAM (~1.4GB, expanded Phase 1)
|
||||
* Linux sees this as usable RAM in e820. */
|
||||
u64 ram_gpa = LINUX_GUEST_RAM_GPA;
|
||||
if (ept_map_range(ram_gpa, ram_buf.phys, ram_size, EPT_RWX) != 0) {
|
||||
@@ -695,6 +798,41 @@ int linux_loader_init(void) {
|
||||
}
|
||||
log_hex64("[LINUX] guest RAM mapped GPA=", ram_gpa);
|
||||
|
||||
/* VSCode integration Phase 1: allocate graphics surface pool and EPT-map
|
||||
* it into the Linux guest at LINUX_GUEST_SURFACE_GPA. Linux user-space
|
||||
* obtains a writable pointer to this region via UTSM_HCALL_SURFACE_INFO +
|
||||
* /dev/utsm mmap; UTSM reads it to blit guest graphics to the Deshab
|
||||
* framebuffer (Phase 3 virtio-gpu shadow scanout). Non-fatal on failure:
|
||||
* Linux guest still runs, VSCode integration phases 2+ will degrade. */
|
||||
{
|
||||
dkm_dma_buffer surface_buf;
|
||||
u64 surface_size = LINUX_GUEST_SURFACE_SIZE; /* 16MB */
|
||||
if (dma_alloc_pages(surface_size / 4096, 2 * 1024 * 1024, 0, &surface_buf) != 0) {
|
||||
log_warn("[LINUX] failed to alloc surface pool (16MB) — VSCode integration degraded");
|
||||
g_guest.surface_hpa = 0;
|
||||
g_guest.surface_gpa = 0;
|
||||
g_guest.surface_size = 0;
|
||||
} else if (ept_map_range(LINUX_GUEST_SURFACE_GPA, surface_buf.phys,
|
||||
surface_size, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_warn("[LINUX] EPT map surface pool failed — VSCode integration degraded");
|
||||
g_guest.surface_hpa = 0;
|
||||
g_guest.surface_gpa = 0;
|
||||
g_guest.surface_size = 0;
|
||||
} else {
|
||||
/* Zero the surface pool so guest reads deterministic data before
|
||||
* the X server writes a real scanout. */
|
||||
u8 *surface_virt = (u8 *)surface_buf.virt;
|
||||
for (u64 i = 0; i < surface_size; i++) surface_virt[i] = 0;
|
||||
g_guest.surface_hpa = surface_buf.phys;
|
||||
g_guest.surface_gpa = LINUX_GUEST_SURFACE_GPA;
|
||||
g_guest.surface_size = surface_size;
|
||||
g_surface_vaddr = surface_buf.virt; /* VSCode Phase 3: for virtio-gpu + desktop blit */
|
||||
log_hex64("[LINUX] surface pool HPA=", surface_buf.phys);
|
||||
log_hex64("[LINUX] surface pool GPA=", g_guest.surface_gpa);
|
||||
log_hex64("[LINUX] surface pool size=", surface_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Identity-map the first 1MB (BIOS area, needed by some Linux code paths) */
|
||||
if (ept_identity_map(0, 0x100000, EPT_RWX) != 0) {
|
||||
log_warn("[LINUX] EPT identity map first 1MB failed (non-critical)");
|
||||
@@ -715,3 +853,23 @@ int linux_loader_init(void) {
|
||||
const struct linux_guest_info *linux_get_guest_info(void) {
|
||||
return g_guest.loaded ? &g_guest : (struct linux_guest_info *)0;
|
||||
}
|
||||
|
||||
/* VSCode integration Phase 1: query graphics surface pool.
|
||||
* Returns 0 on success (surface pool allocated and mapped), -1 if not
|
||||
* initialized. Caller may pass NULL for any output it doesn't need. */
|
||||
int linux_get_surface_info(u64 *hpa_out, u64 *gpa_out, u64 *size_out) {
|
||||
if (!g_guest.loaded || g_guest.surface_hpa == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (hpa_out) *hpa_out = g_guest.surface_hpa;
|
||||
if (gpa_out) *gpa_out = g_guest.surface_gpa;
|
||||
if (size_out) *size_out = g_guest.surface_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* VSCode Phase 3: return host virtual address of the surface pool.
|
||||
* The virtio-gpu 2D backend composites the scanout here; the desktop reads it
|
||||
* for blitting to the framebuffer. Returns NULL if the pool is not allocated. */
|
||||
void *linux_get_surface_vaddr(void) {
|
||||
return g_surface_vaddr;
|
||||
}
|
||||
|
||||
+199
-2
@@ -389,8 +389,10 @@ static void rootfs_blk_queue_notify(u32 queue_idx) {
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_BLOCK, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
/* 必须按 gpa_base 定位:slot0 SATA blk 与本后端 device_id 同为 2,
|
||||
* virtio_queue_get_ptrs(VIRTIO_ID_BLOCK) 只会命中第一个(slot0)。 */
|
||||
if (virtio_queue_get_ptrs_by_gpa(VIRTIO_MMIO_ROOTFS_GPA, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -448,3 +450,198 @@ void virtio_rootfs_blk_init(void) {
|
||||
|
||||
virtio_mmio_register(&g_rootfs_blk_backend);
|
||||
}
|
||||
|
||||
/* ===== Extra rootfs virtio-blk 后端(VSCode Phase 4,可写持久卷) =====
|
||||
*
|
||||
* 第三个 virtio-blk 设备,后端为内存中的 linux-extra-rootfs.img
|
||||
* (Limine boot module,路径含 "extra")。guest 看到 /dev/vdc,
|
||||
* 用作 overlayfs 的 rw 上层或 VSCode 数据卷。
|
||||
*
|
||||
* 与 rootfs(vdb 只读)不同,本后端支持 VIRTIO_BLK_T_OUT 真正写入内存
|
||||
* (易失:重启丢失;Phase 7 再做写回 FAT32 持久化)。
|
||||
*
|
||||
* 无 extra-rootfs module 时安全不注册(guest cmdline 无 slot6 参数)。 */
|
||||
|
||||
static u8 *g_xrootfs_data;
|
||||
static u64 g_xrootfs_size;
|
||||
static u64 g_xrootfs_capacity;
|
||||
static u16 g_xrootfs_last_avail;
|
||||
|
||||
static u32 xrootfs_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_xrootfs_capacity;
|
||||
cfg.size_max = 128;
|
||||
cfg.seg_max = 32;
|
||||
cfg.blk_size = 512;
|
||||
if (offset < sizeof(cfg)) {
|
||||
u32 val = 0;
|
||||
rf_memcpy(&val, c + offset, (offset + 4 <= sizeof(cfg)) ? 4 : (sizeof(cfg) - offset));
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 xrootfs_blk_handle_chain(struct virtq_desc *desc, u16 head, u32 qnum) {
|
||||
(void)qnum;
|
||||
struct virtio_blk_outhdr hdr;
|
||||
u8 *hptr = (u8 *)virtio_gpa_to_host(desc[head].addr);
|
||||
if (!hptr) return 0;
|
||||
rf_memcpy(&hdr, hptr, sizeof(hdr));
|
||||
|
||||
u32 type = hdr.type;
|
||||
u64 sector = hdr.sector;
|
||||
|
||||
u16 data_idx = 0xFFFF;
|
||||
u16 status_idx = 0xFFFF;
|
||||
if (desc[head].flags & VIRTQ_DESC_F_NEXT) {
|
||||
data_idx = desc[head].next;
|
||||
u16 cur = data_idx;
|
||||
int hops = 0;
|
||||
while ((desc[cur].flags & VIRTQ_DESC_F_NEXT) && hops < 16) {
|
||||
cur = desc[cur].next;
|
||||
hops++;
|
||||
}
|
||||
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;
|
||||
|
||||
switch (type) {
|
||||
case VIRTIO_BLK_T_IN: { /* 读:镜像内存 → guest */
|
||||
if (data_idx == 0xFFFF || !g_xrootfs_data) {
|
||||
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; }
|
||||
u64 offset = sector * 512ULL;
|
||||
u64 avail = (offset < g_xrootfs_size) ? (g_xrootfs_size - offset) : 0;
|
||||
u32 copied = 0;
|
||||
if (avail > 0) {
|
||||
u32 n = (dlen < avail) ? dlen : (u32)avail;
|
||||
rf_memcpy(dbuf, g_xrootfs_data + offset, n);
|
||||
copied = n;
|
||||
}
|
||||
/* 越界部分填零 */
|
||||
while (copied < dlen) dbuf[copied++] = 0;
|
||||
bytes_done = dlen;
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_OUT: { /* 写:guest → 镜像内存(可写卷) */
|
||||
if (data_idx == 0xFFFF || !g_xrootfs_data) {
|
||||
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; }
|
||||
u64 offset = sector * 512ULL;
|
||||
if (offset + dlen > g_xrootfs_size) {
|
||||
/* 越界写:只写卷内部分,其余丢弃 */
|
||||
u64 avail = (offset < g_xrootfs_size) ? (g_xrootfs_size - offset) : 0;
|
||||
if (avail > 0) rf_memcpy(g_xrootfs_data + offset, dbuf, avail);
|
||||
} else {
|
||||
rf_memcpy(g_xrootfs_data + offset, dbuf, dlen);
|
||||
}
|
||||
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-EXTRA-ROOTFS";
|
||||
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;
|
||||
}
|
||||
|
||||
static void xrootfs_blk_queue_notify(u32 queue_idx) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
|
||||
/* 按 gpa_base 定位(三个 blk 后端 device_id 同为 2) */
|
||||
if (virtio_queue_get_ptrs_by_gpa(VIRTIO_MMIO_EXTRA_ROOTFS_GPA, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
u16 cur = g_xrootfs_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u32 len = xrootfs_blk_handle_chain(desc, head, qnum);
|
||||
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_xrootfs_last_avail = cur;
|
||||
}
|
||||
|
||||
static void xrootfs_blk_reset(void) {
|
||||
g_xrootfs_last_avail = 0;
|
||||
}
|
||||
|
||||
static struct virtio_backend g_xrootfs_blk_backend = {
|
||||
.device_id = VIRTIO_ID_BLOCK,
|
||||
.gpa_base = VIRTIO_MMIO_EXTRA_ROOTFS_GPA,
|
||||
.irq = 11, /* guest ISA IRQ11(cmdline :11,vector 0x3B) */
|
||||
.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 = xrootfs_blk_read_config,
|
||||
.queue_notify = xrootfs_blk_queue_notify,
|
||||
.reset = xrootfs_blk_reset,
|
||||
};
|
||||
|
||||
void virtio_extra_rootfs_blk_init(void) {
|
||||
u64 size = 0;
|
||||
void *img = linux_find_extra_rootfs_module(&size);
|
||||
if (!img || size == 0) {
|
||||
log_warn("[VBLK] no extra-rootfs module, slot6 backend not registered");
|
||||
return;
|
||||
}
|
||||
|
||||
g_xrootfs_data = (u8 *)img;
|
||||
g_xrootfs_size = size;
|
||||
g_xrootfs_capacity = size / 512;
|
||||
if (size % 512) g_xrootfs_capacity++;
|
||||
|
||||
log_info("[VBLK] extra-rootfs-blk backend bound (rw)");
|
||||
log_hex64("[VBLK] extra-rootfs size=", size);
|
||||
|
||||
virtio_mmio_register(&g_xrootfs_blk_backend);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,918 @@
|
||||
/* virtio_gpu.c — UTSM virtio-gpu 2D backend (VSCode integration Phase 3).
|
||||
*
|
||||
* Implements a minimal virtio-gpu 2D device (device_id=16) so the Linux guest
|
||||
* gets /dev/dri/card0 + /dev/fb0 (via DRM fbdev emulation). The X server
|
||||
* (Xfbdev MVP / Xorg Phase 4) drives the scanout; the composited frame lands
|
||||
* in the shared graphics surface pool, which the Deshab desktop reads to blit
|
||||
* guest graphics onto the IDE host area.
|
||||
*
|
||||
* Device layout: slot 5 @ 0xF4005000 IRQ10, two queues (ctrlq + cursorq).
|
||||
*
|
||||
* Supported 2D command set (Virtio 1.1 §5.4.4):
|
||||
* GET_NUM_SCANOUTS, GET_DISPLAY_INFO, RESOURCE_CREATE_2D, RESOURCE_DESTROY,
|
||||
* RESOURCE_ATTACH_BACKING, RESOURCE_DETACH_BACKING, SET_SCANOUT,
|
||||
* TRANSFER_TO_HOST_2D, RESOURCE_FLUSH.
|
||||
*
|
||||
* Cursor commands (UPDATE_CURSOR / MOVE_CURSOR) are acknowledged but not
|
||||
* rendered — the Deshab desktop draws its own PS/2 cursor over the blit.
|
||||
*
|
||||
* Data flow:
|
||||
* 1. Guest DRM driver creates a 2D resource, attaches guest RAM backing,
|
||||
* binds it to scanout 0 (SET_SCANOUT), and TRANSFER_TO_HOST_2D copies a
|
||||
* rect from guest backing into the surface pool (scanout shadow).
|
||||
* 2. RESOURCE_FLUSH marks the scanout dirty.
|
||||
* 3. The desktop polls virtio_gpu_get_scanout_info() and blits the dirty
|
||||
* scanout to the framebuffer IDE host area, clearing dirty.
|
||||
*
|
||||
* MVP simplifications:
|
||||
* - Single scanout (num_scanouts=1), fixed display 1024×768 XRGB8888.
|
||||
* - Resource backing assumed contiguous (first mem_entry's GPA + total len).
|
||||
* Guest framebuffer allocations satisfy this.
|
||||
* - No virgl 3D, no capsets, no cross-endian byte swap (raw 32bpp copy).
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* ===== virtio-gpu protocol structs (must match <uapi/linux/virtio_gpu.h>) ===== */
|
||||
|
||||
struct virtio_gpu_config {
|
||||
u32 events_read;
|
||||
u32 events_clear;
|
||||
u32 num_scanouts;
|
||||
u32 num_capsets;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_ctrl_hdr {
|
||||
u32 type;
|
||||
u32 flags;
|
||||
u64 fence_id;
|
||||
u32 ctx_id;
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_rect {
|
||||
u32 x;
|
||||
u32 y;
|
||||
u32 width;
|
||||
u32 height;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_display_one {
|
||||
struct virtio_gpu_rect r;
|
||||
u32 enabled;
|
||||
u32 flags;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resp_display_info {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_display_one pmodes[16];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resp_num_scanouts {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
u32 num_scanouts;
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_create_2d {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
u32 resource_id;
|
||||
u32 format;
|
||||
u32 width;
|
||||
u32 height;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_mem_entry {
|
||||
u64 addr;
|
||||
u32 length;
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_attach_backing {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
u32 resource_id;
|
||||
u32 nr_entries;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_set_scanout {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
u32 scanout_id;
|
||||
u32 resource_id;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_transfer_to_host_2d {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
u64 offset;
|
||||
u32 resource_id;
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_flush {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
u32 resource_id;
|
||||
u32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* P7.6: cursor queue commands (UPDATE_CURSOR / MOVE_CURSOR share struct) */
|
||||
struct virtio_gpu_update_cursor {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect pos; /* x, y on scanout */
|
||||
u32 resource_id; /* 0 = hide cursor */
|
||||
u32 hot_x;
|
||||
u32 hot_y;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define VGPU_CURSOR_MAX 64 /* max cursor image dimension (pixels) */
|
||||
|
||||
/* command types */
|
||||
#define VIRTIO_GPU_CMD_GET_NUM_SCANOUTS 0x0102
|
||||
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0103
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0104
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_DESTROY 0x0105
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING 0x0107
|
||||
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0108
|
||||
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0109
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x010a
|
||||
#define VIRTIO_GPU_CMD_UPDATE_CURSOR 0x0110
|
||||
#define VIRTIO_GPU_CMD_MOVE_CURSOR 0x0111
|
||||
|
||||
/* response types */
|
||||
#define VIRTIO_GPU_RESP_OK_NODATA 0x1100
|
||||
#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101
|
||||
#define VIRTIO_GPU_RESP_ERR_UNSPEC 0x1200
|
||||
#define VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY 0x1201
|
||||
#define VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID 0x1202
|
||||
#define VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID 0x1203
|
||||
#define VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER 0x1205
|
||||
|
||||
/* formats we accept (32bpp). Copied raw — no endian swap. */
|
||||
#define VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM 1
|
||||
#define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2
|
||||
#define VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM 3
|
||||
#define VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM 4
|
||||
|
||||
/* fence flag (we just ack fences, no real fencing) */
|
||||
#define VIRTIO_GPU_FLAG_FENCE (1 << 0)
|
||||
|
||||
/* Fixed scanout geometry advertised to the guest. 1024×768 fits in the 16MB
|
||||
* surface pool (4 MB) with headroom, and is a standard Xfbdev mode. */
|
||||
#define VGPU_SCANOUT_W 1024
|
||||
#define VGPU_SCANOUT_H 768
|
||||
#define VGPU_BPP 4 /* XRGB8888 = 4 bytes/pixel */
|
||||
#define VGPU_SCANOUT_STRIDE (VGPU_SCANOUT_W * VGPU_BPP) /* 4096 bytes/row */
|
||||
#define VGPU_SCANOUT_SIZE (VGPU_SCANOUT_STRIDE * VGPU_SCANOUT_H) /* 3 MB */
|
||||
|
||||
#define VGPU_MAX_RESOURCES 16
|
||||
#define VGPU_MAX_BACKING_ENTRIES 8
|
||||
|
||||
struct vgpu_resource {
|
||||
int in_use;
|
||||
u32 resource_id;
|
||||
u32 format;
|
||||
u32 width;
|
||||
u32 height;
|
||||
int has_backing;
|
||||
/* Backing memory (contiguous assumption: first entry GPA + total len).
|
||||
* Multi-entry attach is summed; guest framebuffer allocations are
|
||||
* physically contiguous so this holds in practice. */
|
||||
u64 backing_gpa;
|
||||
u64 backing_len;
|
||||
};
|
||||
|
||||
/* Per-device state. Single instance (one virtio-gpu device). */
|
||||
static struct {
|
||||
u64 gpa_base;
|
||||
u32 irq;
|
||||
|
||||
/* config space image (16 bytes) */
|
||||
struct virtio_gpu_config cfg;
|
||||
|
||||
/* resource table */
|
||||
struct vgpu_resource res[VGPU_MAX_RESOURCES];
|
||||
|
||||
/* scanout 0 state */
|
||||
int scanout_enabled;
|
||||
u32 scanout_res_id; /* 0 = none */
|
||||
u32 scanout_x, scanout_y;
|
||||
u32 scanout_w, scanout_h;
|
||||
|
||||
/* surface pool (host vaddr) — composited frames land here */
|
||||
void *surface_vaddr;
|
||||
u64 surface_size;
|
||||
|
||||
/* dirty: set on FLUSH of the scanout resource, cleared by get_scanout_info */
|
||||
int dirty;
|
||||
/* Phase 7: 累积 dirty rect(scanout 坐标系并集),desktop 局部 blit 用。
|
||||
* dirty_rect_valid=0 时无矩形(与 dirty=0 等价;首帧建议全量拷贝)。 */
|
||||
int dirty_rect_valid;
|
||||
u32 dirty_rx, dirty_ry, dirty_rw, dirty_rh;
|
||||
|
||||
/* P7.6: hardware cursor state (cursorq UPDATE_CURSOR / MOVE_CURSOR)。
|
||||
* cursor_visible=0 直到 guest 发 UPDATE_CURSOR 且 resource_id!=0。
|
||||
* cursor_bitmap 存 ARGB 像素(row stride = VGPU_CURSOR_MAX),供 desktop
|
||||
* alpha-blend 叠加到 IDE host 区域。 */
|
||||
int cursor_visible;
|
||||
u32 cursor_x, cursor_y; /* position on scanout */
|
||||
u32 cursor_hot_x, cursor_hot_y;
|
||||
u32 cursor_w, cursor_h; /* actual cursor image dimensions */
|
||||
u32 cursor_bitmap[VGPU_CURSOR_MAX * VGPU_CURSOR_MAX];
|
||||
|
||||
/* per-queue avail cursor */
|
||||
u16 last_avail_ctrl;
|
||||
u16 last_avail_cursor;
|
||||
|
||||
struct virtio_backend backend;
|
||||
} g_gpu;
|
||||
|
||||
/* ===== resource table helpers ===== */
|
||||
|
||||
static struct vgpu_resource *res_find(u32 id) {
|
||||
if (id == 0) return 0;
|
||||
for (int i = 0; i < VGPU_MAX_RESOURCES; i++) {
|
||||
if (g_gpu.res[i].in_use && g_gpu.res[i].resource_id == id)
|
||||
return &g_gpu.res[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct vgpu_resource *res_alloc(void) {
|
||||
for (int i = 0; i < VGPU_MAX_RESOURCES; i++) {
|
||||
if (!g_gpu.res[i].in_use) {
|
||||
g_gpu.res[i].in_use = 1;
|
||||
return &g_gpu.res[i];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void res_release(struct vgpu_resource *r) {
|
||||
if (!r) return;
|
||||
r->in_use = 0;
|
||||
r->resource_id = 0;
|
||||
r->has_backing = 0;
|
||||
r->backing_gpa = 0;
|
||||
r->backing_len = 0;
|
||||
}
|
||||
|
||||
/* ===== surface pool access =====
|
||||
* The scanout shadow lives at the start of the 16MB graphics surface pool
|
||||
* (allocated in linux_loader.c). virtio-gpu composites there; the desktop
|
||||
* reads the same region. Falls back to disabled scanout if the pool is absent. */
|
||||
static int surface_ready(void) {
|
||||
return g_gpu.surface_vaddr != 0 && g_gpu.surface_size >= VGPU_SCANOUT_SIZE;
|
||||
}
|
||||
|
||||
/* ===== descriptor chain walker =====
|
||||
* A virtio-gpu ctrl chain is typically: [cmd desc (read, F_NEXT)] →
|
||||
* [resp desc (write)]. We collect the first readable buffer as the command
|
||||
* and the first writable buffer as the response. Returns 0 on success and
|
||||
* fills cmd/resp host vaddrs + lens; resp may be NULL if driver omitted it. */
|
||||
struct chain_bufs {
|
||||
void *cmd; u32 cmd_len;
|
||||
void *resp; u32 resp_len;
|
||||
u16 head; /* chain head index (for used ring) */
|
||||
};
|
||||
|
||||
static int walk_chain(struct virtq_desc *desc, u32 qnum, u16 head,
|
||||
struct chain_bufs *out) {
|
||||
u16 cur = head;
|
||||
int steps = 0;
|
||||
out->cmd = 0; out->cmd_len = 0;
|
||||
out->resp = 0; out->resp_len = 0;
|
||||
out->head = head;
|
||||
|
||||
while (steps < 64) { /* bound chain length */
|
||||
if (cur >= qnum) return -1;
|
||||
struct virtq_desc *d = &desc[cur];
|
||||
void *host = virtio_gpa_to_host(d->addr);
|
||||
if (!host) return -1; /* unmapped GPA */
|
||||
|
||||
if (d->flags & VIRTQ_DESC_F_WRITE) {
|
||||
if (!out->resp) { out->resp = host; out->resp_len = d->len; }
|
||||
} else {
|
||||
if (!out->cmd) { out->cmd = host; out->cmd_len = d->len; }
|
||||
}
|
||||
if (!(d->flags & VIRTQ_DESC_F_NEXT)) break;
|
||||
cur = d->next;
|
||||
steps++;
|
||||
}
|
||||
return (out->cmd != 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Write a simple NODATA response (just the ctrl_hdr). */
|
||||
static void write_resp_nodata(void *resp, u32 resp_cap, u32 cmd_type, u32 resp_type) {
|
||||
if (!resp || resp_cap < (u32)sizeof(struct virtio_gpu_ctrl_hdr)) return;
|
||||
struct virtio_gpu_ctrl_hdr *h = (struct virtio_gpu_ctrl_hdr *)resp;
|
||||
h->type = resp_type;
|
||||
h->flags = 0;
|
||||
h->fence_id = 0;
|
||||
h->ctx_id = 0;
|
||||
h->padding = 0;
|
||||
(void)cmd_type;
|
||||
}
|
||||
|
||||
/* ===== 2D command handlers =====
|
||||
* Each returns the response type to write. cmd points at the full command
|
||||
* (ctrl_hdr + command-specific body). */
|
||||
|
||||
static u32 handle_get_display_info(void *resp, u32 resp_cap) {
|
||||
if (!resp || resp_cap < (u32)sizeof(struct virtio_gpu_resp_display_info))
|
||||
return VIRTIO_GPU_RESP_ERR_UNSPEC;
|
||||
struct virtio_gpu_resp_display_info *r =
|
||||
(struct virtio_gpu_resp_display_info *)resp;
|
||||
/* header */
|
||||
r->hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO;
|
||||
r->hdr.flags = 0; r->hdr.fence_id = 0; r->hdr.ctx_id = 0; r->hdr.padding = 0;
|
||||
/* one enabled scanout at (0,0,1024,768) */
|
||||
for (int i = 0; i < 16; i++) {
|
||||
r->pmodes[i].r.x = 0; r->pmodes[i].r.y = 0;
|
||||
r->pmodes[i].r.width = 0; r->pmodes[i].r.height = 0;
|
||||
r->pmodes[i].enabled = 0;
|
||||
r->pmodes[i].flags = 0;
|
||||
}
|
||||
r->pmodes[0].r.width = VGPU_SCANOUT_W;
|
||||
r->pmodes[0].r.height = VGPU_SCANOUT_H;
|
||||
r->pmodes[0].enabled = 1;
|
||||
return VIRTIO_GPU_RESP_OK_DISPLAY_INFO;
|
||||
}
|
||||
|
||||
static u32 handle_resource_create_2d(struct virtio_gpu_resource_create_2d *cmd) {
|
||||
if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
u32 id = cmd->resource_id;
|
||||
if (id == 0) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
if (res_find(id)) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
/* accept 32bpp formats; reject exotic sizes */
|
||||
if (cmd->format < VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM ||
|
||||
cmd->format > VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM)
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
if (cmd->width == 0 || cmd->height == 0 ||
|
||||
cmd->width > 4096 || cmd->height > 4096)
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
|
||||
struct vgpu_resource *r = res_alloc();
|
||||
if (!r) return VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY;
|
||||
r->resource_id = id;
|
||||
r->format = cmd->format;
|
||||
r->width = cmd->width;
|
||||
r->height = cmd->height;
|
||||
r->has_backing = 0;
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
static u32 handle_resource_destroy(u32 resource_id) {
|
||||
struct vgpu_resource *r = res_find(resource_id);
|
||||
if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
/* if it was the scanout resource, detach */
|
||||
if (g_gpu.scanout_enabled && g_gpu.scanout_res_id == resource_id) {
|
||||
g_gpu.scanout_enabled = 0;
|
||||
g_gpu.scanout_res_id = 0;
|
||||
}
|
||||
res_release(r);
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
static u32 handle_attach_backing(struct virtio_gpu_resource_attach_backing *cmd,
|
||||
u32 cmd_len) {
|
||||
if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
u32 id = cmd->resource_id;
|
||||
struct vgpu_resource *r = res_find(id);
|
||||
if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
u32 n = cmd->nr_entries;
|
||||
if (n == 0 || n > VGPU_MAX_BACKING_ENTRIES)
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
|
||||
/* entries immediately follow the attach_backing command in the same buffer */
|
||||
u32 hdr_off = (u32)sizeof(struct virtio_gpu_resource_attach_backing);
|
||||
if (cmd_len < hdr_off + n * (u32)sizeof(struct virtio_gpu_mem_entry))
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
|
||||
struct virtio_gpu_mem_entry *entries =
|
||||
(struct virtio_gpu_mem_entry *)((u8 *)cmd + hdr_off);
|
||||
|
||||
/* MVP: record first entry GPA + summed length (contiguous assumption). */
|
||||
u64 base = entries[0].addr;
|
||||
u64 total = 0;
|
||||
for (u32 i = 0; i < n; i++) total += entries[i].length;
|
||||
|
||||
r->has_backing = 1;
|
||||
r->backing_gpa = base;
|
||||
r->backing_len = total;
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
static u32 handle_detach_backing(u32 resource_id) {
|
||||
struct vgpu_resource *r = res_find(resource_id);
|
||||
if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
r->has_backing = 0;
|
||||
r->backing_gpa = 0;
|
||||
r->backing_len = 0;
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
static u32 handle_set_scanout(struct virtio_gpu_set_scanout *cmd) {
|
||||
if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
if (cmd->scanout_id != 0) return VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
|
||||
|
||||
if (cmd->resource_id == 0) {
|
||||
/* disabling scanout */
|
||||
g_gpu.scanout_enabled = 0;
|
||||
g_gpu.scanout_res_id = 0;
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
struct vgpu_resource *r = res_find(cmd->resource_id);
|
||||
if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
if (cmd->r.width == 0 || cmd->r.height == 0)
|
||||
return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
|
||||
g_gpu.scanout_enabled = 1;
|
||||
g_gpu.scanout_res_id = cmd->resource_id;
|
||||
g_gpu.scanout_x = cmd->r.x;
|
||||
g_gpu.scanout_y = cmd->r.y;
|
||||
g_gpu.scanout_w = cmd->r.width;
|
||||
g_gpu.scanout_h = cmd->r.height;
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
/* Copy a rect of a resource from guest backing memory into the surface pool
|
||||
* scanout region. Called for both TRANSFER_TO_HOST_2D and (implicitly via flush)
|
||||
* — we do the actual copy on TRANSFER, flush just marks dirty. */
|
||||
static void composite_rect(struct vgpu_resource *r, struct virtio_gpu_rect *rect,
|
||||
u64 offset) {
|
||||
if (!surface_ready() || !r || !r->has_backing) return;
|
||||
/* only composite the resource currently bound to the scanout */
|
||||
if (!g_gpu.scanout_enabled || g_gpu.scanout_res_id != r->resource_id) return;
|
||||
|
||||
void *backing_host = virtio_gpa_to_host(r->backing_gpa);
|
||||
if (!backing_host) return;
|
||||
|
||||
u32 src_stride = r->width * VGPU_BPP;
|
||||
u32 dst_stride = VGPU_SCANOUT_STRIDE; /* scanout is fixed 1024×768 */
|
||||
|
||||
/* clamp rect to resource bounds */
|
||||
u32 rx = rect->x, ry = rect->y, rw = rect->width, rh = rect->height;
|
||||
if (rx >= r->width || ry >= r->height) return;
|
||||
if (rx + rw > r->width) rw = r->width - rx;
|
||||
if (ry + rh > r->height) rh = r->height - ry;
|
||||
/* clamp to scanout bounds */
|
||||
u32 dx = g_gpu.scanout_x + rx;
|
||||
u32 dy = g_gpu.scanout_y + ry;
|
||||
if (dx >= VGPU_SCANOUT_W || dy >= VGPU_SCANOUT_H) return;
|
||||
if (dx + rw > VGPU_SCANOUT_W) rw = VGPU_SCANOUT_W - dx;
|
||||
if (dy + rh > VGPU_SCANOUT_H) rh = VGPU_SCANOUT_H - dy;
|
||||
if (rw == 0 || rh == 0) return;
|
||||
|
||||
u8 *dst = (u8 *)g_gpu.surface_vaddr;
|
||||
u8 *src = (u8 *)backing_host + offset;
|
||||
u32 copy_bytes = rw * VGPU_BPP;
|
||||
|
||||
/* guard against backing_len overflow */
|
||||
u64 src_base = offset + (u64)ry * src_stride + (u64)rx * VGPU_BPP;
|
||||
u64 src_need = src_base + (u64)rh * src_stride;
|
||||
if (src_need > r->backing_len) {
|
||||
/* partial: clamp rows to what the backing covers */
|
||||
u64 avail_rows = (r->backing_len > offset) ? (r->backing_len - offset) / src_stride : 0;
|
||||
if (ry >= avail_rows) return;
|
||||
if (ry + rh > avail_rows) rh = (u32)(avail_rows - ry);
|
||||
if (rh == 0) return;
|
||||
}
|
||||
|
||||
for (u32 row = 0; row < rh; row++) {
|
||||
u8 *s = src + (u64)(ry + row) * src_stride + (u64)rx * VGPU_BPP;
|
||||
u8 *d = dst + (u64)(dy + row) * dst_stride + (u64)dx * VGPU_BPP;
|
||||
/* word-copy 4-byte pixels */
|
||||
u32 n = copy_bytes / 4;
|
||||
u32 *sp = (u32 *)s;
|
||||
u32 *dp = (u32 *)d;
|
||||
for (u32 i = 0; i < n; i++) dp[i] = sp[i];
|
||||
/* trailing bytes (shouldn't happen for 32bpp) */
|
||||
for (u32 i = n * 4; i < copy_bytes; i++) d[i] = s[i];
|
||||
}
|
||||
}
|
||||
|
||||
static u32 handle_transfer_to_host_2d(struct virtio_gpu_transfer_to_host_2d *cmd) {
|
||||
if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
struct vgpu_resource *r = res_find(cmd->resource_id);
|
||||
if (!r) return VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
|
||||
composite_rect(r, &cmd->r, cmd->offset);
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
static u32 handle_resource_flush(struct virtio_gpu_resource_flush *cmd) {
|
||||
if (!cmd) return VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
/* mark scanout dirty so the desktop re-blits. We only flushed the scanout
|
||||
* resource (composite_rect already copied it on TRANSFER). */
|
||||
if (g_gpu.scanout_enabled && g_gpu.scanout_res_id == cmd->resource_id) {
|
||||
g_gpu.dirty = 1;
|
||||
/* Phase 7: 把 flush rect(resource 坐标系)并入累积 dirty rect
|
||||
* (scanout 坐标系并集),desktop 据此只 blit 变化区域。
|
||||
* 钳制规则与 composite_rect 一致。 */
|
||||
u32 rx = cmd->r.x, ry = cmd->r.y, rw = cmd->r.width, rh = cmd->r.height;
|
||||
u32 dx = g_gpu.scanout_x + rx;
|
||||
u32 dy = g_gpu.scanout_y + ry;
|
||||
if (dx < VGPU_SCANOUT_W && dy < VGPU_SCANOUT_H && rw > 0 && rh > 0) {
|
||||
if (dx + rw > VGPU_SCANOUT_W) rw = VGPU_SCANOUT_W - dx;
|
||||
if (dy + rh > VGPU_SCANOUT_H) rh = VGPU_SCANOUT_H - dy;
|
||||
if (!g_gpu.dirty_rect_valid) {
|
||||
g_gpu.dirty_rx = dx; g_gpu.dirty_ry = dy;
|
||||
g_gpu.dirty_rw = rw; g_gpu.dirty_rh = rh;
|
||||
g_gpu.dirty_rect_valid = 1;
|
||||
} else {
|
||||
/* 并集:expand 到同时包含旧矩形与新矩形 */
|
||||
u32 x0 = (dx < g_gpu.dirty_rx) ? dx : g_gpu.dirty_rx;
|
||||
u32 y0 = (dy < g_gpu.dirty_ry) ? dy : g_gpu.dirty_ry;
|
||||
u32 x1 = ((dx + rw) > (g_gpu.dirty_rx + g_gpu.dirty_rw))
|
||||
? (dx + rw) : (g_gpu.dirty_rx + g_gpu.dirty_rw);
|
||||
u32 y1 = ((dy + rh) > (g_gpu.dirty_ry + g_gpu.dirty_rh))
|
||||
? (dy + rh) : (g_gpu.dirty_ry + g_gpu.dirty_rh);
|
||||
g_gpu.dirty_rx = x0; g_gpu.dirty_ry = y0;
|
||||
g_gpu.dirty_rw = x1 - x0; g_gpu.dirty_rh = y1 - y0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return VIRTIO_GPU_RESP_OK_NODATA;
|
||||
}
|
||||
|
||||
/* ===== ctrlq processing ===== */
|
||||
|
||||
static void gpu_ctrlq_process(void) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs_by_gpa(g_gpu.gpa_base, 0,
|
||||
&desc, &avail, &used, &qnum) != 0)
|
||||
return;
|
||||
|
||||
u16 cur = g_gpu.last_avail_ctrl;
|
||||
u16 used_idx = used->idx;
|
||||
int budget = 32; /* bound work per notify */
|
||||
int filled = 0;
|
||||
|
||||
while (budget-- > 0 && cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
if (head >= qnum) { cur++; continue; }
|
||||
|
||||
struct chain_bufs cb;
|
||||
if (walk_chain(desc, qnum, head, &cb) != 0) {
|
||||
/* bad chain: return it empty so the driver recycles */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++; cur++; filled++;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct virtio_gpu_ctrl_hdr *hdr = (struct virtio_gpu_ctrl_hdr *)cb.cmd;
|
||||
u32 resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC;
|
||||
u32 resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr); /* default NODATA */
|
||||
|
||||
if (cb.cmd_len < (u32)sizeof(struct virtio_gpu_ctrl_hdr)) {
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC;
|
||||
} else {
|
||||
u32 t = hdr->type;
|
||||
switch (t) {
|
||||
case VIRTIO_GPU_CMD_GET_NUM_SCANOUTS:
|
||||
if (cb.resp && cb.resp_len >= (u32)sizeof(struct virtio_gpu_resp_num_scanouts)) {
|
||||
struct virtio_gpu_resp_num_scanouts *rn =
|
||||
(struct virtio_gpu_resp_num_scanouts *)cb.resp;
|
||||
rn->hdr.type = VIRTIO_GPU_RESP_OK_NODATA;
|
||||
rn->hdr.flags = 0; rn->hdr.fence_id = 0;
|
||||
rn->hdr.ctx_id = 0; rn->hdr.padding = 0;
|
||||
rn->num_scanouts = 1;
|
||||
rn->padding = 0;
|
||||
resp_type = VIRTIO_GPU_RESP_OK_NODATA;
|
||||
resp_len = (u32)sizeof(struct virtio_gpu_resp_num_scanouts);
|
||||
} else {
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_UNSPEC;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_GET_DISPLAY_INFO:
|
||||
resp_type = handle_get_display_info(cb.resp, cb.resp_len);
|
||||
if (resp_type == VIRTIO_GPU_RESP_OK_DISPLAY_INFO)
|
||||
resp_len = (u32)sizeof(struct virtio_gpu_resp_display_info);
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D:
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_resource_create_2d))
|
||||
resp_type = handle_resource_create_2d(
|
||||
(struct virtio_gpu_resource_create_2d *)cb.cmd);
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_RESOURCE_DESTROY: {
|
||||
/* DESTROY has just hdr + resource_id after */
|
||||
u32 rid = 0;
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_ctrl_hdr) + 4)
|
||||
rid = *(u32 *)((u8 *)cb.cmd + sizeof(struct virtio_gpu_ctrl_hdr));
|
||||
resp_type = handle_resource_destroy(rid);
|
||||
break;
|
||||
}
|
||||
case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_resource_attach_backing))
|
||||
resp_type = handle_attach_backing(
|
||||
(struct virtio_gpu_resource_attach_backing *)cb.cmd, cb.cmd_len);
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING: {
|
||||
u32 rid = 0;
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_ctrl_hdr) + 4)
|
||||
rid = *(u32 *)((u8 *)cb.cmd + sizeof(struct virtio_gpu_ctrl_hdr));
|
||||
resp_type = handle_detach_backing(rid);
|
||||
break;
|
||||
}
|
||||
case VIRTIO_GPU_CMD_SET_SCANOUT:
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_set_scanout))
|
||||
resp_type = handle_set_scanout(
|
||||
(struct virtio_gpu_set_scanout *)cb.cmd);
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D:
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_transfer_to_host_2d))
|
||||
resp_type = handle_transfer_to_host_2d(
|
||||
(struct virtio_gpu_transfer_to_host_2d *)cb.cmd);
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case VIRTIO_GPU_CMD_RESOURCE_FLUSH:
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_resource_flush))
|
||||
resp_type = handle_resource_flush(
|
||||
(struct virtio_gpu_resource_flush *)cb.cmd);
|
||||
else
|
||||
resp_type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
default:
|
||||
/* unknown / 3D / cursor commands on ctrlq: ack NODATA */
|
||||
resp_type = VIRTIO_GPU_RESP_OK_NODATA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* write the response (unless GET_* already wrote a richer response) */
|
||||
if (resp_type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO &&
|
||||
resp_type != VIRTIO_GPU_RESP_OK_NODATA) {
|
||||
/* error: write NODATA with error type if we haven't already */
|
||||
if (hdr->type != VIRTIO_GPU_CMD_GET_NUM_SCANOUTS)
|
||||
write_resp_nodata(cb.resp, cb.resp_len, hdr->type, resp_type);
|
||||
resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr);
|
||||
} else if (resp_type == VIRTIO_GPU_RESP_OK_NODATA &&
|
||||
hdr->type != VIRTIO_GPU_CMD_GET_NUM_SCANOUTS &&
|
||||
hdr->type != VIRTIO_GPU_CMD_GET_DISPLAY_INFO) {
|
||||
/* plain NODATA success: write header */
|
||||
write_resp_nodata(cb.resp, cb.resp_len, hdr->type, resp_type);
|
||||
resp_len = (u32)sizeof(struct virtio_gpu_ctrl_hdr);
|
||||
}
|
||||
|
||||
/* fence ack: if the command requested a fence, echo fence_id in resp */
|
||||
if ((hdr->flags & VIRTIO_GPU_FLAG_FENCE) && cb.resp &&
|
||||
cb.resp_len >= (u32)sizeof(struct virtio_gpu_ctrl_hdr)) {
|
||||
struct virtio_gpu_ctrl_hdr *rh = (struct virtio_gpu_ctrl_hdr *)cb.resp;
|
||||
rh->flags |= VIRTIO_GPU_FLAG_FENCE;
|
||||
rh->fence_id = hdr->fence_id;
|
||||
}
|
||||
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = resp_len;
|
||||
used_idx++; cur++; filled++;
|
||||
}
|
||||
|
||||
if (filled > 0) {
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_gpu.last_avail_ctrl = cur;
|
||||
virtio_mmio_raise_irq_by_gpa(g_gpu.gpa_base);
|
||||
}
|
||||
}
|
||||
|
||||
/* P7.6: cursorq - process UPDATE_CURSOR / MOVE_CURSOR.
|
||||
* UPDATE_CURSOR: if resource_id != 0, copy cursor image from resource backing
|
||||
* (up to 64x64 ARGB). If resource_id == 0, hide cursor.
|
||||
* MOVE_CURSOR: update cursor position on scanout. */
|
||||
static void gpu_cursorq_process(void) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs_by_gpa(g_gpu.gpa_base, 1,
|
||||
&desc, &avail, &used, &qnum) != 0)
|
||||
return;
|
||||
|
||||
u16 cur = g_gpu.last_avail_cursor;
|
||||
u16 used_idx = used->idx;
|
||||
int budget = 16;
|
||||
int filled = 0;
|
||||
|
||||
while (budget-- > 0 && cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
if (head >= qnum) { cur++; continue; }
|
||||
|
||||
struct chain_bufs cb;
|
||||
if (walk_chain(desc, qnum, head, &cb) == 0 && cb.cmd && cb.resp) {
|
||||
struct virtio_gpu_ctrl_hdr *hdr = (struct virtio_gpu_ctrl_hdr *)cb.cmd;
|
||||
|
||||
if (cb.cmd_len >= (u32)sizeof(struct virtio_gpu_update_cursor)) {
|
||||
struct virtio_gpu_update_cursor *cmd =
|
||||
(struct virtio_gpu_update_cursor *)cb.cmd;
|
||||
|
||||
if (hdr->type == VIRTIO_GPU_CMD_UPDATE_CURSOR) {
|
||||
u32 rid = cmd->resource_id;
|
||||
g_gpu.cursor_x = cmd->pos.x;
|
||||
g_gpu.cursor_y = cmd->pos.y;
|
||||
g_gpu.cursor_hot_x = cmd->hot_x;
|
||||
g_gpu.cursor_hot_y = cmd->hot_y;
|
||||
|
||||
if (rid != 0) {
|
||||
struct vgpu_resource *r = res_find(rid);
|
||||
if (r && r->has_backing && r->width > 0 && r->height > 0) {
|
||||
void *host = virtio_gpa_to_host(r->backing_gpa);
|
||||
if (host) {
|
||||
int cw = (int)r->width;
|
||||
int ch = (int)r->height;
|
||||
if (cw > VGPU_CURSOR_MAX) cw = VGPU_CURSOR_MAX;
|
||||
if (ch > VGPU_CURSOR_MAX) ch = VGPU_CURSOR_MAX;
|
||||
u32 *src = (u32 *)host;
|
||||
for (int row = 0; row < ch; row++) {
|
||||
for (int col = 0; col < cw; col++) {
|
||||
g_gpu.cursor_bitmap[row * VGPU_CURSOR_MAX + col] =
|
||||
src[row * (int)r->width + col];
|
||||
}
|
||||
}
|
||||
g_gpu.cursor_w = (u32)cw;
|
||||
g_gpu.cursor_h = (u32)ch;
|
||||
g_gpu.cursor_visible = 1;
|
||||
} else {
|
||||
/* backing GPA unmapped: still show at pos, no image */
|
||||
g_gpu.cursor_visible = 0;
|
||||
}
|
||||
} else {
|
||||
g_gpu.cursor_visible = 0;
|
||||
}
|
||||
} else {
|
||||
/* resource_id == 0: hide cursor */
|
||||
g_gpu.cursor_visible = 0;
|
||||
}
|
||||
} else if (hdr->type == VIRTIO_GPU_CMD_MOVE_CURSOR) {
|
||||
g_gpu.cursor_x = cmd->pos.x;
|
||||
g_gpu.cursor_y = cmd->pos.y;
|
||||
/* MOVE_CURSOR does not change visibility or image */
|
||||
}
|
||||
}
|
||||
|
||||
write_resp_nodata(cb.resp, cb.resp_len, hdr->type, VIRTIO_GPU_RESP_OK_NODATA);
|
||||
}
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = (u32)sizeof(struct virtio_gpu_ctrl_hdr);
|
||||
used_idx++; cur++; filled++;
|
||||
}
|
||||
|
||||
if (filled > 0) {
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_gpu.last_avail_cursor = cur;
|
||||
virtio_mmio_raise_irq_by_gpa(g_gpu.gpa_base);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== virtio_backend interface ===== */
|
||||
|
||||
static u32 gpu_read_config(u32 offset, int width) {
|
||||
/* config space is 16 bytes, little-endian u32 fields. Guest reads u32. */
|
||||
if (width != 4) return 0;
|
||||
if (offset >= (u32)sizeof(struct virtio_gpu_config)) return 0;
|
||||
const u8 *p = (const u8 *)&g_gpu.cfg;
|
||||
u32 v;
|
||||
v = (u32)p[offset] | ((u32)p[offset+1] << 8) |
|
||||
((u32)p[offset+2] << 16) | ((u32)p[offset+3] << 24);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void gpu_write_config(u32 offset, u32 value) {
|
||||
/* events_clear: writing a bit clears it in events_read. We have no events,
|
||||
* so this is effectively a no-op, but honor the w1c semantics. */
|
||||
if (offset == 4) { /* events_clear */
|
||||
g_gpu.cfg.events_read &= ~value;
|
||||
}
|
||||
(void)offset;
|
||||
}
|
||||
|
||||
static void gpu_queue_notify(u32 queue_idx) {
|
||||
if (queue_idx == 0) {
|
||||
gpu_ctrlq_process();
|
||||
} else if (queue_idx == 1) {
|
||||
gpu_cursorq_process();
|
||||
}
|
||||
}
|
||||
|
||||
static void gpu_reset(void) {
|
||||
g_gpu.cfg.events_read = 0;
|
||||
g_gpu.cfg.events_clear = 0;
|
||||
g_gpu.cfg.num_scanouts = 1;
|
||||
g_gpu.cfg.num_capsets = 0;
|
||||
g_gpu.scanout_enabled = 0;
|
||||
g_gpu.scanout_res_id = 0;
|
||||
g_gpu.scanout_w = 0; g_gpu.scanout_h = 0;
|
||||
g_gpu.dirty = 0;
|
||||
g_gpu.dirty_rect_valid = 0;
|
||||
g_gpu.dirty_rx = 0; g_gpu.dirty_ry = 0;
|
||||
g_gpu.dirty_rw = 0; g_gpu.dirty_rh = 0;
|
||||
g_gpu.cursor_visible = 0;
|
||||
g_gpu.cursor_x = 0; g_gpu.cursor_y = 0;
|
||||
g_gpu.cursor_hot_x = 0; g_gpu.cursor_hot_y = 0;
|
||||
g_gpu.cursor_w = 0; g_gpu.cursor_h = 0;
|
||||
g_gpu.last_avail_ctrl = 0;
|
||||
g_gpu.last_avail_cursor = 0;
|
||||
for (int i = 0; i < VGPU_MAX_RESOURCES; i++) res_release(&g_gpu.res[i]);
|
||||
}
|
||||
|
||||
static struct virtio_backend g_gpu_backend = {
|
||||
.device_id = VIRTIO_ID_GPU,
|
||||
.gpa_base = VIRTIO_MMIO_GPU_GPA,
|
||||
.irq = 10, /* cmdline :10, vector 0x3A */
|
||||
.num_queues = 2, /* ctrlq + cursorq */
|
||||
.queue_size = 64,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1),
|
||||
.config_len = (u32)sizeof(struct virtio_gpu_config),
|
||||
.read_config = gpu_read_config,
|
||||
.write_config = gpu_write_config,
|
||||
.queue_notify = gpu_queue_notify,
|
||||
.reset = gpu_reset,
|
||||
};
|
||||
|
||||
/* ===== public API ===== */
|
||||
|
||||
int virtio_gpu_get_scanout_info(struct virtio_gpu_scanout_info *out) {
|
||||
if (!out) return -1;
|
||||
if (!surface_ready()) {
|
||||
out->host_vaddr = 0;
|
||||
out->width = 0; out->height = 0; out->stride = 0;
|
||||
out->dirty = 0; out->enabled = 0;
|
||||
out->dirty_x = 0; out->dirty_y = 0;
|
||||
out->dirty_w = 0; out->dirty_h = 0;
|
||||
out->cursor_visible = 0; out->cursor_x = 0; out->cursor_y = 0;
|
||||
out->cursor_hot_x = 0; out->cursor_hot_y = 0;
|
||||
out->cursor_w = 0; out->cursor_h = 0; out->cursor_bitmap = 0;
|
||||
return -1;
|
||||
}
|
||||
out->host_vaddr = g_gpu.surface_vaddr;
|
||||
out->width = VGPU_SCANOUT_W;
|
||||
out->height = VGPU_SCANOUT_H;
|
||||
out->stride = VGPU_SCANOUT_STRIDE;
|
||||
out->enabled = (u32)g_gpu.scanout_enabled;
|
||||
out->dirty = (u32)g_gpu.dirty;
|
||||
/* Phase 7: 输出累积 dirty rect(查询一并清零)。
|
||||
* dirty=1 但 rect 无效(理论不出现)时给全帧矩形兜底。 */
|
||||
if (g_gpu.dirty && g_gpu.dirty_rect_valid) {
|
||||
out->dirty_x = g_gpu.dirty_rx;
|
||||
out->dirty_y = g_gpu.dirty_ry;
|
||||
out->dirty_w = g_gpu.dirty_rw;
|
||||
out->dirty_h = g_gpu.dirty_rh;
|
||||
} else if (g_gpu.dirty) {
|
||||
out->dirty_x = 0; out->dirty_y = 0;
|
||||
out->dirty_w = VGPU_SCANOUT_W; out->dirty_h = VGPU_SCANOUT_H;
|
||||
} else {
|
||||
out->dirty_x = 0; out->dirty_y = 0;
|
||||
out->dirty_w = 0; out->dirty_h = 0;
|
||||
}
|
||||
g_gpu.dirty = 0; /* query consumes the dirty flag */
|
||||
g_gpu.dirty_rect_valid = 0;
|
||||
g_gpu.dirty_rw = 0; g_gpu.dirty_rh = 0;
|
||||
|
||||
/* P7.6: cursor state (not consumed by query - desktop reads every frame) */
|
||||
out->cursor_visible = (u32)g_gpu.cursor_visible;
|
||||
out->cursor_x = g_gpu.cursor_x;
|
||||
out->cursor_y = g_gpu.cursor_y;
|
||||
out->cursor_hot_x = g_gpu.cursor_hot_x;
|
||||
out->cursor_hot_y = g_gpu.cursor_hot_y;
|
||||
out->cursor_w = g_gpu.cursor_w;
|
||||
out->cursor_h = g_gpu.cursor_h;
|
||||
out->cursor_bitmap = g_gpu.cursor_visible ? g_gpu.cursor_bitmap : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void virtio_gpu_backend_init(void) {
|
||||
g_gpu.gpa_base = VIRTIO_MMIO_GPU_GPA;
|
||||
g_gpu.irq = 10;
|
||||
|
||||
gpu_reset(); /* sets cfg + clears resource table */
|
||||
|
||||
/* Bind the surface pool as the scanout shadow buffer. */
|
||||
g_gpu.surface_vaddr = linux_get_surface_vaddr();
|
||||
u64 hpa = 0, gpa = 0, size = 0;
|
||||
if (linux_get_surface_info(&hpa, &gpa, &size) == 0) {
|
||||
g_gpu.surface_size = size;
|
||||
} else {
|
||||
g_gpu.surface_size = 0;
|
||||
}
|
||||
|
||||
if (!surface_ready()) {
|
||||
log_warn("[VGPU] surface pool unavailable — scanout disabled");
|
||||
} else {
|
||||
log_info("[VGPU] virtio-gpu 2D backend ready (1024x768 XRGB8888)");
|
||||
}
|
||||
|
||||
virtio_mmio_register(&g_gpu_backend);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
vmm/virtio_gpu.o: vmm\virtio_gpu.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
@@ -0,0 +1,392 @@
|
||||
/* virtio_input.c — UTSM virtio-input backend (keyboard + mouse).
|
||||
*
|
||||
* VSCode integration Phase 2: forward Deshab desktop input events to the
|
||||
* Linux guest via two virtio-input devices (device_id=18):
|
||||
* slot 3 @ 0xF4003000 IRQ8 — keyboard (evdev /dev/input/event0)
|
||||
* slot 4 @ 0xF4004000 IRQ9 — mouse (evdev /dev/input/event1)
|
||||
*
|
||||
* Data flow:
|
||||
* 1. Deshab desktop (DSK) calls virtio_input_push_keyboard/push_mouse_* on
|
||||
* a PS/2 scancode / mouse packet. The event lands in a pending ring.
|
||||
* 2. vmexit_before_resume() calls virtio_input_poll() every ~1ms.
|
||||
* 3. poll drains pending events into guest eventq buffers the driver posted
|
||||
* (device→driver direction), bumps the used ring, and raises IRQ8/IRQ9.
|
||||
* 4. Linux evdev delivers the events to userspace (Xorg/libinput).
|
||||
*
|
||||
* The statusq (queue 1, driver→device) is consumed but ignored — we only
|
||||
* acknowledge status events (e.g. keyboard LED state) with len=0.
|
||||
*
|
||||
* Config space implements the select/subsel state machine per virtio-input
|
||||
* spec: driver writes select+subsel, then reads the 128-byte payload.
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* ===== Config space layout (virtio-input spec §5) =====
|
||||
* [0] select — which config item
|
||||
* [1] subsel — sub-selector (e.g. event type for EV_BITS)
|
||||
* [2] size — valid payload bytes (0..128)
|
||||
* [3..7] reserved
|
||||
* [8..135] payload (string or bitmap) */
|
||||
#define VIO_CFG_LEN 136
|
||||
#define VIO_CFG_PAYLOAD 128
|
||||
|
||||
/* select values (virtio_input_config select field) */
|
||||
#define VIO_CFG_ID_NAME 1
|
||||
#define VIO_CFG_ID_SERIAL 2
|
||||
#define VIO_CFG_ID_DEVIDS 3
|
||||
#define VIO_CFG_PROP_BITS 4
|
||||
#define VIO_CFG_EV_BITS 5
|
||||
|
||||
/* Pending event ring depth. 256 events is generous headroom over the
|
||||
* ~1kHz poll rate — if the guest hasn't posted eventq buffers for 256ms
|
||||
* of input, events start dropping (oldest first). */
|
||||
#define VIO_PEND_MAX 256
|
||||
|
||||
struct virtio_input_dev {
|
||||
u64 gpa_base;
|
||||
u32 irq;
|
||||
const char *name;
|
||||
|
||||
/* Config select/subsel state (written by guest, read back as payload) */
|
||||
u8 cfg_select;
|
||||
u8 cfg_subsel;
|
||||
u8 cfg_buf[VIO_CFG_LEN];
|
||||
|
||||
/* Pending event ring (SPSC): push_* produces, poll consumes.
|
||||
* No lock — UTSM is cooperative: push runs in DSK context (guest
|
||||
* parked), poll runs in vmexit_before_resume (just before resume).
|
||||
* They never execute concurrently. */
|
||||
struct virtio_input_event pending[VIO_PEND_MAX];
|
||||
u32 pend_head;
|
||||
u32 pend_tail;
|
||||
u32 pend_count;
|
||||
|
||||
/* Per-queue avail cursor (last processed avail->idx). */
|
||||
u16 last_avail_event;
|
||||
u16 last_avail_status;
|
||||
|
||||
struct virtio_backend backend;
|
||||
};
|
||||
|
||||
static struct virtio_input_dev g_kbd;
|
||||
static struct virtio_input_dev g_mouse;
|
||||
|
||||
/* ===== Config space rebuild =====
|
||||
* Called whenever the guest writes select/subsel. Fills cfg_buf with the
|
||||
* full 136-byte config image (select, subsel, size, reserved, payload). */
|
||||
static void input_rebuild_cfg(struct virtio_input_dev *dev) {
|
||||
u8 *buf = dev->cfg_buf;
|
||||
buf[0] = dev->cfg_select;
|
||||
buf[1] = dev->cfg_subsel;
|
||||
buf[2] = 0; /* size, set below */
|
||||
for (int i = 3; i < 8; i++) buf[i] = 0;
|
||||
|
||||
u8 *payload = buf + 8;
|
||||
for (int i = 0; i < VIO_CFG_PAYLOAD; i++) payload[i] = 0;
|
||||
u8 size = 0;
|
||||
|
||||
switch (dev->cfg_select) {
|
||||
case VIO_CFG_ID_NAME: {
|
||||
const char *s = dev->name;
|
||||
u32 i = 0;
|
||||
while (s[i] && i < VIO_CFG_PAYLOAD) { payload[i] = (u8)s[i]; i++; }
|
||||
size = (u8)i;
|
||||
break;
|
||||
}
|
||||
case VIO_CFG_ID_SERIAL:
|
||||
size = 0; /* no serial */
|
||||
break;
|
||||
case VIO_CFG_ID_DEVIDS: {
|
||||
/* Bitmap of supported event types (bit N = EV_TYPE N).
|
||||
* keyboard: EV_KEY only; mouse: EV_KEY + EV_REL. */
|
||||
payload[VIO_INPUT_EV_KEY / 8] |= (u8)(1u << (VIO_INPUT_EV_KEY % 8));
|
||||
if (dev == &g_mouse) {
|
||||
payload[VIO_INPUT_EV_REL / 8] |= (u8)(1u << (VIO_INPUT_EV_REL % 8));
|
||||
}
|
||||
u32 max_ev = (dev == &g_mouse) ? VIO_INPUT_EV_REL : VIO_INPUT_EV_KEY;
|
||||
size = (u8)(max_ev / 8 + 1);
|
||||
break;
|
||||
}
|
||||
case VIO_CFG_PROP_BITS:
|
||||
size = 0;
|
||||
break;
|
||||
case VIO_CFG_EV_BITS: {
|
||||
/* subsel = event type; payload = bitmap of supported event codes. */
|
||||
u16 ev = dev->cfg_subsel;
|
||||
if (dev == &g_kbd && ev == VIO_INPUT_EV_KEY) {
|
||||
/* Keyboard: advertise keys 1..248 (ESCAPE..KEY_WWW). The driver
|
||||
* enumerates all set bits; only forwarded keys will ever arrive. */
|
||||
for (u32 i = 1; i <= 248; i++) {
|
||||
payload[i / 8] |= (u8)(1u << (i % 8));
|
||||
}
|
||||
size = 32; /* 256 bits = 32 bytes */
|
||||
} else if (dev == &g_mouse) {
|
||||
if (ev == VIO_INPUT_EV_KEY) {
|
||||
payload[VIO_INPUT_BTN_LEFT / 8] |= (u8)(1u << (VIO_INPUT_BTN_LEFT % 8));
|
||||
payload[VIO_INPUT_BTN_RIGHT / 8] |= (u8)(1u << (VIO_INPUT_BTN_RIGHT % 8));
|
||||
payload[VIO_INPUT_BTN_MIDDLE / 8] |= (u8)(1u << (VIO_INPUT_BTN_MIDDLE % 8));
|
||||
size = (u8)(VIO_INPUT_BTN_MIDDLE / 8 + 1);
|
||||
} else if (ev == VIO_INPUT_EV_REL) {
|
||||
payload[VIO_INPUT_REL_X / 8] |= (u8)(1u << (VIO_INPUT_REL_X % 8));
|
||||
payload[VIO_INPUT_REL_Y / 8] |= (u8)(1u << (VIO_INPUT_REL_Y % 8));
|
||||
size = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
size = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
buf[2] = size;
|
||||
}
|
||||
|
||||
/* Read width bytes from the config buffer at offset (little-endian u32). */
|
||||
static u32 input_read_config_at(struct virtio_input_dev *dev, u32 offset, int width) {
|
||||
if (offset >= VIO_CFG_LEN) return 0;
|
||||
u32 val = 0;
|
||||
for (int i = 0; i < width && offset + i < VIO_CFG_LEN; i++) {
|
||||
val |= (u32)dev->cfg_buf[offset + i] << (8 * i);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/* ===== Per-device thin wrappers (backend callbacks have no ctx) ===== */
|
||||
|
||||
static u32 kbd_read_config(u32 offset, int width) { return input_read_config_at(&g_kbd, offset, width); }
|
||||
static u32 mouse_read_config(u32 offset, int width) { return input_read_config_at(&g_mouse, offset, width); }
|
||||
|
||||
static void kbd_write_config(u32 offset, u32 value) {
|
||||
if (offset == 0) { g_kbd.cfg_select = (u8)value; input_rebuild_cfg(&g_kbd); }
|
||||
else if (offset == 1) { g_kbd.cfg_subsel = (u8)value; input_rebuild_cfg(&g_kbd); }
|
||||
}
|
||||
static void mouse_write_config(u32 offset, u32 value) {
|
||||
if (offset == 0) { g_mouse.cfg_select = (u8)value; input_rebuild_cfg(&g_mouse); }
|
||||
else if (offset == 1) { g_mouse.cfg_subsel = (u8)value; input_rebuild_cfg(&g_mouse); }
|
||||
}
|
||||
|
||||
static void input_reset_dev(struct virtio_input_dev *dev) {
|
||||
dev->cfg_select = 0;
|
||||
dev->cfg_subsel = 0;
|
||||
dev->pend_head = 0;
|
||||
dev->pend_tail = 0;
|
||||
dev->pend_count = 0;
|
||||
dev->last_avail_event = 0;
|
||||
dev->last_avail_status = 0;
|
||||
input_rebuild_cfg(dev);
|
||||
}
|
||||
static void kbd_reset(void) { input_reset_dev(&g_kbd); }
|
||||
static void mouse_reset(void) { input_reset_dev(&g_mouse); }
|
||||
|
||||
/* ===== Eventq drain (device → driver) =====
|
||||
* Walk the eventq avail ring: for each empty buffer the driver posted, pop
|
||||
* one pending event, write it into the buffer, complete the used entry.
|
||||
* Returns the number of buffers filled. */
|
||||
static int input_drain_eventq(struct virtio_input_dev *dev) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs_by_gpa(dev->gpa_base, 0, &desc, &avail, &used, &qnum) != 0)
|
||||
return 0;
|
||||
|
||||
int filled = 0;
|
||||
u16 cur = dev->last_avail_event;
|
||||
u16 used_idx = used->idx;
|
||||
int budget = 32; /* cap per poll to bound vmexit latency */
|
||||
|
||||
while (budget-- > 0 && dev->pend_count > 0) {
|
||||
if (cur == avail->idx) break; /* no empty buffers posted */
|
||||
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
if (head >= qnum) { cur++; continue; } /* bad index, skip */
|
||||
|
||||
struct virtq_desc *d = &desc[head];
|
||||
struct virtio_input_event *buf = (struct virtio_input_event *)virtio_gpa_to_host(d->addr);
|
||||
if (!buf || d->len < (u32)sizeof(struct virtio_input_event)) {
|
||||
/* buffer too small or unmapped: return it empty so guest recycles */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Pop one pending event into the guest buffer */
|
||||
u32 idx = dev->pend_tail;
|
||||
buf->type = dev->pending[idx].type;
|
||||
buf->code = dev->pending[idx].code;
|
||||
buf->value = dev->pending[idx].value;
|
||||
dev->pend_tail = (idx + 1) % VIO_PEND_MAX;
|
||||
dev->pend_count--;
|
||||
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = sizeof(struct virtio_input_event);
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
}
|
||||
|
||||
if (filled > 0) {
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
dev->last_avail_event = cur;
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
/* ===== Statusq drain (driver → device) =====
|
||||
* The driver occasionally posts status events (e.g. keyboard LED state).
|
||||
* We don't act on them — just complete the buffers with len=0 so the driver
|
||||
* can recycle them and the avail ring doesn't fill up. */
|
||||
static int input_drain_statusq(struct virtio_input_dev *dev) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs_by_gpa(dev->gpa_base, 1, &desc, &avail, &used, &qnum) != 0)
|
||||
return 0;
|
||||
|
||||
int filled = 0;
|
||||
u16 cur = dev->last_avail_status;
|
||||
u16 used_idx = used->idx;
|
||||
int budget = 8;
|
||||
|
||||
while (budget-- > 0 && cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
if (head < qnum) {
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
filled++;
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
|
||||
if (filled > 0) {
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
dev->last_avail_status = cur;
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
/* ===== Backend queue_notify callbacks =====
|
||||
* eventq notify (queue 0): the driver just posted empty buffers — try to
|
||||
* drain pending events now. IRQ is raised by the framework after notify.
|
||||
* statusq notify (queue 1): the driver sent a status event — consume it. */
|
||||
static void kbd_queue_notify(u32 queue_idx) {
|
||||
if (queue_idx == 0) {
|
||||
if (input_drain_eventq(&g_kbd) > 0)
|
||||
virtio_mmio_raise_irq_by_gpa(g_kbd.gpa_base);
|
||||
} else if (queue_idx == 1) {
|
||||
input_drain_statusq(&g_kbd);
|
||||
}
|
||||
}
|
||||
static void mouse_queue_notify(u32 queue_idx) {
|
||||
if (queue_idx == 0) {
|
||||
if (input_drain_eventq(&g_mouse) > 0)
|
||||
virtio_mmio_raise_irq_by_gpa(g_mouse.gpa_base);
|
||||
} else if (queue_idx == 1) {
|
||||
input_drain_statusq(&g_mouse);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Pending event push API (called by DSK / desktop input forwarding) ===== */
|
||||
|
||||
static void input_push(struct virtio_input_dev *dev, u16 type, u16 code, u32 value) {
|
||||
if (dev->pend_count >= VIO_PEND_MAX) {
|
||||
/* Ring full: drop the oldest event to make room for the newest.
|
||||
* Dropping old (not new) preserves the most recent input state,
|
||||
* which is what matters for interactive feel. */
|
||||
dev->pend_tail = (dev->pend_tail + 1) % VIO_PEND_MAX;
|
||||
dev->pend_count--;
|
||||
}
|
||||
u32 idx = dev->pend_head;
|
||||
dev->pending[idx].type = type;
|
||||
dev->pending[idx].code = code;
|
||||
dev->pending[idx].value = value;
|
||||
dev->pend_head = (idx + 1) % VIO_PEND_MAX;
|
||||
dev->pend_count++;
|
||||
}
|
||||
|
||||
void virtio_input_push_keyboard(u16 code, u32 value) {
|
||||
/* code = linux keycode (KEY_A..), value = 0=release, 1=press, 2=repeat */
|
||||
input_push(&g_kbd, VIO_INPUT_EV_KEY, code, value);
|
||||
}
|
||||
|
||||
void virtio_input_push_mouse_rel(u16 axis, i32 value) {
|
||||
/* axis = VIO_INPUT_REL_X or VIO_INPUT_REL_Y */
|
||||
input_push(&g_mouse, VIO_INPUT_EV_REL, axis, (u32)value);
|
||||
}
|
||||
|
||||
void virtio_input_push_mouse_button(u16 btn, u32 value) {
|
||||
/* btn = VIO_INPUT_BTN_LEFT/RIGHT/MIDDLE, value = 0=release, 1=press */
|
||||
input_push(&g_mouse, VIO_INPUT_EV_KEY, btn, value);
|
||||
}
|
||||
|
||||
void virtio_input_push_mouse_syn(void) {
|
||||
/* SYN_REPORT marks the end of a logical input frame (e.g. a complete
|
||||
* mouse movement + button change). libinput relies on this to deliver
|
||||
* a single consolidated event to clients. */
|
||||
input_push(&g_mouse, VIO_INPUT_EV_SYN, 0, 0);
|
||||
}
|
||||
|
||||
/* ===== Periodic poll (called from vmexit_before_resume) ===== */
|
||||
void virtio_input_poll(void) {
|
||||
int kbd_filled = input_drain_eventq(&g_kbd);
|
||||
int mouse_filled = input_drain_eventq(&g_mouse);
|
||||
if (kbd_filled > 0) virtio_mmio_raise_irq_by_gpa(g_kbd.gpa_base);
|
||||
if (mouse_filled > 0) virtio_mmio_raise_irq_by_gpa(g_mouse.gpa_base);
|
||||
}
|
||||
|
||||
/* ===== Backend init ===== */
|
||||
|
||||
static struct virtio_backend g_kbd_backend = {
|
||||
.device_id = VIRTIO_ID_INPUT,
|
||||
.gpa_base = VIRTIO_MMIO_KBD_GPA,
|
||||
.irq = 8, /* cmdline :8, vector 0x38 */
|
||||
.num_queues = 2, /* eventq + statusq */
|
||||
.queue_size = 64,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1),
|
||||
.config_len = VIO_CFG_LEN,
|
||||
.read_config = kbd_read_config,
|
||||
.write_config = kbd_write_config,
|
||||
.queue_notify = kbd_queue_notify,
|
||||
.reset = kbd_reset,
|
||||
};
|
||||
|
||||
static struct virtio_backend g_mouse_backend = {
|
||||
.device_id = VIRTIO_ID_INPUT,
|
||||
.gpa_base = VIRTIO_MMIO_MOUSE_GPA,
|
||||
.irq = 9, /* cmdline :9, vector 0x39 */
|
||||
.num_queues = 2,
|
||||
.queue_size = 64,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1),
|
||||
.config_len = VIO_CFG_LEN,
|
||||
.read_config = mouse_read_config,
|
||||
.write_config = mouse_write_config,
|
||||
.queue_notify = mouse_queue_notify,
|
||||
.reset = mouse_reset,
|
||||
};
|
||||
|
||||
void virtio_input_backend_init(void) {
|
||||
g_kbd.gpa_base = VIRTIO_MMIO_KBD_GPA;
|
||||
g_kbd.irq = 8;
|
||||
g_kbd.name = "Deshab Virtual Keyboard";
|
||||
input_reset_dev(&g_kbd);
|
||||
|
||||
g_mouse.gpa_base = VIRTIO_MMIO_MOUSE_GPA;
|
||||
g_mouse.irq = 9;
|
||||
g_mouse.name = "Deshab Virtual Mouse";
|
||||
input_reset_dev(&g_mouse);
|
||||
|
||||
virtio_mmio_register(&g_kbd_backend);
|
||||
virtio_mmio_register(&g_mouse_backend);
|
||||
|
||||
log_info("[VINPUT] keyboard + mouse backends registered");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
vmm/virtio_input.o: vmm\virtio_input.c include\utsm\virtio_mmio.h \
|
||||
include\utsm\types.h include\utsm\linux_loader.h include\utsm\log.h
|
||||
include\utsm\virtio_mmio.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\linux_loader.h:
|
||||
include\utsm\log.h:
|
||||
@@ -20,7 +20,8 @@ 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
|
||||
/* VSCode Phase 1: 3 (blk/net/rootfs) → 8 slots for input/gpu/extra-rootfs. */
|
||||
#define MAX_VIRTIO_DEVICES 8
|
||||
|
||||
/* 每个 virtio 设备的运行状态(框架层维护,后端无关) */
|
||||
struct virtio_dev_state {
|
||||
@@ -227,7 +228,13 @@ void virtio_mmio_write(u64 gpa, u64 value, int width) {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* device config 空间写(多数设备 config 只读,忽略) */
|
||||
/* VSCode Phase 2: device config 空间写。virtio-input 用此通道设置
|
||||
* select/subsel 后再读结果;其它设备 config 只读(write_config=NULL)。 */
|
||||
if (off >= VIRTIO_MMIO_CONFIG && off < VIRTIO_MMIO_CONFIG + s->be->config_len) {
|
||||
if (s->be->write_config) {
|
||||
s->be->write_config(off - VIRTIO_MMIO_CONFIG, v32);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -264,6 +271,35 @@ int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: 按 gpa_base 定位队列。用于两个 virtio-input(kbd+mouse),
|
||||
* 它们 device_id 相同(VIRTIO_ID_INPUT=18),只能靠 gpa_base 区分。 */
|
||||
int virtio_queue_get_ptrs_by_gpa(u64 gpa_base, 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->gpa_base == gpa_base) {
|
||||
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");
|
||||
@@ -283,17 +319,34 @@ void virtio_mmio_raise_irq(u32 device_id) {
|
||||
}
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: 按 gpa_base 触发中断(两个 virtio-input 同 device_id=18
|
||||
* 但不同 gpa_base / irq,需要分别注入)。 */
|
||||
void virtio_mmio_raise_irq_by_gpa(u64 gpa_base) {
|
||||
for (int i = 0; i < g_vdev_count; i++) {
|
||||
struct virtio_dev_state *s = &g_vdevs[i];
|
||||
if (!s->be || s->be->gpa_base != gpa_base) continue;
|
||||
s->interrupt_status |= 1;
|
||||
if (s->be->irq != 0) {
|
||||
vmx_guest_queue_irq(0x30 + s->be->irq);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 框架初始化 =====
|
||||
*
|
||||
* 注册 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)。
|
||||
* 这里声明以便链接;具体注册在各自文件完成。 */
|
||||
/* 后端初始化符号(定义于 virtio_blk.c / virtio_net.c / virtio_input.c /
|
||||
* virtio_gpu.c)。这里声明以便链接;具体注册在各自文件完成。 */
|
||||
void virtio_blk_backend_init(void);
|
||||
void virtio_net_backend_init(void);
|
||||
void virtio_rootfs_blk_init(void); /* memory-backed Arch rootfs (/dev/vdb) */
|
||||
void virtio_input_backend_init(void); /* VSCode Phase 2: keyboard + mouse */
|
||||
void virtio_gpu_backend_init(void); /* VSCode Phase 3: virtio-gpu 2D */
|
||||
void virtio_extra_rootfs_blk_init(void); /* VSCode Phase 4: rw volume (/dev/vdc) */
|
||||
|
||||
void virtio_mmio_init(void) {
|
||||
if (g_vio_ready) return;
|
||||
@@ -305,6 +358,17 @@ void virtio_mmio_init(void) {
|
||||
* Must come after blk/net so /dev/vda (SATA) and /dev/vdb (rootfs) order
|
||||
* is deterministic. */
|
||||
virtio_rootfs_blk_init();
|
||||
/* VSCode Phase 2: virtio-input (keyboard + mouse). Safe no-op until the
|
||||
* desktop enables input forwarding via the push_* API. */
|
||||
virtio_input_backend_init();
|
||||
/* VSCode Phase 3: virtio-gpu 2D (scanout compositing into surface pool).
|
||||
* Safe no-op if the surface pool was not allocated (Linux guest not loaded
|
||||
* or alloc failed) — scanout stays disabled. */
|
||||
virtio_gpu_backend_init();
|
||||
/* VSCode Phase 4: writable extra-rootfs volume (/dev/vdc, overlay upper).
|
||||
* Safe no-op if no extra-rootfs boot module — guest probe sees bad magic
|
||||
* on slot6 and skips it. */
|
||||
virtio_extra_rootfs_blk_init();
|
||||
|
||||
g_vio_ready = 1;
|
||||
log_info("[VIO] virtio-mmio backend init ok");
|
||||
|
||||
@@ -60,12 +60,16 @@ static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len
|
||||
* - Linux guest:park(推进 RIP 越过 HLT,exit-to-host,设 g_guest_parked=1)
|
||||
* host 调用 linux_resume() 时 vmresume 唤醒 guest 从 HLT 之后继续执行。 */
|
||||
static int handle_hlt(u64 rip, u64 instr_len, int *out_resume) {
|
||||
*out_resume = 0; /* 两种情况都 exit-to-host */
|
||||
*out_resume = 0; /* 三种情况默认 exit-to-host */
|
||||
if (g_linux_guest_active) {
|
||||
/* Linux guest: park — 推进 RIP 越过 HLT 指令 */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
g_guest_parked = 1;
|
||||
log_info("[VMEXIT] HLT - Linux guest parked");
|
||||
} else if (g_xj380_guest_active) {
|
||||
/* OpenXJ380 guest: 推进 RIP 并立即继续执行(不 park 不 terminate) */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
*out_resume = 1;
|
||||
} else {
|
||||
/* self-test guest: 终止 */
|
||||
log_info("[VMEXIT] HLT - guest halted");
|
||||
@@ -493,6 +497,7 @@ static void vmexit_before_resume(void) {
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
poll_host_serial_to_guest();
|
||||
virtio_net_poll();
|
||||
virtio_input_poll(); /* VSCode Phase 2: drain pending kbd/mouse events */
|
||||
pit_tick_update();
|
||||
maybe_inject_irq();
|
||||
}
|
||||
|
||||
+173
-18
@@ -85,6 +85,7 @@ static u8 g_msr_bitmap[4096] __attribute__((aligned(4096)));
|
||||
* VMX 要求 guest TR 非空且引用 GDT 中的 TSS 描述符。 */
|
||||
static u8 g_guest_gdt[4096] __attribute__((aligned(4096)));
|
||||
static u8 g_guest_tss[4096] __attribute__((aligned(4096)));
|
||||
static u8 g_guest_idt[4096] __attribute__((aligned(4096))); /* P8.4: IDT page for KVM nested VMX */
|
||||
|
||||
/* 64-bit GDT entry */
|
||||
typedef struct __attribute__((packed)) {
|
||||
@@ -234,10 +235,11 @@ static void vmcs_setup_host_state(void) {
|
||||
}
|
||||
|
||||
static void vmcs_setup_guest_state_phase1(void) {
|
||||
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表 */
|
||||
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表。
|
||||
* P8.4: CR4 must include VMXE (CR4_FIXED0 requires it under KVM). */
|
||||
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR3, read_cr3_local());
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
|
||||
|
||||
/* 段选择子:flat 模型。
|
||||
* CS=0x08 (GDT[1] code), DS/SS/ES=0x10 (GDT[2] data),
|
||||
@@ -261,7 +263,9 @@ static void vmcs_setup_guest_state_phase1(void) {
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_BASE, 0x4000);
|
||||
vmx_vmcs_write(VMCS_GUEST_GDTR_BASE, 0x3000);
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0);
|
||||
/* P8.4: IDTR base must be non-zero & page-aligned for KVM nested VMX.
|
||||
* Guest won't use interrupts (RFLAGS.IF=0), but VMX still checks. */
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0x5000);
|
||||
|
||||
/* 段 limit:代码/数据段 4GB,GDTR 覆盖 0x28 字节(3 个描述符 + TSS),
|
||||
* TR limit = 0x67(104 字节最小 TSS) */
|
||||
@@ -278,16 +282,17 @@ static void vmcs_setup_guest_state_phase1(void) {
|
||||
|
||||
/* 段 access rights:
|
||||
* CS: 0xA09B = present, DPL0, code, L=1 (64-bit), executed/read
|
||||
* DS/SS/ES/FS/GS: 0xC093 = present, DPL0, data, writable
|
||||
* LDTR: 0x82 = unusable (L=0 in high nibble)
|
||||
* DS/SS/ES: 0xC093 = present, DPL0, data, writable
|
||||
* P8.4: FS/GS/LDTR selector=0 -> must set bit 16 (unusable) per Intel SDM.
|
||||
* Without it, KVM nested VMX rejects VMLAUNCH (invalid guest state).
|
||||
* TR: 0x8B = present, DPL0, 64-bit busy TSS */
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0x1C093); /* unusable (selector=0) */
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0x1C093); /* unusable (selector=0) */
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x10082); /* unusable (selector=0) */
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B);
|
||||
|
||||
/* Guest RIP/RSP/RFLAGS */
|
||||
@@ -308,6 +313,10 @@ static void vmcs_setup_guest_state_phase1(void) {
|
||||
* NXE 允许 guest 使用 NX 位。 */
|
||||
vmx_vmcs_write(VMCS_GUEST_IA32_EFER, EFER_LME | EFER_LMA | EFER_NXE);
|
||||
|
||||
/* P8.4: VMCS link pointer must be 0xFFFFFFFFFFFFFFFF (Intel SDM Vol 3,
|
||||
* VM-entry checks). KVM nested VMX enforces this strictly. */
|
||||
vmx_vmcs_write(VMCS_GUEST_LINK_POINTER, 0xFFFFFFFFFFFFFFFFULL);
|
||||
|
||||
/* Guest activity state = 0 (active) */
|
||||
vmx_vmcs_write(VMCS_GUEST_ACTIVITY_STATE, 0);
|
||||
/* Guest interruptibility = 0(无阻塞) */
|
||||
@@ -325,14 +334,11 @@ static void vmcs_setup_controls(u64 eptp) {
|
||||
| CPU_BASED_INVLPG_EXITING;
|
||||
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
||||
|
||||
/* Secondary controls:启用 EPT + VPID */
|
||||
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
|
||||
{
|
||||
u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2);
|
||||
u32 allowed1 = (u32)(msr >> 32);
|
||||
cpu2 &= allowed1;
|
||||
cpu2 |= SEC_EXEC_ENABLE_EPT;
|
||||
}
|
||||
/* P8.4d: Try without EPT first to isolate nested VMX issue under KVM.
|
||||
* If KVM requires EPT, vmx_adjust_control will OR it back in. */
|
||||
u64 cpu2 = 0; /* no EPT, no VPID */
|
||||
cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2);
|
||||
log_hex64("[VMM] IA32_VMX_PROCBASED_CTLS2 raw=", vmx_read_msr(IA32_VMX_PROCBASED_CTLS2));
|
||||
|
||||
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
||||
@@ -357,8 +363,15 @@ static void vmcs_setup_controls(u64 eptp) {
|
||||
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
||||
vmx_vmcs_write(VMCS_VPID, 1);
|
||||
if (cpu2 & SEC_EXEC_ENABLE_EPT) {
|
||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
||||
log_hex64("[VMM] EPT enabled, eptp=", eptp);
|
||||
} else {
|
||||
log_info("[VMM] EPT not available (shadow paging)");
|
||||
}
|
||||
if (cpu2 & SEC_EXEC_ENABLE_VPID) {
|
||||
vmx_vmcs_write(VMCS_VPID, 1);
|
||||
}
|
||||
|
||||
/* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */
|
||||
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6));
|
||||
@@ -418,6 +431,90 @@ void vmm_shutdown(void) {
|
||||
int vmm_is_ready(void) { return g_vmm_ready; }
|
||||
u64 vmm_get_eptp(void) { return ept_get_eptp(); }
|
||||
|
||||
/* P8.4: Dump all guest-state VMCS fields for KVM nested VMX debugging.
|
||||
* Called before VMLAUNCH to identify field encoding/value errors. */
|
||||
static void vmm_dump_guest_state(void) {
|
||||
log_info("[VMM] === Guest State Validation ===");
|
||||
|
||||
/* ---- CR / EFER ---- */
|
||||
u64 g_cr0 = vmx_vmcs_read(VMCS_GUEST_CR0);
|
||||
u64 g_cr3 = vmx_vmcs_read(VMCS_GUEST_CR3);
|
||||
u64 g_cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
u64 g_efer = vmx_vmcs_read(VMCS_GUEST_IA32_EFER);
|
||||
log_hex64("[VMM] gCR0=", g_cr0);
|
||||
log_hex64("[VMM] gCR3=", g_cr3);
|
||||
log_hex64("[VMM] gCR4=", g_cr4);
|
||||
log_hex64("[VMM] gEFER=", g_efer);
|
||||
|
||||
/* ---- RIP/RSP/RFLAGS ---- */
|
||||
log_hex64("[VMM] gRIP=", vmx_vmcs_read(VMCS_GUEST_RIP));
|
||||
log_hex64("[VMM] gRSP=", vmx_vmcs_read(VMCS_GUEST_RSP));
|
||||
log_hex64("[VMM] gRFLAGS=", vmx_vmcs_read(VMCS_GUEST_RFLAGS));
|
||||
|
||||
/* ---- Segment selectors ---- */
|
||||
u64 es = vmx_vmcs_read(VMCS_GUEST_ES_SELECTOR);
|
||||
u64 cs = vmx_vmcs_read(VMCS_GUEST_CS_SELECTOR);
|
||||
u64 ss = vmx_vmcs_read(VMCS_GUEST_SS_SELECTOR);
|
||||
u64 ds = vmx_vmcs_read(VMCS_GUEST_DS_SELECTOR);
|
||||
u64 fs = vmx_vmcs_read(VMCS_GUEST_FS_SELECTOR);
|
||||
u64 gs = vmx_vmcs_read(VMCS_GUEST_GS_SELECTOR);
|
||||
u64 ldtr = vmx_vmcs_read(VMCS_GUEST_LDTR_SELECTOR);
|
||||
u64 tr = vmx_vmcs_read(VMCS_GUEST_TR_SELECTOR);
|
||||
log_hex64("[VMM] sSEL ES=", es); log_hex64("[VMM] sSEL CS=", cs);
|
||||
log_hex64("[VMM] sSEL SS=", ss); log_hex64("[VMM] sSEL DS=", ds);
|
||||
log_hex64("[VMM] sSEL FS=", fs); log_hex64("[VMM] sSEL GS=", gs);
|
||||
log_hex64("[VMM] sSEL LDTR=", ldtr); log_hex64("[VMM] sSEL TR=", tr);
|
||||
|
||||
/* ---- Segment limits ---- */
|
||||
log_hex64("[VMM] sLIM ES=", vmx_vmcs_read(VMCS_GUEST_ES_LIMIT));
|
||||
log_hex64("[VMM] sLIM CS=", vmx_vmcs_read(VMCS_GUEST_CS_LIMIT));
|
||||
log_hex64("[VMM] sLIM SS=", vmx_vmcs_read(VMCS_GUEST_SS_LIMIT));
|
||||
log_hex64("[VMM] sLIM DS=", vmx_vmcs_read(VMCS_GUEST_DS_LIMIT));
|
||||
log_hex64("[VMM] sLIM FS=", vmx_vmcs_read(VMCS_GUEST_FS_LIMIT));
|
||||
log_hex64("[VMM] sLIM GS=", vmx_vmcs_read(VMCS_GUEST_GS_LIMIT));
|
||||
log_hex64("[VMM] sLIM LDTR=", vmx_vmcs_read(VMCS_GUEST_LDTR_LIMIT));
|
||||
log_hex64("[VMM] sLIM TR=", vmx_vmcs_read(VMCS_GUEST_TR_LIMIT));
|
||||
|
||||
/* ---- Segment access rights ---- */
|
||||
log_hex64("[VMM] sACC ES=", vmx_vmcs_read(VMCS_GUEST_ES_ACCESS));
|
||||
log_hex64("[VMM] sACC CS=", vmx_vmcs_read(VMCS_GUEST_CS_ACCESS));
|
||||
log_hex64("[VMM] sACC SS=", vmx_vmcs_read(VMCS_GUEST_SS_ACCESS));
|
||||
log_hex64("[VMM] sACC DS=", vmx_vmcs_read(VMCS_GUEST_DS_ACCESS));
|
||||
log_hex64("[VMM] sACC FS=", vmx_vmcs_read(VMCS_GUEST_FS_ACCESS));
|
||||
log_hex64("[VMM] sACC GS=", vmx_vmcs_read(VMCS_GUEST_GS_ACCESS));
|
||||
log_hex64("[VMM] sACC LDTR=", vmx_vmcs_read(VMCS_GUEST_LDTR_ACCESS));
|
||||
log_hex64("[VMM] sACC TR=", vmx_vmcs_read(VMCS_GUEST_TR_ACCESS));
|
||||
|
||||
/* ---- Segment bases ---- */
|
||||
log_hex64("[VMM] sBAS CS=", vmx_vmcs_read(VMCS_GUEST_CS_BASE));
|
||||
log_hex64("[VMM] sBAS TR=", vmx_vmcs_read(VMCS_GUEST_TR_BASE));
|
||||
log_hex64("[VMM] sBAS GDTR=", vmx_vmcs_read(VMCS_GUEST_GDTR_BASE));
|
||||
log_hex64("[VMM] sBAS IDTR=", vmx_vmcs_read(VMCS_GUEST_IDTR_BASE));
|
||||
|
||||
/* ---- GDTR/IDTR limits ---- */
|
||||
log_hex64("[VMM] dLIM GDTR=", vmx_vmcs_read(VMCS_GUEST_GDTR_LIMIT));
|
||||
log_hex64("[VMM] dLIM IDTR=", vmx_vmcs_read(VMCS_GUEST_IDTR_LIMIT));
|
||||
|
||||
/* ---- Activity / interruptibility / link ---- */
|
||||
log_hex64("[VMM] gACT=", vmx_vmcs_read(VMCS_GUEST_ACTIVITY_STATE));
|
||||
log_hex64("[VMM] gINTSTATE=", vmx_vmcs_read(VMCS_GUEST_INTERRUPTIBILITY));
|
||||
log_hex64("[VMM] gLINK=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER));
|
||||
|
||||
/* ---- SYSENTER ---- */
|
||||
log_hex64("[VMM] gSYSENTER_CS=", vmx_vmcs_read(VMCS_GUEST_SYSENTER_CS));
|
||||
log_hex64("[VMM] gDR7=", vmx_vmcs_read(VMCS_GUEST_DR7));
|
||||
|
||||
/* ---- Host state summary ---- */
|
||||
log_hex64("[VMM] hCR0=", vmx_vmcs_read(VMCS_HOST_CR0));
|
||||
log_hex64("[VMM] hCR3=", vmx_vmcs_read(VMCS_HOST_CR3));
|
||||
log_hex64("[VMM] hCR4=", vmx_vmcs_read(VMCS_HOST_CR4));
|
||||
log_hex64("[VMM] hRIP=", vmx_vmcs_read(VMCS_HOST_RIP));
|
||||
log_hex64("[VMM] hRSP=", vmx_vmcs_read(VMCS_HOST_RSP));
|
||||
log_hex64("[VMM] hEFER=", vmx_vmcs_read(VMCS_HOST_IA32_EFER));
|
||||
|
||||
log_info("[VMM] === End Guest State ===");
|
||||
}
|
||||
|
||||
int vmm_self_test(void) {
|
||||
if (!g_vmm_ready) {
|
||||
log_error("[VMM] not ready");
|
||||
@@ -458,14 +555,46 @@ int vmm_self_test(void) {
|
||||
log_error("[VMM] map guest TSS failed");
|
||||
return -6;
|
||||
}
|
||||
/* P8.4: Map IDT page for KVM nested VMX (IDTR base = 0x5000) */
|
||||
{
|
||||
u64 guest_idt_phys = (u64)g_guest_idt - hhdm;
|
||||
for (int i = 0; i < 4096; i++) g_guest_idt[i] = 0;
|
||||
if (ept_map_range(0x5000, guest_idt_phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[VMM] map guest IDT failed");
|
||||
return -9;
|
||||
}
|
||||
}
|
||||
|
||||
/* 配置 VMCS */
|
||||
vmcs_setup_guest_state_phase1();
|
||||
vmcs_setup_controls(ept_get_eptp());
|
||||
vmcs_setup_host_state();
|
||||
|
||||
/* P8.4: VMCLEAR + VMPTRLD to put VMCS in "clear" state before VMLAUNCH.
|
||||
* Without this, VMLAUNCH fails (error 4 or silent failure under KVM). */
|
||||
if (vmx_vmcs_clear(g_vmcs_phys) != 0) {
|
||||
log_error("[VMM] vmclear failed");
|
||||
return -10;
|
||||
}
|
||||
if (vmx_vmcs_load(g_vmcs_phys) != 0) {
|
||||
log_error("[VMM] vmptrld after vmclear failed");
|
||||
return -11;
|
||||
}
|
||||
|
||||
log_info("[VMM] launching guest");
|
||||
|
||||
/* P8.4: dump key VMCS fields before VMLAUNCH for debugging */
|
||||
log_hex64("[VMM] pin=", vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL));
|
||||
log_hex64("[VMM] cpu=", vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL));
|
||||
log_hex64("[VMM] cpu2=", vmx_vmcs_read(VMCS_SECONDARY_VM_EXEC_CONTROL));
|
||||
log_hex64("[VMM] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS));
|
||||
log_hex64("[VMM] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS));
|
||||
log_hex64("[VMM] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER));
|
||||
log_hex64("[VMM] link=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER));
|
||||
|
||||
/* P8.4: Dump all guest-state fields for KVM debug */
|
||||
vmm_dump_guest_state();
|
||||
|
||||
/* vmlaunch 控制流:
|
||||
* - 失败:执行下一条指令,failed=1
|
||||
* - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。
|
||||
@@ -495,6 +624,32 @@ int vmm_self_test(void) {
|
||||
serial_write("[VMM] ");
|
||||
serial_write(vmx_error_str(error));
|
||||
serial_putc('\n');
|
||||
|
||||
/* P8.4: diagnostic - dump guest CR0/CR4 vs VMX fixed bits */
|
||||
{
|
||||
u64 cr0_fixed0 = vmx_read_msr(0x486); /* IA32_VMX_CR0_FIXED0 */
|
||||
u64 cr0_fixed1 = vmx_read_msr(0x487); /* IA32_VMX_CR0_FIXED1 */
|
||||
u64 cr4_fixed0 = vmx_read_msr(0x488); /* IA32_VMX_CR4_FIXED0 */
|
||||
u64 cr4_fixed1 = vmx_read_msr(0x489); /* IA32_VMX_CR4_FIXED1 */
|
||||
u64 g_cr0 = vmx_vmcs_read(VMCS_GUEST_CR0);
|
||||
u64 g_cr4 = vmx_vmcs_read(VMCS_GUEST_CR4);
|
||||
log_hex64("[VMM] CR0_FIXED0=", cr0_fixed0);
|
||||
log_hex64("[VMM] CR0_FIXED1=", cr0_fixed1);
|
||||
log_hex64("[VMM] guest CR0 =", g_cr0);
|
||||
log_hex64("[VMM] CR4_FIXED0=", cr4_fixed0);
|
||||
log_hex64("[VMM] CR4_FIXED1=", cr4_fixed1);
|
||||
log_hex64("[VMM] guest CR4 =", g_cr4);
|
||||
/* Check: guest CR0 must have all bits in CR0_FIXED0 set,
|
||||
* and must not have any bits set that are 0 in CR0_FIXED1 */
|
||||
u64 cr0_missing = cr0_fixed0 & ~g_cr0; /* bits FIXED0 requires but guest lacks */
|
||||
u64 cr0_extra = g_cr0 & ~cr0_fixed1; /* bits guest has but FIXED1 forbids */
|
||||
log_hex64("[VMM] CR0 missing(need)=", cr0_missing);
|
||||
log_hex64("[VMM] CR0 extra(forbid)=", cr0_extra);
|
||||
u64 cr4_missing = cr4_fixed0 & ~g_cr4;
|
||||
u64 cr4_extra = g_cr4 & ~cr4_fixed1;
|
||||
log_hex64("[VMM] CR4 missing(need)=", cr4_missing);
|
||||
log_hex64("[VMM] CR4 extra(forbid)=", cr4_extra);
|
||||
}
|
||||
return -7;
|
||||
}
|
||||
|
||||
|
||||
+379
-331
@@ -1,331 +1,379 @@
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* ===== MSR / CR helpers ===== */
|
||||
|
||||
u64 vmx_read_msr(u32 msr) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
|
||||
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
||||
u32 hi = (u32)(value >> 32);
|
||||
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
|
||||
}
|
||||
|
||||
static inline u64 read_cr0(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr0, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_cr4(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr4, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void write_cr4(u64 v) {
|
||||
__asm__ volatile("mov %0, %%cr4" :: "r"(v));
|
||||
}
|
||||
|
||||
static inline u64 read_cr3(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr3, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_rflags(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("pushfq; popq %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
/* ===== CPUID ===== */
|
||||
|
||||
static void cpuid(u32 leaf, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) {
|
||||
__asm__ volatile("cpuid"
|
||||
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
|
||||
: "a"(leaf));
|
||||
}
|
||||
|
||||
int vmx_supported(void) {
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
return (ecx & (1U << 5)) != 0; /* CPUID.1:ECX[5] = VMX */
|
||||
}
|
||||
|
||||
/* ===== VMXON region ===== */
|
||||
|
||||
static u64 g_vmxon_phys;
|
||||
static u64 g_vmcs_revision;
|
||||
static u64 g_vmx_basic;
|
||||
static int g_vmx_enabled;
|
||||
|
||||
/* VMX 要求 CR0 固定位必须为 1(VMX operation 不允许清除)。
|
||||
* 这些位由 IA32_VMX_BASIC MSR 的高 32 位(CR0 fixed-0)和
|
||||
* IA32_VMX_CR0_FIXED1 MSR 决定。简化:直接 OR 上 PE/NE/PG/WP。 */
|
||||
static u64 vmx_required_cr0(void) {
|
||||
return read_cr0() | CR0_PE | CR0_NE | CR0_WP | CR0_PG;
|
||||
}
|
||||
|
||||
static u64 vmx_required_cr4(void) {
|
||||
return read_cr4() | CR4_VMXE | CR4_PAE | CR4_PGE;
|
||||
}
|
||||
|
||||
/* Check whether we are running under a hypervisor that does NOT support
|
||||
* nested VMX. This covers:
|
||||
* - WHPX: CPUID may report VMX but VMXON/CR4.VMXE causes fatal exit
|
||||
* - Any hypervisor without nested virtualization support
|
||||
*
|
||||
* Detection strategy:
|
||||
* 1. Check CPUID.01H:ECX[31] (hypervisor present bit)
|
||||
* 2. If hypervisor present, check CPUID.40000000H for vendor signature
|
||||
* 3. Try reading IA32_VMX_BASIC MSR — if it returns 0, VMX is not real
|
||||
* 4. Check Hyper-V specific: HV_CPU_MANAGEMENT_FEATURES for nested VMX
|
||||
*
|
||||
* If VMX CPUID flag is set but we're under a hypervisor that doesn't expose
|
||||
* real VMX MSRs, we must not attempt CR4.VMXE or VMXON, as this will cause
|
||||
* a fatal VM exit in WHPX (exit code 4).
|
||||
*/
|
||||
static int vmx_truly_available(void) {
|
||||
/* Step 1: If CPUID doesn't even report VMX, definitely not available */
|
||||
if (!vmx_supported()) return 0;
|
||||
|
||||
/* Step 2: Check if running under a hypervisor */
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
if (!(ecx & (1U << 31))) {
|
||||
/* No hypervisor — bare metal, VMX should work */
|
||||
return 1;
|
||||
}
|
||||
log_info("[VMX] hypervisor present bit set, checking nested VMX support");
|
||||
|
||||
/* Step 3: Try reading IA32_VMX_BASIC — if it's zero or faults,
|
||||
* the hypervisor doesn't expose real VMX. */
|
||||
/* We use a safe read: if the MSR doesn't exist, rdmsr would #GP.
|
||||
* However, under WHPX, IA32_VMX_BASIC may return a non-zero value
|
||||
* even though VMXON will fail. So we need another check. */
|
||||
|
||||
/* Step 4: Check hypervisor vendor for nested VMX support */
|
||||
cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
|
||||
|
||||
/* Check for Hyper-V ("Microsoft Hv") */
|
||||
if (ebx == 0x7263694DU && ecx == 0x666F736FU && edx == 0x76482074U) {
|
||||
log_info("[VMX] Hyper-V detected");
|
||||
/* Check if HV_CPU_MANAGEMENT_FEATURES (MSR 0x4000000F) exists.
|
||||
* Hyper-V CPUID leaf 0x40000003 tells us which MSRs are available.
|
||||
* Partition_privileges bit 1 (nested virtualization) is at EBX bit 1
|
||||
* of CPUID 0x40000000 + 3 = 0x40000003.
|
||||
* But safer: just check the max CPUID leaf for Hyper-V. */
|
||||
u32 hv_max_leaf = eax;
|
||||
if (hv_max_leaf >= 0x40000003) {
|
||||
/* CPUID 0x40000003 EBX bit 1 = AccessIntrCtrl (nested) */
|
||||
cpuid(0x40000003, &eax, &ebx, &ecx, &edx);
|
||||
if (ebx & (1U << 1)) {
|
||||
log_info("[VMX] Hyper-V nested virtualization supported");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
log_warn("[VMX] Hyper-V WITHOUT nested VMX support");
|
||||
log_warn("[VMX] CR4.VMXE / VMXON would cause fatal exit, skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check for KVM ("KVMKVMKVM") */
|
||||
if (ebx == 0x4B4D564BU && ecx == 0x564B4D56U && edx == 0x4D564B4DU) {
|
||||
log_info("[VMX] KVM detected — nested VMX may be available");
|
||||
/* KVM typically supports nested VMX if enabled */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Unknown hypervisor — be conservative: skip VMX to avoid
|
||||
* fatal exits. The user must explicitly enable nested VMX on
|
||||
* their hypervisor for Linux compat to work. */
|
||||
log_warn("[VMX] unknown hypervisor, VMX may not be truly available");
|
||||
log_warn("[VMX] skipping VMX init to avoid potential fatal exit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_enable(void) {
|
||||
if (g_vmx_enabled) return 0;
|
||||
|
||||
if (!vmx_truly_available()) {
|
||||
log_warn("[VMX] VMX not truly available, skipping VMX init");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 读 IA32_VMX_BASIC,获取 VMCS revision ID 与 region 大小 */
|
||||
g_vmx_basic = vmx_read_msr(IA32_VMX_BASIC);
|
||||
g_vmcs_revision = g_vmx_basic & 0x7FFFFFFFULL;
|
||||
u32 vmcs_size = (u32)((g_vmx_basic >> 32) & 0x1FFF);
|
||||
if (vmcs_size == 0) vmcs_size = 4096;
|
||||
|
||||
log_hex64("[VMX] VMX_BASIC=", g_vmx_basic);
|
||||
log_hex64("[VMX] VMCS revision=", g_vmcs_revision);
|
||||
|
||||
/* 分配 VMXON region(4KB 对齐,物理页) */
|
||||
dkm_dma_buffer vmxon_buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &vmxon_buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMXON region");
|
||||
return -2;
|
||||
}
|
||||
g_vmxon_phys = vmxon_buf.phys;
|
||||
|
||||
/* 在 VMXON region 偏移 0 写入 VMCS revision ID */
|
||||
u32 *vmxon_ptr = (u32 *)vmxon_buf.virt;
|
||||
vmxon_ptr[0] = (u32)g_vmcs_revision;
|
||||
log_hex64("[VMX] VMXON phys=", g_vmxon_phys);
|
||||
|
||||
/* 设置 CR4.VMXE */
|
||||
u64 cr4 = vmx_required_cr4();
|
||||
write_cr4(cr4);
|
||||
log_hex64("[VMX] CR4=", cr4);
|
||||
|
||||
/* 确保 CR0 满足 VMX 要求(这里只读不写,UEFI 已配置好) */
|
||||
(void)vmx_required_cr0();
|
||||
|
||||
/* 执行 vmxon */
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmxon %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(g_vmxon_phys)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
if (err) {
|
||||
/* 第二次尝试:读 vmx instruction error 通过 VMCS 不适用 vmxon;
|
||||
* 简化:返回失败。 */
|
||||
log_error("[VMX] vmxon failed");
|
||||
return -3;
|
||||
}
|
||||
|
||||
g_vmx_enabled = 1;
|
||||
log_info("[VMX] VMX root mode enabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_disable(void) {
|
||||
if (!g_vmx_enabled) return 0;
|
||||
__asm__ volatile("vmxoff" ::: "memory");
|
||||
g_vmx_enabled = 0;
|
||||
/* 清 CR4.VMXE */
|
||||
u64 cr4 = read_cr4();
|
||||
cr4 &= ~CR4_VMXE;
|
||||
write_cr4(cr4);
|
||||
log_info("[VMX] VMX root mode disabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_alloc(u64 *phys_out) {
|
||||
if (!phys_out) return -1;
|
||||
dkm_dma_buffer buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMCS region");
|
||||
return -2;
|
||||
}
|
||||
/* 偏移 0 写入 VMCS revision ID */
|
||||
u32 *p = (u32 *)buf.virt;
|
||||
p[0] = (u32)g_vmcs_revision;
|
||||
*phys_out = buf.phys;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_load(u64 phys) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmptrld %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(phys)
|
||||
: "memory"
|
||||
);
|
||||
if (err) {
|
||||
log_error("[VMX] vmptrld failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 vmx_vmcs_read(u64 field) {
|
||||
u64 value;
|
||||
__asm__ volatile(
|
||||
"vmread %1, %0\n"
|
||||
: "=r"(value)
|
||||
: "r"(field)
|
||||
: "memory"
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
void vmx_vmcs_write(u64 field, u64 value) {
|
||||
__asm__ volatile(
|
||||
"vmwrite %1, %0\n"
|
||||
:: "r"(field), "r"(value)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
int vmx_vmlaunch(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmlaunch\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
int vmx_vmresume(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmresume\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 暴露给其它模块用的辅助 */
|
||||
u64 vmx_get_host_cr3(void) { return read_cr3(); }
|
||||
u64 vmx_get_host_rflags(void) { return read_rflags(); }
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* ===== MSR / CR helpers ===== */
|
||||
|
||||
u64 vmx_read_msr(u32 msr) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
|
||||
u32 lo = (u32)(value & 0xFFFFFFFFULL);
|
||||
u32 hi = (u32)(value >> 32);
|
||||
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
|
||||
}
|
||||
|
||||
static inline u64 read_cr0(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr0, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_cr4(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr4, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void write_cr0(u64 v) {
|
||||
__asm__ volatile("mov %0, %%cr0" :: "r"(v));
|
||||
}
|
||||
static inline void write_cr4(u64 v) {
|
||||
__asm__ volatile("mov %0, %%cr4" :: "r"(v));
|
||||
}
|
||||
|
||||
static inline u64 read_cr3(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr3, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline u64 read_rflags(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("pushfq; popq %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
/* ===== CPUID ===== */
|
||||
|
||||
static void cpuid(u32 leaf, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) {
|
||||
__asm__ volatile("cpuid"
|
||||
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
|
||||
: "a"(leaf));
|
||||
}
|
||||
|
||||
int vmx_supported(void) {
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
return (ecx & (1U << 5)) != 0; /* CPUID.1:ECX[5] = VMX */
|
||||
}
|
||||
|
||||
/* ===== VMXON region ===== */
|
||||
|
||||
static u64 g_vmxon_phys;
|
||||
static u64 g_vmcs_revision;
|
||||
static u64 g_vmx_basic;
|
||||
static int g_vmx_enabled;
|
||||
|
||||
/* VMX 要求 CR0 固定位必须为 1(VMX operation 不允许清除)。
|
||||
* 这些位由 IA32_VMX_BASIC MSR 的高 32 位(CR0 fixed-0)和
|
||||
* IA32_VMX_CR0_FIXED1 MSR 决定。简化:直接 OR 上 PE/NE/PG/WP。 */
|
||||
static u64 vmx_required_cr0(void) {
|
||||
return read_cr0() | CR0_PE | CR0_NE | CR0_WP | CR0_PG;
|
||||
}
|
||||
|
||||
static u64 vmx_required_cr4(void) {
|
||||
return read_cr4() | CR4_VMXE | CR4_PAE | CR4_PGE;
|
||||
}
|
||||
|
||||
/* Check whether we are running under a hypervisor that does NOT support
|
||||
* nested VMX. This covers:
|
||||
* - WHPX: CPUID may report VMX but VMXON/CR4.VMXE causes fatal exit
|
||||
* - Any hypervisor without nested virtualization support
|
||||
*
|
||||
* Detection strategy:
|
||||
* 1. Check CPUID.01H:ECX[31] (hypervisor present bit)
|
||||
* 2. If hypervisor present, check CPUID.40000000H for vendor signature
|
||||
* 3. Try reading IA32_VMX_BASIC MSR — if it returns 0, VMX is not real
|
||||
* 4. Check Hyper-V specific: HV_CPU_MANAGEMENT_FEATURES for nested VMX
|
||||
*
|
||||
* If VMX CPUID flag is set but we're under a hypervisor that doesn't expose
|
||||
* real VMX MSRs, we must not attempt CR4.VMXE or VMXON, as this will cause
|
||||
* a fatal VM exit in WHPX (exit code 4).
|
||||
*/
|
||||
static int vmx_truly_available(void) {
|
||||
/* Step 1: If CPUID doesn't even report VMX, definitely not available */
|
||||
if (!vmx_supported()) return 0;
|
||||
|
||||
/* Step 2: Check if running under a hypervisor */
|
||||
u32 eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
if (!(ecx & (1U << 31))) {
|
||||
/* No hypervisor — bare metal, VMX should work */
|
||||
return 1;
|
||||
}
|
||||
log_info("[VMX] hypervisor present bit set, checking nested VMX support");
|
||||
|
||||
/* Step 3: Try reading IA32_VMX_BASIC — if it's zero or faults,
|
||||
* the hypervisor doesn't expose real VMX. */
|
||||
/* We use a safe read: if the MSR doesn't exist, rdmsr would #GP.
|
||||
* However, under WHPX, IA32_VMX_BASIC may return a non-zero value
|
||||
* even though VMXON will fail. So we need another check. */
|
||||
|
||||
/* Step 4: Check hypervisor vendor for nested VMX support */
|
||||
cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
|
||||
|
||||
/* P8.3 Fix: diagnostic logging of hypervisor vendor */
|
||||
{
|
||||
char hv[13];
|
||||
hv[0] = (char)(ebx & 0xFF); hv[1] = (char)((ebx >> 8) & 0xFF);
|
||||
hv[2] = (char)((ebx >> 16) & 0xFF); hv[3] = (char)((ebx >> 24) & 0xFF);
|
||||
hv[4] = (char)(ecx & 0xFF); hv[5] = (char)((ecx >> 8) & 0xFF);
|
||||
hv[6] = (char)((ecx >> 16) & 0xFF); hv[7] = (char)((ecx >> 24) & 0xFF);
|
||||
hv[8] = (char)(edx & 0xFF); hv[9] = (char)((edx >> 8) & 0xFF);
|
||||
hv[10] = (char)((edx >> 16) & 0xFF); hv[11] = (char)((edx >> 24) & 0xFF);
|
||||
hv[12] = 0;
|
||||
log_info("[VMX] hypervisor vendor: ");
|
||||
log_info(hv);
|
||||
}
|
||||
|
||||
/* Check for Hyper-V ("Microsoft Hv") */
|
||||
if (ebx == 0x7263694DU && ecx == 0x666F736FU && edx == 0x76482074U) {
|
||||
log_info("[VMX] Hyper-V detected");
|
||||
/* Check if HV_CPU_MANAGEMENT_FEATURES (MSR 0x4000000F) exists.
|
||||
* Hyper-V CPUID leaf 0x40000003 tells us which MSRs are available.
|
||||
* Partition_privileges bit 1 (nested virtualization) is at EBX bit 1
|
||||
* of CPUID 0x40000000 + 3 = 0x40000003.
|
||||
* But safer: just check the max CPUID leaf for Hyper-V. */
|
||||
u32 hv_max_leaf = eax;
|
||||
if (hv_max_leaf >= 0x40000003) {
|
||||
/* CPUID 0x40000003 EBX bit 1 = AccessIntrCtrl (nested) */
|
||||
cpuid(0x40000003, &eax, &ebx, &ecx, &edx);
|
||||
if (ebx & (1U << 1)) {
|
||||
log_info("[VMX] Hyper-V nested virtualization supported");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
log_warn("[VMX] Hyper-V WITHOUT nested VMX support");
|
||||
log_warn("[VMX] CR4.VMXE / VMXON would cause fatal exit, skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* P8.3 Fix: Check for KVM ("KVMKVMKVM"). Match ebx+ecx only
|
||||
* (edx was wrongly 0x4D564B4D, should be 0x0000004D for "M\0\0\0"). */
|
||||
if (ebx == 0x4B4D564BU && ecx == 0x564B4D56U) {
|
||||
log_info("[VMX] KVM detected - nested VMX may be available");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* P8.3 Fix: Unknown hypervisor - try IA32_VMX_BASIC MSR. */
|
||||
{
|
||||
u64 vmx_basic = vmx_read_msr(IA32_VMX_BASIC);
|
||||
if (vmx_basic != 0) {
|
||||
log_info("[VMX] unknown hypervisor but IA32_VMX_BASIC non-zero, attempting");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
log_warn("[VMX] unknown hypervisor, IA32_VMX_BASIC=0, VMX not available");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_enable(void) {
|
||||
if (g_vmx_enabled) return 0;
|
||||
|
||||
if (!vmx_truly_available()) {
|
||||
log_warn("[VMX] VMX not truly available, skipping VMX init");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 读 IA32_VMX_BASIC,获取 VMCS revision ID 与 region 大小 */
|
||||
g_vmx_basic = vmx_read_msr(IA32_VMX_BASIC);
|
||||
g_vmcs_revision = g_vmx_basic & 0x7FFFFFFFULL;
|
||||
u32 vmcs_size = (u32)((g_vmx_basic >> 32) & 0x1FFF);
|
||||
if (vmcs_size == 0) vmcs_size = 4096;
|
||||
|
||||
log_hex64("[VMX] VMX_BASIC=", g_vmx_basic);
|
||||
log_hex64("[VMX] VMCS revision=", g_vmcs_revision);
|
||||
|
||||
/* 分配 VMXON region(4KB 对齐,物理页) */
|
||||
dkm_dma_buffer vmxon_buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &vmxon_buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMXON region");
|
||||
return -2;
|
||||
}
|
||||
g_vmxon_phys = vmxon_buf.phys;
|
||||
|
||||
/* 在 VMXON region 偏移 0 写入 VMCS revision ID */
|
||||
u32 *vmxon_ptr = (u32 *)vmxon_buf.virt;
|
||||
vmxon_ptr[0] = (u32)g_vmcs_revision;
|
||||
log_hex64("[VMX] VMXON phys=", g_vmxon_phys);
|
||||
|
||||
/* 设置 CR4.VMXE */
|
||||
u64 cr4 = vmx_required_cr4();
|
||||
write_cr4(cr4);
|
||||
log_hex64("[VMX] CR4=", cr4);
|
||||
|
||||
/* 确保 CR0 满足 VMX 要求(这里只读不写,UEFI 已配置好) */
|
||||
/* P8.3 Fix: ensure CR0 meets VMX requirements before VMXON */
|
||||
u64 cr0 = vmx_required_cr0();
|
||||
write_cr0(cr0);
|
||||
log_hex64("[VMX] CR0=", cr0);
|
||||
|
||||
/* 执行 vmxon */
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmxon %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(g_vmxon_phys)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
if (err) {
|
||||
/* 第二次尝试:读 vmx instruction error 通过 VMCS 不适用 vmxon;
|
||||
* 简化:返回失败。 */
|
||||
log_error("[VMX] vmxon failed");
|
||||
return -3;
|
||||
}
|
||||
|
||||
g_vmx_enabled = 1;
|
||||
log_info("[VMX] VMX root mode enabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_disable(void) {
|
||||
if (!g_vmx_enabled) return 0;
|
||||
__asm__ volatile("vmxoff" ::: "memory");
|
||||
g_vmx_enabled = 0;
|
||||
/* 清 CR4.VMXE */
|
||||
u64 cr4 = read_cr4();
|
||||
cr4 &= ~CR4_VMXE;
|
||||
write_cr4(cr4);
|
||||
log_info("[VMX] VMX root mode disabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_alloc(u64 *phys_out) {
|
||||
if (!phys_out) return -1;
|
||||
dkm_dma_buffer buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0x100000000ULL, &buf) != 0) {
|
||||
log_error("[VMX] failed to alloc VMCS region");
|
||||
return -2;
|
||||
}
|
||||
/* 偏移 0 写入 VMCS revision ID */
|
||||
u32 *p = (u32 *)buf.virt;
|
||||
p[0] = (u32)g_vmcs_revision;
|
||||
*phys_out = buf.phys;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmx_vmcs_load(u64 phys) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmptrld %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(phys)
|
||||
: "memory"
|
||||
);
|
||||
if (err) {
|
||||
log_error("[VMX] vmptrld failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* P8.4: VMCLEAR - put VMCS in "clear" state (required before VMLAUNCH). */
|
||||
int vmx_vmcs_clear(u64 phys) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmclear %1\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
: "m"(phys)
|
||||
: "memory"
|
||||
);
|
||||
if (err) {
|
||||
log_error("[VMX] vmclear failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 vmx_vmcs_read(u64 field) {
|
||||
u64 value;
|
||||
__asm__ volatile(
|
||||
"vmread %1, %0\n"
|
||||
: "=r"(value)
|
||||
: "r"(field)
|
||||
: "memory"
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
void vmx_vmcs_write(u64 field, u64 value) {
|
||||
__asm__ volatile(
|
||||
"vmwrite %1, %0\n"
|
||||
:: "r"(field), "r"(value)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
int vmx_vmlaunch(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmlaunch\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
int vmx_vmresume(void) {
|
||||
int err;
|
||||
__asm__ volatile(
|
||||
"vmresume\n"
|
||||
"jnc 1f\n"
|
||||
"jmp 2f\n"
|
||||
"1:\n"
|
||||
"mov $0, %0\n"
|
||||
"jmp 3f\n"
|
||||
"2:\n"
|
||||
"mov $1, %0\n"
|
||||
"3:\n"
|
||||
: "=r"(err)
|
||||
:: "memory"
|
||||
);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 暴露给其它模块用的辅助 */
|
||||
u64 vmx_get_host_cr3(void) { return read_cr3(); }
|
||||
u64 vmx_get_host_rflags(void) { return read_rflags(); }
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
/* xj380_boot.c — VMCS configuration and vmlaunch for OpenXJ380 guest.
|
||||
*
|
||||
* 参照 linux_boot.c 模式。XJ380 内核入口 KernelMain 是 C++ extern "C",
|
||||
* 以 SysV ABI 传 3 个引用参数(退化为指针):
|
||||
* RDI = &FrameBufferConfig (HHDM 虚拟地址)
|
||||
* RSI = &EFI_SYSTEM_TABLE (伪表,RuntimeServices=NULL,内核仅存指针)
|
||||
* RDX = &BOOT_CONFIG (HHDM 虚拟地址)
|
||||
* RIP = 0xFFFFFFFF80000000(guest 虚拟地址,由 guest 页表内核区映射)。
|
||||
*
|
||||
* guest 终止(异常/三重故障/0xCF9 重启)后本函数返回,host 恢复。
|
||||
*/
|
||||
|
||||
#include <utsm/xj380_loader.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* VM-Exit 汇编入口与 C 分发器(vmm.c / vmexit_asm.S) */
|
||||
extern void vmx_vm_exit_handler(void);
|
||||
extern int vmexit_dispatch(void);
|
||||
extern u64 vmexit_get_count(void);
|
||||
|
||||
extern u8 g_host_stack[8192];
|
||||
|
||||
/* 当前运行的 guest 是否为 XJ380(vmm.h 声明,vmexit.c handle_hlt 检查)。
|
||||
* 定义在本模块:XJ380 launch 置 1,guest 终止后清 0。 */
|
||||
volatile int g_xj380_guest_active;
|
||||
|
||||
/* CR 辅助 */
|
||||
static inline u64 read_cr0_local(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr0, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
static inline u64 read_cr3_local(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr3, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
static inline u64 read_cr4_local(void) {
|
||||
u64 v;
|
||||
__asm__ volatile("mov %%cr4, %0" : "=r"(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
static u64 vmx_adjust_control(u64 value, u32 msr) {
|
||||
u32 allowed0 = (u32)vmx_read_msr(msr);
|
||||
u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32);
|
||||
u32 adjusted = (u32)value | allowed0;
|
||||
adjusted &= allowed1;
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
/* MSR bitmap(4KB,全零 = 不拦截任何 MSR,与 Linux guest 一致) */
|
||||
static u8 g_xj380_msr_bitmap[4096] __attribute__((aligned(4096)));
|
||||
|
||||
/* ===== VMCS host state ===== */
|
||||
|
||||
static void xj380_vmcs_setup_host_state(void) {
|
||||
vmx_vmcs_write(VMCS_HOST_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG);
|
||||
vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local());
|
||||
vmx_vmcs_write(VMCS_HOST_CR4, read_cr4_local() | CR4_VMXE);
|
||||
|
||||
vmx_vmcs_write(VMCS_HOST_CS_SELECTOR, 0x08);
|
||||
vmx_vmcs_write(VMCS_HOST_SS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_HOST_DS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_HOST_ES_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_HOST_FS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_GS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_FS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_GS_BASE, 0);
|
||||
|
||||
{
|
||||
struct __attribute__((packed)) { u16 limit; u64 base; } gdtr, idtr;
|
||||
__asm__ volatile("sgdt %0" : "=m"(gdtr));
|
||||
__asm__ volatile("sidt %0" : "=m"(idtr));
|
||||
vmx_vmcs_write(VMCS_HOST_GDTR_BASE, gdtr.base);
|
||||
vmx_vmcs_write(VMCS_HOST_IDTR_BASE, idtr.base);
|
||||
|
||||
u16 tr_sel;
|
||||
u64 tr_base;
|
||||
__asm__ volatile("str %0" : "=r"(tr_sel));
|
||||
u64 gdt_base = gdtr.base;
|
||||
u64 *tss_desc = (u64 *)(gdt_base + (tr_sel & 0xFFF8));
|
||||
u64 low = tss_desc[0];
|
||||
u64 high = tss_desc[1];
|
||||
tr_base = (low >> 16) & 0xFFFFFF;
|
||||
tr_base |= ((low >> 32) & 0xFF) << 24;
|
||||
tr_base |= (high & 0xFFFFFFFF) << 32;
|
||||
vmx_vmcs_write(VMCS_HOST_TR_SELECTOR, tr_sel);
|
||||
vmx_vmcs_write(VMCS_HOST_TR_BASE, tr_base);
|
||||
}
|
||||
|
||||
{
|
||||
u64 sysenter_cs;
|
||||
__asm__ volatile("rdmsr" : "=a"(sysenter_cs) : "c"(0x174));
|
||||
vmx_vmcs_write(VMCS_HOST_SYSENTER_CS, sysenter_cs & 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_HOST_SYSENTER_ESP, 0);
|
||||
vmx_vmcs_write(VMCS_HOST_SYSENTER_EIP, 0);
|
||||
}
|
||||
|
||||
{
|
||||
u32 low, high;
|
||||
__asm__ volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(0xC0000080));
|
||||
u64 host_efer = ((u64)high << 32) | low;
|
||||
vmx_vmcs_write(VMCS_HOST_IA32_EFER, host_efer);
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_HOST_RSP, (u64)&g_host_stack[sizeof(g_host_stack) - 16]);
|
||||
vmx_vmcs_write(VMCS_HOST_RIP, (u64)vmx_vm_exit_handler);
|
||||
}
|
||||
|
||||
/* ===== VMCS guest state ===== */
|
||||
|
||||
static void xj380_vmcs_setup_guest_state(const struct xj380_guest_info *gi) {
|
||||
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_gpa);
|
||||
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_SELECTOR, 0x10);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_SELECTOR, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_SELECTOR, 0x18);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_BASE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_BASE, gi->gdt_gpa + 0x100);
|
||||
vmx_vmcs_write(VMCS_GUEST_GDTR_BASE, gi->gdt_gpa);
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_BASE, 0);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_LIMIT, 0xFFFFFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_LIMIT, 0xFFFF);
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_LIMIT, 0x67);
|
||||
vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28);
|
||||
vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); /* 64 位代码 L=1 */
|
||||
vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093);
|
||||
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82); /* unusable */
|
||||
vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */
|
||||
|
||||
/* guest RIP = XJ380 内核入口(guest 虚拟地址,经内核区页表映射) */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, gi->kernel_entry);
|
||||
vmx_vmcs_write(VMCS_GUEST_RSP, XJ380_BOOT_STACK_TOP);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_RFLAGS, 0x2); /* IF=0 */
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_IA32_EFER, EFER_LME | EFER_LMA | EFER_NXE);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_SYSENTER_CS, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_SYSENTER_ESP, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_SYSENTER_EIP, 0);
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_DR7, 0x400);
|
||||
vmx_vmcs_write(VMCS_GUEST_ACTIVITY_STATE, 0);
|
||||
vmx_vmcs_write(VMCS_GUEST_INTERRUPTIBILITY, 0);
|
||||
}
|
||||
|
||||
/* ===== VMCS controls ===== */
|
||||
|
||||
static void xj380_vmcs_setup_controls(u64 eptp) {
|
||||
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);
|
||||
|
||||
u64 cpu = CPU_BASED_HLT_EXITING
|
||||
| CPU_BASED_ACTIVATE_SECONDARY
|
||||
| CPU_BASED_USE_MSR_BITMAPS
|
||||
| CPU_BASED_UNCOND_IO_EXITING
|
||||
| CPU_BASED_INVLPG_EXITING;
|
||||
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
|
||||
|
||||
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
|
||||
{
|
||||
u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2);
|
||||
u32 allowed1 = (u32)(msr >> 32);
|
||||
cpu2 &= allowed1;
|
||||
cpu2 |= SEC_EXEC_ENABLE_EPT;
|
||||
}
|
||||
|
||||
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
|
||||
| VM_EXIT_HOST_ADDR_SPACE_SIZE
|
||||
| VM_EXIT_LOAD_HOST_EFER;
|
||||
exit_ctrl = vmx_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS);
|
||||
|
||||
u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS
|
||||
| VM_ENTRY_IA32E_MODE_GUEST
|
||||
| VM_ENTRY_LOAD_GUEST_EFER;
|
||||
entry_ctrl = vmx_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS);
|
||||
|
||||
vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin);
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu);
|
||||
vmx_vmcs_write(VMCS_SECONDARY_VM_EXEC_CONTROL, cpu2);
|
||||
vmx_vmcs_write(VMCS_VM_EXIT_CONTROLS, exit_ctrl);
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_CONTROLS, entry_ctrl);
|
||||
|
||||
{
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
u64 msr_bitmap_phys = (u64)g_xj380_msr_bitmap - hhdm;
|
||||
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
|
||||
vmx_vmcs_write(VMCS_VPID, 3); /* VPID 1=self-test, 2=Linux, 3=XJ380 */
|
||||
|
||||
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6) | (1ULL << 8));
|
||||
}
|
||||
|
||||
/* ===== 传给 vmlaunch asm 的参数 ===== */
|
||||
static volatile u64 g_xj380_rdi;
|
||||
static volatile u64 g_xj380_rsi;
|
||||
static volatile u64 g_xj380_rdx;
|
||||
|
||||
/* ===== XJ380 launch =====
|
||||
*
|
||||
* 前置条件:vmm_init() + xj380_loader_init()。
|
||||
* guest 终止后返回 0;vmlaunch 失败返回负值。 */
|
||||
int xj380_launch(void) {
|
||||
const struct xj380_guest_info *gi = xj380_get_guest_info();
|
||||
if (!gi || !gi->loaded) {
|
||||
log_error("[XJ380] guest not loaded");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!vmm_is_ready()) {
|
||||
log_error("[XJ380] VMM not ready");
|
||||
return -2;
|
||||
}
|
||||
|
||||
log_info("[XJ380] launch begin");
|
||||
|
||||
/* 真机 EPT TLB 刷新:vmlaunch 前确保 EPT 映射可见 */
|
||||
ept_flush_ept();
|
||||
|
||||
/* 重置 VM-Exit 状态 */
|
||||
g_guest_terminated = 0;
|
||||
g_last_exit_reason = 0xFFFFFFFFULL;
|
||||
g_linux_guest_active = 0; /* XJ380 是当前 active guest */
|
||||
g_guest_parked = 0;
|
||||
g_xj380_guest_active = 1;
|
||||
|
||||
/* 配置 VMCS */
|
||||
xj380_vmcs_setup_guest_state(gi);
|
||||
xj380_vmcs_setup_controls(vmm_get_eptp());
|
||||
xj380_vmcs_setup_host_state();
|
||||
|
||||
/* 初始 arm preemption timer(1ms 后首次周期 exit) */
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
|
||||
/* KernelMain 3 个 SysV 参数(HHDM 虚拟地址) */
|
||||
g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET;
|
||||
g_xj380_rsi = XJ380_FAKEEFI_GPA + XJ380_HHDM_OFFSET;
|
||||
g_xj380_rdx = XJ380_BOOTCFG_GPA + XJ380_HHDM_OFFSET;
|
||||
|
||||
log_hex64("[XJ380] RIP=", gi->kernel_entry);
|
||||
log_hex64("[XJ380] RSP=", XJ380_BOOT_STACK_TOP);
|
||||
log_hex64("[XJ380] RDI(fbc)=", g_xj380_rdi);
|
||||
log_hex64("[XJ380] RSI(efi)=", g_xj380_rsi);
|
||||
log_hex64("[XJ380] RDX(bootcfg)=", g_xj380_rdx);
|
||||
log_hex64("[XJ380] CR3=", gi->pgt_gpa);
|
||||
log_hex64("[XJ380] GDTR=", gi->gdt_gpa);
|
||||
log_info("[XJ380] vmlaunch (host suspended until guest terminates)");
|
||||
|
||||
int failed;
|
||||
__asm__ volatile(
|
||||
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t"
|
||||
"leaq 1f(%%rip), %%rax\n\t"
|
||||
"movq %%rax, g_saved_return_rip(%%rip)\n\t"
|
||||
"movq g_xj380_rdi(%%rip), %%rdi\n\t"
|
||||
"movq g_xj380_rsi(%%rip), %%rsi\n\t"
|
||||
"movq g_xj380_rdx(%%rip), %%rdx\n\t"
|
||||
"vmlaunch\n\t"
|
||||
/* ---- failure path ---- */
|
||||
"movl $1, %0\n\t"
|
||||
"jmp 2f\n\t"
|
||||
/* ---- post_guest: terminate path jmps here ---- */
|
||||
"1:\n\t"
|
||||
"movl $0, %0\n\t"
|
||||
"2:\n\t"
|
||||
: "=r"(failed)
|
||||
:: "rax", "rcx", "rdx", "rsi", "rdi",
|
||||
"r8", "r9", "r10", "r11", "memory"
|
||||
);
|
||||
|
||||
if (failed) {
|
||||
u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR);
|
||||
log_hex64("[XJ380] vmlaunch failed, error=", error);
|
||||
g_xj380_guest_active = 0;
|
||||
return -3;
|
||||
}
|
||||
|
||||
/* guest 终止,host 恢复 */
|
||||
g_xj380_guest_active = 0;
|
||||
log_info("[XJ380] guest terminated, host resumed");
|
||||
log_hex64("[XJ380] last exit reason=", g_last_exit_reason);
|
||||
log_hex64("[XJ380] vmexit count=", vmexit_get_count());
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
vmm/xj380_boot.o: vmm\xj380_boot.c include\utsm\xj380_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\vmm.h include\utsm\log.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\xj380_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\vmm.h:
|
||||
include\utsm\log.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
@@ -0,0 +1,712 @@
|
||||
/* xj380_loader.c — OpenXJ380 kernel.krl loader for UTSM VMX non-root guest.
|
||||
*
|
||||
* 参照 linux_loader.c 的既有模式实现 XJ380 第二 guest:
|
||||
* 1. 在 Limine boot module 中查找 kernel.krl(路径含 "xj380")
|
||||
* 2. 解析 ELF64 PT_LOAD 段(固定链接 0xFFFFFFFF80000000)
|
||||
* 3. 分配 host 物理内存:boot area / framebuffer / kernel / RAM
|
||||
* 4. 构造 guest 初始页表(恒等 0-4GB + HHDM + 内核区三区布局)
|
||||
* 5. 构造 guest GDT(64 位 code/data/TSS)
|
||||
* 6. 构造 ACPI 桩表 MADT/FADT/MCFG/HPET(幽灵设备地址,EPT 按需分配)
|
||||
* 7. 构造 EFI 格式内存图(单一 Conventional 区 = guest RAM)
|
||||
* 8. 构造 BOOT_CONFIG / FrameBufferConfig / 伪 EFI_SYSTEM_TABLE
|
||||
* 9. EPT 映射所有 guest 区域
|
||||
*
|
||||
* VMCS 配置与 vmlaunch 见 xj380_boot.c。
|
||||
*/
|
||||
|
||||
#include <utsm/xj380_loader.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_module_request g_module_request;
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
/* ===== 运行期辅助 ===== */
|
||||
|
||||
static void mem_copy(void *dst, const void *src, u64 n) {
|
||||
u8 *d = (u8 *)dst;
|
||||
const u8 *s = (const u8 *)src;
|
||||
for (u64 i = 0; i < n; i++) d[i] = s[i];
|
||||
}
|
||||
|
||||
static void mem_zero(void *dst, u64 n) {
|
||||
u8 *d = (u8 *)dst;
|
||||
for (u64 i = 0; i < n; i++) d[i] = 0;
|
||||
}
|
||||
|
||||
static u64 hhdm(void) {
|
||||
return g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
}
|
||||
|
||||
static void *to_virt(u64 hpa) {
|
||||
return (void *)(hpa + hhdm());
|
||||
}
|
||||
|
||||
/* ===== ELF64 解析(kernel.krl 为 ET_EXEC) ===== */
|
||||
|
||||
#define ELF_MAGIC 0x464C457FULL /* "\x7fELF" */
|
||||
#define ELF_ET_EXEC 2
|
||||
#define ELF_PT_LOAD 1
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry;
|
||||
u64 e_phoff;
|
||||
u64 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
} xj_elf64_ehdr;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u32 p_type;
|
||||
u32 p_flags;
|
||||
u64 p_offset;
|
||||
u64 p_vaddr;
|
||||
u64 p_paddr;
|
||||
u64 p_filesz;
|
||||
u64 p_memsz;
|
||||
u64 p_align;
|
||||
} xj_elf64_phdr;
|
||||
|
||||
/* 解析 ELF:校验头、遍历 PT_LOAD 计算内核跨度,记录入口。
|
||||
* 返回 0 成功;负值失败。 */
|
||||
static int xj380_parse_elf(const void *img, u64 size,
|
||||
u64 *entry_out, u64 *span_out) {
|
||||
if (size < sizeof(xj_elf64_ehdr)) {
|
||||
log_error("[XJ380] kernel.krl too small");
|
||||
return -1;
|
||||
}
|
||||
const xj_elf64_ehdr *eh = (const xj_elf64_ehdr *)img;
|
||||
if (*(u32 *)eh->e_ident != (u32)ELF_MAGIC) {
|
||||
log_error("[XJ380] bad ELF magic");
|
||||
return -2;
|
||||
}
|
||||
if (eh->e_ident[4] != 2) { /* ELFCLASS64 */
|
||||
log_error("[XJ380] not a 64-bit ELF");
|
||||
return -3;
|
||||
}
|
||||
if (eh->e_type != ELF_ET_EXEC) {
|
||||
log_error("[XJ380] kernel.krl not ET_EXEC");
|
||||
return -4;
|
||||
}
|
||||
if (eh->e_phnum == 0 || eh->e_phentsize != sizeof(xj_elf64_phdr) ||
|
||||
eh->e_phoff + (u64)eh->e_phnum * sizeof(xj_elf64_phdr) > size) {
|
||||
log_error("[XJ380] bad program header table");
|
||||
return -5;
|
||||
}
|
||||
|
||||
u64 span = 0;
|
||||
const xj_elf64_phdr *ph = (const xj_elf64_phdr *)((const u8 *)img + eh->e_phoff);
|
||||
for (u16 i = 0; i < eh->e_phnum; i++) {
|
||||
if (ph[i].p_type != ELF_PT_LOAD) continue;
|
||||
if (ph[i].p_vaddr < XJ380_KERNEL_VADDR) {
|
||||
log_error("[XJ380] PT_LOAD below kernel base");
|
||||
log_hex64("[XJ380] vaddr=", ph[i].p_vaddr);
|
||||
return -6;
|
||||
}
|
||||
u64 off = ph[i].p_vaddr - XJ380_KERNEL_VADDR;
|
||||
if (off + ph[i].p_memsz > XJ380_GUEST_KERNEL_MAX) {
|
||||
log_error("[XJ380] kernel exceeds reserved window (32MB)");
|
||||
return -7;
|
||||
}
|
||||
if (ph[i].p_offset + ph[i].p_filesz > size) {
|
||||
log_error("[XJ380] PT_LOAD filesz beyond image");
|
||||
return -8;
|
||||
}
|
||||
if (off + ph[i].p_memsz > span) span = off + ph[i].p_memsz;
|
||||
}
|
||||
if (span == 0) {
|
||||
log_error("[XJ380] no PT_LOAD segments");
|
||||
return -9;
|
||||
}
|
||||
|
||||
if (entry_out) *entry_out = eh->e_entry;
|
||||
if (span_out) *span_out = span;
|
||||
log_hex64("[XJ380] ELF entry=", eh->e_entry);
|
||||
log_hex64("[XJ380] kernel span=", span);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Guest 状态 ===== */
|
||||
|
||||
static struct xj380_guest_info g_guest;
|
||||
|
||||
/* ===== Guest 页表(三区布局,同 XJ380 引导器契约) =====
|
||||
*
|
||||
* PML4 @ pgt_gpa + 0x0000
|
||||
* [0] → PDPT0 恒等映射 0-4GB(早期直读 MADT 等,内核自清低半)
|
||||
* [256] → PDPT0 HHDM 映射 0xFFFF800000000000 + [0,4GB)
|
||||
* [511] → PDPT_K 内核区 0xFFFFFF8000000000+
|
||||
* PDPT0 @ pgt_gpa + 0x1000,[0..3] → PD0..PD3(每页覆盖 1GB,2MB 大页)
|
||||
* PD0-3 @ pgt_gpa + 0x2000..0x5000
|
||||
* PDPT_K@ pgt_gpa + 0x6000,[480] → PD_K(0xFFFFFFFF80000000 区间)
|
||||
* PD_K @ pgt_gpa + 0x7000,[0..n] 2MB 大页映射内核 GPA
|
||||
*/
|
||||
#define XJ_PGT_PDPT0_OFF 0x1000
|
||||
#define XJ_PGT_PD0_OFF 0x2000
|
||||
#define XJ_PGT_PDPTK_OFF 0x6000
|
||||
#define XJ_PGT_PDK_OFF 0x7000
|
||||
|
||||
/* x86-64 页表项位 */
|
||||
#define XJ_PTE_P (1ULL << 0)
|
||||
#define XJ_PTE_RW (1ULL << 1)
|
||||
#define XJ_PTE_PS (1ULL << 7)
|
||||
|
||||
static int xj380_build_page_tables(u64 pgt_hpa, u64 kernel_gpa, u64 kernel_span) {
|
||||
u64 *pml4 = (u64 *)to_virt(pgt_hpa);
|
||||
mem_zero(pml4, XJ380_PGT_SIZE);
|
||||
|
||||
u64 pdpt0_gpa = g_guest.pgt_gpa + XJ_PGT_PDPT0_OFF;
|
||||
u64 pdptk_gpa = g_guest.pgt_gpa + XJ_PGT_PDPTK_OFF;
|
||||
|
||||
/* PML4[0] 恒等 + PML4[256] HHDM 指向同一个 PDPT0 */
|
||||
pml4[0] = pdpt0_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
pml4[256] = pdpt0_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
/* PML4[511] → PDPT_K */
|
||||
pml4[511] = pdptk_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
|
||||
/* PDPT0[0..3] → PD0..PD3(覆盖 0-4GB) */
|
||||
u64 *pdpt0 = (u64 *)((u8 *)pml4 + XJ_PGT_PDPT0_OFF);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
u64 pd_gpa = g_guest.pgt_gpa + XJ_PGT_PD0_OFF + (u64)i * 4096;
|
||||
pdpt0[i] = pd_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
}
|
||||
/* 每个 PD 512 项,每项 2MB 大页:GPA = i*1GB + j*2MB */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
u64 *pd = (u64 *)((u8 *)pml4 + XJ_PGT_PD0_OFF + (u64)i * 4096);
|
||||
for (int j = 0; j < 512; j++) {
|
||||
u64 page_gpa = (u64)i * (1ULL << 30) + (u64)j * (2ULL << 20);
|
||||
pd[j] = page_gpa | XJ_PTE_P | XJ_PTE_RW | XJ_PTE_PS;
|
||||
}
|
||||
}
|
||||
|
||||
/* PDPT_K[480] → PD_K(虚拟 0xFFFFFFFF80000000 落在 PML4[511] 第 480 个 1GB) */
|
||||
u64 *pdptk = (u64 *)((u8 *)pml4 + XJ_PGT_PDPTK_OFF);
|
||||
u64 pdk_gpa = g_guest.pgt_gpa + XJ_PGT_PDK_OFF;
|
||||
pdptk[480] = pdk_gpa | XJ_PTE_P | XJ_PTE_RW;
|
||||
|
||||
/* PD_K[i]:虚拟 0xFFFFFFFF80000000 + i*2MB → GPA kernel_gpa + i*2MB */
|
||||
u64 *pdk = (u64 *)((u8 *)pml4 + XJ_PGT_PDK_OFF);
|
||||
u64 pages = (kernel_span + (2ULL << 20) - 1) >> 21;
|
||||
for (u64 i = 0; i < pages; i++) {
|
||||
u64 gpa = kernel_gpa + (i << 21);
|
||||
pdk[i] = gpa | XJ_PTE_P | XJ_PTE_RW | XJ_PTE_PS;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Guest GDT(与 linux_loader 相同的 64 位布局) ===== */
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u16 limit_low;
|
||||
u16 base_low;
|
||||
u8 base_mid;
|
||||
u8 access;
|
||||
u8 flags_limit_high;
|
||||
u8 base_high;
|
||||
} xj_gdt_entry64;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u32 limit_low_base_low;
|
||||
u8 base_mid;
|
||||
u8 access;
|
||||
u8 flags_limit_high;
|
||||
u8 base_high;
|
||||
u32 base_upper;
|
||||
u32 reserved;
|
||||
} xj_tss_desc64;
|
||||
|
||||
static int xj380_build_gdt(u64 gdt_hpa) {
|
||||
u8 *gdt_page = (u8 *)to_virt(gdt_hpa);
|
||||
mem_zero(gdt_page, 4096);
|
||||
|
||||
xj_gdt_entry64 *gdt = (xj_gdt_entry64 *)gdt_page;
|
||||
gdt[1].limit_low = 0xFFFF;
|
||||
gdt[1].access = 0x9B;
|
||||
gdt[1].flags_limit_high = 0xAF; /* G=1, L=1(64 位代码) */
|
||||
|
||||
gdt[2].limit_low = 0xFFFF;
|
||||
gdt[2].access = 0x93;
|
||||
gdt[2].flags_limit_high = 0xCF;
|
||||
|
||||
/* [0x18] 64 位 TSS 描述符,TSS 本体在同页 +0x100 */
|
||||
xj_tss_desc64 *tss_d = (xj_tss_desc64 *)&gdt_page[0x18];
|
||||
u64 tss_base = g_guest.gdt_gpa + 0x100;
|
||||
u32 tss_limit = 0x67;
|
||||
tss_d->limit_low_base_low = (tss_limit & 0xFFFF) | ((u32)(tss_base & 0xFFFFFF) << 16);
|
||||
tss_d->base_mid = (u8)((tss_base >> 24) & 0xFF);
|
||||
tss_d->access = 0x89;
|
||||
tss_d->flags_limit_high = (u8)((tss_limit >> 16) & 0x0F);
|
||||
tss_d->base_high = 0;
|
||||
tss_d->base_upper = 0;
|
||||
tss_d->reserved = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== ACPI 桩表 =====
|
||||
*
|
||||
* XJ380 初始化需要 MADT(APIC/LAPIC/IOAPIC)、FADT(未使用)、MCFG(PCIe)、
|
||||
* HPET(时钟)。所有设备地址均为"幽灵地址":EPT 未映射该区域时由
|
||||
* handle_ept_violation 按需分配真实页,guest 读写不崩溃。
|
||||
* LAPIC/IOAPIC/HPET/MMCONF 均落在低 4GB,恒等 + HHDM 双路可见。
|
||||
*/
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char signature[4];
|
||||
u32 length;
|
||||
u8 revision;
|
||||
u8 checksum;
|
||||
char oemid[6];
|
||||
char oemtableid[8];
|
||||
u32 oemrev;
|
||||
u32 creatorid;
|
||||
u32 creatorrev;
|
||||
} xj_acpi_header; /* 36 字节 */
|
||||
|
||||
static void acpi_set_header(xj_acpi_header *h, const char sig[4], u32 len) {
|
||||
mem_zero(h, len);
|
||||
h->signature[0] = sig[0];
|
||||
h->signature[1] = sig[1];
|
||||
h->signature[2] = sig[2];
|
||||
h->signature[3] = sig[3];
|
||||
h->length = len;
|
||||
h->revision = 1;
|
||||
mem_copy(h->oemid, "DESHAB", 6);
|
||||
mem_copy(h->oemtableid, "UTSMVMM", 8);
|
||||
h->oemrev = 1;
|
||||
mem_copy((char *)&h->creatorid, "UTSM", 4);
|
||||
h->creatorrev = 1;
|
||||
/* 校验和 = -sum(校验和字节置 0 后求和) */
|
||||
u32 sum = 0;
|
||||
u8 *p = (u8 *)h;
|
||||
for (u32 i = 0; i < len; i++) sum += p[i];
|
||||
h->checksum = (u8)(0 - (u8)sum);
|
||||
}
|
||||
|
||||
/* MADT:36 头 + LAPIC 地址(4) + 标志(4) + LAPIC 项(8) + IOAPIC 项(12) = 64 字节。
|
||||
* LAPIC 项:type=0, len=8, uid=0, id=0, flags=1(仅 enabled,不触发 AP 启动)
|
||||
* IOAPIC 项:type=1, len=12, id=0, addr=0xFEC00000(幽灵), gsib=0 */
|
||||
static int xj380_build_madt(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "APIC", 64);
|
||||
u8 *p = t + 36;
|
||||
p[0] = 0x00; p[1] = 0x00; p[2] = 0x00; p[3] = 0xF0; /* lapic addr = 0xFEE00000 */
|
||||
p[4] = 0x01; p[5] = 0x00; p[6] = 0x00; p[7] = 0x00; /* flags = PCAT_COMPAT */
|
||||
p += 8;
|
||||
p[0] = 0x00; /* type = LAPIC */
|
||||
p[1] = 0x08; /* len */
|
||||
p[2] = 0x00; /* ACPI processor uid */
|
||||
p[3] = 0x00; /* lapic id */
|
||||
p[4] = 0x01; p[5] = 0x00; p[6] = 0x00; p[7] = 0x00; /* flags = enabled only */
|
||||
p += 8;
|
||||
p[0] = 0x01; /* type = IOAPIC */
|
||||
p[1] = 0x0C; /* len */
|
||||
p[2] = 0x00; /* ioapic id */
|
||||
p[3] = 0x00; /* reserved */
|
||||
p[4] = 0x00; p[5] = 0x00; p[6] = 0xC0; p[7] = 0xFE; /* ioapic addr = 0xFEC00000 */
|
||||
p[8] = 0x00; p[9] = 0x00; p[10] = 0x00; p[11] = 0x00; /* gsib */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FADT:最小桩表(XJ380 仅在未接线的 power_shutdown 路径使用,启动不读) */
|
||||
static int xj380_build_fadt(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "FACP", 96);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* MCFG:36 头 + reserved(8) + 1 项(16) = 60 字节。
|
||||
* base=0xF0000000(幽灵 MMCONF),1 个 segment、1 条总线(扫描结果为无设备) */
|
||||
static int xj380_build_mcfg(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "MCFG", 60);
|
||||
u8 *p = t + 36 + 8;
|
||||
p[0] = 0x00; p[1] = 0x00; p[2] = 0x00; p[3] = 0x00;
|
||||
p[4] = 0x00; p[5] = 0x00; p[6] = 0xF0; p[7] = 0x0F; /* base = 0xF0000000 */
|
||||
p[8] = 0x00; p[9] = 0x00; /* segment group 0 */
|
||||
p[10] = 0x00; /* start bus 0 */
|
||||
p[11] = 0x00; /* end bus 0 */
|
||||
p[12] = 0x00; p[13] = 0x00; p[14] = 0x00; p[15] = 0x00; /* reserved */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* HPET:36 头 + event_block_id(4) + GAS(12) + tick_unit(2) + 属性(1) = 55 → 56。
|
||||
* base=0xFED00000(幽灵),generalCapabilities 读 0 → hpetPeriod=0,不挂起 */
|
||||
static int xj380_build_hpet(u64 hpa) {
|
||||
u8 *t = (u8 *)to_virt(hpa);
|
||||
acpi_set_header((xj_acpi_header *)t, "HPET", 56);
|
||||
u8 *p = t + 36;
|
||||
p[0] = 0x00; p[1] = 0x00; p[2] = 0x00; p[3] = 0x00; /* event_block_id */
|
||||
p[4] = 0x00; /* address_space = 系统内存 */
|
||||
p[5] = 0x20; /* bit_width = 64 */
|
||||
p[6] = 0x00; /* bit_offset */
|
||||
p[7] = 0x00; /* access_size */
|
||||
p[8] = 0x00; p[9] = 0x00; p[10] = 0x00; p[11] = 0x00;
|
||||
p[12] = 0x00; p[13] = 0xD0; p[14] = 0xFE; p[15] = 0x0F; /* base = 0xFED00000 */
|
||||
p[16] = 0x00; p[17] = 0x00; /* clock_tick_unit */
|
||||
p[18] = 0x00; /* page_oem_flags */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== EFI 格式内存图 =====
|
||||
*
|
||||
* 仅一个 Conventional 区(guest RAM),其余区域隐式保留。
|
||||
* EfiMemoryDesc:Type(4)+pad(4)+PhysicalStart(8)+VirtualStart(8)+
|
||||
* NumberOfPages(8)+Attribute(8),16 字节对齐 → 48 字节。
|
||||
*/
|
||||
typedef struct __attribute__((aligned(16))) {
|
||||
u32 type;
|
||||
u32 pad;
|
||||
u64 physical_start;
|
||||
u64 virtual_start;
|
||||
u64 number_of_pages;
|
||||
u64 attribute;
|
||||
} xj_efi_memdesc;
|
||||
|
||||
#define XJ_EFI_CONVENTIONAL 7
|
||||
#define XJ_EFI_DESC_SIZE (u64)sizeof(xj_efi_memdesc)
|
||||
#define XJ_EFI_MEM_ATTR 0xFULL
|
||||
|
||||
static int xj380_build_memmap(u64 hpa) {
|
||||
xj_efi_memdesc *d = (xj_efi_memdesc *)to_virt(hpa);
|
||||
mem_zero(d, sizeof(*d));
|
||||
d->type = XJ_EFI_CONVENTIONAL;
|
||||
d->physical_start = g_guest.ram_gpa;
|
||||
d->virtual_start = 0;
|
||||
d->number_of_pages = g_guest.ram_size / 4096;
|
||||
d->attribute = XJ_EFI_MEM_ATTR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== BOOT_CONFIG(布局必须与 OpenXJ380 boot/include/boot.h 一致) =====
|
||||
*
|
||||
* MEMORY_MAP 在 memory.h 中 #pragma pack(1):36 字节;BOOT_CONFIG 默认对齐。
|
||||
* 关键:MemoryMap.Buffer / saved_mtrrs 是【物理地址】(内核自行 +HHDM);
|
||||
* temp_stack[] 是【HHDM 虚拟地址】数组(仅拷贝,1 CPU 不 deref)。
|
||||
*/
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
u64 map_size;
|
||||
u64 buffer; /* 物理 GPA */
|
||||
u64 map_key;
|
||||
u64 descriptor_size;
|
||||
u32 descriptor_version;
|
||||
} xj_memory_map; /* 36 字节 */
|
||||
|
||||
typedef struct {
|
||||
xj_memory_map memory_map;
|
||||
u64 fadt;
|
||||
u64 madt;
|
||||
u64 hpet;
|
||||
u64 mcfg;
|
||||
u64 *saved_mtrrs; /* 物理 GPA,内核 +HHDM */
|
||||
u64 *temp_stack[256]; /* HHDM 虚拟地址 */
|
||||
int is_qemu;
|
||||
u64 boot_flags;
|
||||
u64 installer_root_pak;
|
||||
u64 installer_root_pak_size;
|
||||
u64 system_payload_pak;
|
||||
u64 system_payload_pak_size;
|
||||
} xj_boot_config;
|
||||
|
||||
_Static_assert(sizeof(xj_memory_map) == 36, "xj_memory_map layout mismatch");
|
||||
_Static_assert(sizeof(xj_boot_config) == 2176, "xj_boot_config layout mismatch");
|
||||
|
||||
/* FrameBufferConfig(boot/include/fbc.h,默认对齐,24 字节) */
|
||||
typedef struct {
|
||||
u8 *frame_buffer; /* HHDM 虚拟地址 */
|
||||
u32 pixels_per_scan_line;
|
||||
u32 horizontal_resolution;
|
||||
u32 vertical_resolution;
|
||||
u32 pixel_format; /* 0=kRGBR, 1=kBGRR */
|
||||
} xj_framebuffer_config;
|
||||
_Static_assert(sizeof(xj_framebuffer_config) == 24, "fbc layout mismatch");
|
||||
|
||||
static int xj380_build_boot_config(u64 hpa, u64 mtrr_hpa, u64 stack_gpa, u64 fbc_gpa) {
|
||||
xj_boot_config *bc = (xj_boot_config *)to_virt(hpa);
|
||||
mem_zero(bc, sizeof(*bc));
|
||||
|
||||
bc->memory_map.map_size = XJ_EFI_DESC_SIZE; /* 单描述符 */
|
||||
bc->memory_map.buffer = XJ380_MEMMAP_GPA; /* 物理 GPA */
|
||||
bc->memory_map.map_key = 0;
|
||||
bc->memory_map.descriptor_size = XJ_EFI_DESC_SIZE;
|
||||
bc->memory_map.descriptor_version = 1;
|
||||
|
||||
bc->fadt = XJ380_FADT_GPA;
|
||||
bc->madt = XJ380_MADT_GPA;
|
||||
bc->hpet = XJ380_HPET_GPA;
|
||||
bc->mcfg = XJ380_MCFG_GPA;
|
||||
|
||||
bc->saved_mtrrs = (u64 *)mtrr_hpa; /* 物理 GPA */
|
||||
|
||||
u64 stack_hhdm = stack_gpa + XJ380_HHDM_OFFSET;
|
||||
for (int i = 0; i < 256; i++) bc->temp_stack[i] = (u64 *)stack_hhdm;
|
||||
|
||||
bc->is_qemu = 1;
|
||||
bc->boot_flags = XJ380_BOOT_FLAG_DISABLE_KMOD | XJ380_BOOT_FLAG_SAFE_STORAGE_IO;
|
||||
/* installer paks 均为 0(安装器模式关闭) */
|
||||
|
||||
/* FrameBufferConfig:frame_buffer 传 HHDM 虚拟地址,pixel_format=1(kBGRR) */
|
||||
xj_framebuffer_config *fbc = (xj_framebuffer_config *)to_virt(fbc_gpa);
|
||||
mem_zero(fbc, sizeof(*fbc));
|
||||
fbc->frame_buffer = (u8 *)(XJ380_GUEST_FB_GPA + XJ380_HHDM_OFFSET);
|
||||
fbc->pixels_per_scan_line = XJ380_FB_WIDTH;
|
||||
fbc->horizontal_resolution = XJ380_FB_WIDTH;
|
||||
fbc->vertical_resolution = XJ380_FB_HEIGHT;
|
||||
fbc->pixel_format = 1; /* kBGRR */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Module finder ===== */
|
||||
|
||||
void *xj380_find_kernel_module(u64 *size_out) {
|
||||
if (!g_module_request.response) {
|
||||
log_error("[XJ380] no Limine module response");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
/* 路径含 "xj380"(大小写不敏感) */
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
if ((s[0] == 'x' || s[0] == 'X') &&
|
||||
(s[1] == 'j' || s[1] == 'J') &&
|
||||
(s[2] == '3' || s[2] == '3') &&
|
||||
(s[3] == '8' || s[3] == '8') &&
|
||||
(s[4] == '0' || s[4] == '0')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[XJ380] found kernel module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[XJ380] module size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_warn("[XJ380] no kernel.krl module found");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== 主加载入口 ===== */
|
||||
|
||||
int xj380_loader_init(void) {
|
||||
log_info("[XJ380] loader init begin");
|
||||
|
||||
/* 1. 查找 kernel.krl 模块 */
|
||||
u64 krl_size = 0;
|
||||
void *krl = xj380_find_kernel_module(&krl_size);
|
||||
if (!krl) {
|
||||
log_warn("[XJ380] no kernel.krl found, skipping XJ380 guest");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 2. 解析 ELF */
|
||||
u64 entry, span;
|
||||
if (xj380_parse_elf(krl, krl_size, &entry, &span) != 0) {
|
||||
log_error("[XJ380] kernel.krl parse failed");
|
||||
return -2;
|
||||
}
|
||||
if (entry < XJ380_KERNEL_VADDR || entry >= XJ380_KERNEL_VADDR + span) {
|
||||
log_error("[XJ380] entry outside kernel window");
|
||||
return -3;
|
||||
}
|
||||
|
||||
/* 3. 分配 boot area 缓冲(按页) */
|
||||
dkm_dma_buffer pgt_buf, gdt_buf, madt_buf, fadt_buf, mcfg_buf, hpet_buf;
|
||||
dkm_dma_buffer memmap_buf, bootcfg_buf, mtrr_buf, stack_buf, fakeefi_buf, fbc_buf;
|
||||
if (dma_alloc_pages(8, 4096, 0, &pgt_buf) != 0) { log_error("[XJ380] alloc pgt failed"); return -4; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &gdt_buf) != 0) { log_error("[XJ380] alloc gdt failed"); return -5; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &madt_buf) != 0) { log_error("[XJ380] alloc madt failed"); return -6; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &fadt_buf) != 0) { log_error("[XJ380] alloc fadt failed"); return -7; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &mcfg_buf) != 0) { log_error("[XJ380] alloc mcfg failed"); return -8; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &hpet_buf) != 0) { log_error("[XJ380] alloc hpet failed"); return -9; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &memmap_buf) != 0) { log_error("[XJ380] alloc memmap failed"); return -10; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &bootcfg_buf) != 0) { log_error("[XJ380] alloc bootcfg failed"); return -11; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &mtrr_buf) != 0) { log_error("[XJ380] alloc mtrr failed"); return -12; }
|
||||
if (dma_alloc_pages(2, 4096, 0, &stack_buf) != 0) { log_error("[XJ380] alloc stack failed"); return -13; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &fakeefi_buf) != 0) { log_error("[XJ380] alloc fake efi failed"); return -14; }
|
||||
if (dma_alloc_pages(1, 4096, 0, &fbc_buf) != 0) { log_error("[XJ380] alloc fbc failed"); return -15; }
|
||||
|
||||
/* 4. 分配内核缓冲并装入 PT_LOAD 段 */
|
||||
u64 kernel_alloc = (span + 4095) & ~4095ULL;
|
||||
dkm_dma_buffer kernel_buf;
|
||||
if (dma_alloc_pages(kernel_alloc / 4096, 2 * 1024 * 1024, 0, &kernel_buf) != 0) {
|
||||
log_error("[XJ380] failed to alloc kernel memory");
|
||||
return -16;
|
||||
}
|
||||
{
|
||||
const xj_elf64_ehdr *eh = (const xj_elf64_ehdr *)krl;
|
||||
const xj_elf64_phdr *ph = (const xj_elf64_phdr *)((const u8 *)krl + eh->e_phoff);
|
||||
u8 *dst = (u8 *)kernel_buf.virt;
|
||||
for (u16 i = 0; i < eh->e_phnum; i++) {
|
||||
if (ph[i].p_type != ELF_PT_LOAD) continue;
|
||||
u64 off = ph[i].p_vaddr - XJ380_KERNEL_VADDR;
|
||||
mem_copy(dst + off, (const u8 *)krl + ph[i].p_offset, ph[i].p_filesz);
|
||||
if (ph[i].p_memsz > ph[i].p_filesz)
|
||||
mem_zero(dst + off + ph[i].p_filesz, ph[i].p_memsz - ph[i].p_filesz);
|
||||
}
|
||||
log_hex64("[XJ380] kernel loaded HPA=", kernel_buf.phys);
|
||||
}
|
||||
|
||||
/* 5. 分配 framebuffer 并清零 */
|
||||
dkm_dma_buffer fb_buf;
|
||||
if (dma_alloc_pages((XJ380_FB_SIZE + 4095) / 4096, 4096, 0, &fb_buf) != 0) {
|
||||
log_error("[XJ380] failed to alloc framebuffer");
|
||||
return -17;
|
||||
}
|
||||
{
|
||||
u8 *fb = (u8 *)fb_buf.virt;
|
||||
for (u64 i = 0; i < XJ380_FB_SIZE; i++) fb[i] = 0;
|
||||
}
|
||||
|
||||
/* 6. 分配 guest RAM(1GB,降级 512MB/256MB) */
|
||||
u64 ram_size = XJ380_GUEST_RAM_SIZE;
|
||||
dkm_dma_buffer ram_buf;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_warn("[XJ380] failed to alloc guest RAM (1GB), trying 512MB");
|
||||
ram_size = 512 * 1024 * 1024;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_warn("[XJ380] failed to alloc guest RAM (512MB), trying 256MB");
|
||||
ram_size = XJ380_GUEST_RAM_MIN;
|
||||
if (dma_alloc_pages(ram_size / 4096, 2 * 1024 * 1024, 0, &ram_buf) != 0) {
|
||||
log_error("[XJ380] failed to alloc guest RAM (256MB minimum)");
|
||||
return -18;
|
||||
}
|
||||
}
|
||||
}
|
||||
log_hex64("[XJ380] guest RAM HPA=", ram_buf.phys);
|
||||
log_hex64("[XJ380] guest RAM size=", ram_size);
|
||||
|
||||
/* 7. 记录 guest 信息 */
|
||||
g_guest.pgt_gpa = XJ380_PGT_GPA;
|
||||
g_guest.gdt_gpa = XJ380_GDT_GPA;
|
||||
g_guest.kernel_gpa = XJ380_GUEST_KERNEL_GPA;
|
||||
g_guest.kernel_entry = entry;
|
||||
g_guest.kernel_size = span;
|
||||
g_guest.ram_gpa = XJ380_GUEST_RAM_GPA;
|
||||
g_guest.ram_size = ram_size;
|
||||
g_guest.fb_gpa = XJ380_GUEST_FB_GPA;
|
||||
|
||||
/* 8. 构造 guest 页表 / GDT */
|
||||
xj380_build_page_tables(pgt_buf.phys, g_guest.kernel_gpa, span);
|
||||
xj380_build_gdt(gdt_buf.phys);
|
||||
log_hex64("[XJ380] page tables HPA=", pgt_buf.phys);
|
||||
log_hex64("[XJ380] GDT HPA=", gdt_buf.phys);
|
||||
|
||||
/* 9. 构造 ACPI 桩表 */
|
||||
xj380_build_madt(madt_buf.phys);
|
||||
xj380_build_fadt(fadt_buf.phys);
|
||||
xj380_build_mcfg(mcfg_buf.phys);
|
||||
xj380_build_hpet(hpet_buf.phys);
|
||||
|
||||
/* 10. 构造内存图 + BOOT_CONFIG(含 MTRR 缓冲清零、伪 EFI 表清零) */
|
||||
xj380_build_memmap(memmap_buf.phys);
|
||||
mem_zero(mtrr_buf.virt, 4096);
|
||||
mem_zero(fakeefi_buf.virt, 4096);
|
||||
xj380_build_boot_config(bootcfg_buf.phys, mtrr_buf.phys, XJ380_STACK_GPA, fbc_buf.phys);
|
||||
|
||||
/* 11. EPT 映射所有 guest 区域。
|
||||
* boot area 各缓冲独立分配,逐个按自身 GPA 映射。 */
|
||||
if (ept_map_range(XJ380_PGT_GPA, pgt_buf.phys, XJ380_PGT_SIZE, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map page tables failed");
|
||||
return -19;
|
||||
}
|
||||
if (ept_map_range(XJ380_GDT_GPA, gdt_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map GDT failed");
|
||||
return -20;
|
||||
}
|
||||
if (ept_map_range(XJ380_MADT_GPA, madt_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map MADT failed");
|
||||
return -21;
|
||||
}
|
||||
if (ept_map_range(XJ380_FADT_GPA, fadt_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map FADT failed");
|
||||
return -22;
|
||||
}
|
||||
if (ept_map_range(XJ380_MCFG_GPA, mcfg_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map MCFG failed");
|
||||
return -23;
|
||||
}
|
||||
if (ept_map_range(XJ380_HPET_GPA, hpet_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map HPET failed");
|
||||
return -24;
|
||||
}
|
||||
if (ept_map_range(XJ380_MEMMAP_GPA, memmap_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map memory map failed");
|
||||
return -25;
|
||||
}
|
||||
if (ept_map_range(XJ380_BOOTCFG_GPA, bootcfg_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map BOOT_CONFIG failed");
|
||||
return -26;
|
||||
}
|
||||
if (ept_map_range(XJ380_MTRR_GPA, mtrr_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map MTRR buffer failed");
|
||||
return -27;
|
||||
}
|
||||
if (ept_map_range(XJ380_STACK_GPA, stack_buf.phys, 8192, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map stack failed");
|
||||
return -28;
|
||||
}
|
||||
if (ept_map_range(XJ380_FAKEEFI_GPA, fakeefi_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map fake EFI table failed");
|
||||
return -29;
|
||||
}
|
||||
if (ept_map_range(XJ380_FBC_GPA, fbc_buf.phys, 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map FrameBufferConfig failed");
|
||||
return -30;
|
||||
}
|
||||
if (ept_map_range(g_guest.fb_gpa, fb_buf.phys, XJ380_FB_SIZE,
|
||||
EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[XJ380] EPT map framebuffer failed");
|
||||
return -31;
|
||||
}
|
||||
if (ept_map_range(g_guest.kernel_gpa, kernel_buf.phys, kernel_alloc,
|
||||
EPT_RWX) != 0) {
|
||||
log_error("[XJ380] EPT map kernel failed");
|
||||
return -32;
|
||||
}
|
||||
if (ept_map_range(g_guest.ram_gpa, ram_buf.phys, ram_size,
|
||||
EPT_RWX) != 0) {
|
||||
log_error("[XJ380] EPT map guest RAM failed");
|
||||
return -33;
|
||||
}
|
||||
/* 低 1MB 恒等映射(BIOS 区,与 linux_loader 一致;<1MB 无需按需分配) */
|
||||
if (ept_identity_map(0, 0x100000, EPT_RWX) != 0) {
|
||||
log_warn("[XJ380] EPT identity map first 1MB failed (non-critical)");
|
||||
}
|
||||
|
||||
/* 12. 完成 */
|
||||
g_guest.loaded = 1;
|
||||
log_hex64("[XJ380] kernel GPA=", g_guest.kernel_gpa);
|
||||
log_hex64("[XJ380] kernel entry=", g_guest.kernel_entry);
|
||||
log_hex64("[XJ380] kernel size=", g_guest.kernel_size);
|
||||
log_hex64("[XJ380] RAM GPA=", g_guest.ram_gpa);
|
||||
log_hex64("[XJ380] RAM size=", g_guest.ram_size);
|
||||
log_info("[XJ380] loader init ok");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct xj380_guest_info *xj380_get_guest_info(void) {
|
||||
return g_guest.loaded ? &g_guest : (struct xj380_guest_info *)0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
vmm/xj380_loader.o: vmm\xj380_loader.c include\utsm\xj380_loader.h \
|
||||
include\utsm\types.h include\utsm\vmx.h include\utsm\ept.h \
|
||||
include\utsm\log.h include\utsm\dma.h vmm\..\arch\x86_64\limine.h
|
||||
include\utsm\xj380_loader.h:
|
||||
include\utsm\types.h:
|
||||
include\utsm\vmx.h:
|
||||
include\utsm\ept.h:
|
||||
include\utsm\log.h:
|
||||
include\utsm\dma.h:
|
||||
vmm\..\arch\x86_64\limine.h:
|
||||
+1085
-14
@@ -19,6 +19,8 @@
|
||||
*/
|
||||
|
||||
#include "../UTSM/include/utsm/dsk.h"
|
||||
#include "../UTSM/include/utsm/linux_compat.h" /* VSCode Phase 2: input_forward_* */
|
||||
#include "../UTSM/include/utsm/pe.h" /* P5d: PE 窗口模式 run_windowed/inject_* */
|
||||
|
||||
/* block 设备函数类型(从 kernel_api + 0xA8 获取 block_api,read @ +0x10, write @ +0x18) */
|
||||
typedef int (*desktop_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer);
|
||||
@@ -27,7 +29,8 @@ typedef int (*desktop_block_write_fn)(u32 index, u64 lba, u32 count, const void
|
||||
/* 先包含 ascii_bitmaps.c(定义 g_ascii),再包含 deshab_ui.h(引用 g_ascii) */
|
||||
#include "../firstInit/ascii_bitmaps.c"
|
||||
#include "../UTSM/include/utsm/deshab_ui.h"
|
||||
/* 用户态 FAT32 读写 */
|
||||
/* 用户态 FAT32 读写。P5d:PE exe 可达数 MB,数据缓冲同 cmd.elf 提到 4MB。 */
|
||||
#define F32_DATA_BYTES (4u * 1024u * 1024u)
|
||||
#include "../tools/fat32_io.h"
|
||||
|
||||
typedef unsigned char u8;
|
||||
@@ -60,6 +63,17 @@ static void kstrcat(char *d, const char *s, int cap) {
|
||||
if (n < cap) kstrcpy(d + n, s, cap - n);
|
||||
}
|
||||
|
||||
/* P7.9: u32 -> decimal string(返回长度) */
|
||||
static int u32dec(u32 v, char *out) {
|
||||
char tmp[10];
|
||||
int n = 0;
|
||||
if (v == 0) { out[0] = '0'; out[1] = 0; return 1; }
|
||||
while (v > 0) { tmp[n++] = '0' + (char)(v % 10); v /= 10; }
|
||||
for (int i = 0; i < n; i++) out[i] = tmp[n - 1 - i];
|
||||
out[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static u64 rdtsc(void) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
@@ -164,6 +178,14 @@ typedef struct {
|
||||
app_draw_fn on_draw;
|
||||
app_destroy_fn on_destroy;
|
||||
const char *elf_name;
|
||||
/* VSCode Phase 5: Linux guest 应用(来自 LINUXAPP.CNF)。
|
||||
* linux_path 非 0 时 launch_app 走 launch_linux_app → exec_async。 */
|
||||
const char *linux_path;
|
||||
const char *linux_args;
|
||||
/* P5d: Windows PE 应用(来自 PEAPPS.CNF)。
|
||||
* pe_file 非 0 时 launch_app 走 launch_pe_app → run_windowed 桌面嵌入。 */
|
||||
const char *pe_file;
|
||||
const char *pe_args;
|
||||
} app_descriptor;
|
||||
|
||||
/* 桌面图标 */
|
||||
@@ -174,7 +196,7 @@ typedef struct {
|
||||
int selected;
|
||||
} desktop_icon;
|
||||
|
||||
#define MAX_ICONS 8
|
||||
#define MAX_ICONS 16 /* P7.4: 与 MAX_APPS 对齐(多实例 VSCODE2 等占位) */
|
||||
|
||||
/* 工作区 */
|
||||
typedef struct {
|
||||
@@ -211,6 +233,29 @@ static desktop_block_read_fn g_block_read;
|
||||
static desktop_block_write_fn g_block_write;
|
||||
static const dsk_boot_context *g_boot_ctx = 0;
|
||||
|
||||
/* VSCode Phase 2: Linux guest 输入转发。
|
||||
* g_lxc_svc 由 dsk_entry() 从 boot context reserved[5] 读取;
|
||||
* g_input_forward_enabled 在 IDE 标签 attach/detach 时联动置位,
|
||||
* 开启后 PS/2 鼠标键盘事件经 virtio-input 注入 Linux guest(VSCode 运行其中)。 */
|
||||
static const linux_compat_service *g_lxc_svc = 0;
|
||||
static int g_input_forward_enabled = 0;
|
||||
|
||||
/* P5d: PE 窗口桌面嵌入状态。
|
||||
* g_pe_svc 由 dsk_entry() 从 boot context reserved[4] 读取(magic 校验)。
|
||||
* launch_pe_app 创建受管窗口后阻塞在 pe_service.run_windowed;
|
||||
* PE 消息空转时 shim 回调 pe_pump_cb 驱动 desktop 一帧。
|
||||
* 单实例:run_windowed 阻塞期间拒绝启动第二个 PE 窗口(pump 重入安全)。 */
|
||||
static const pe_service *g_pe_svc = 0;
|
||||
static int g_pe_running = 0;
|
||||
static int g_pe_win_id = -1;
|
||||
static u8 *g_pe_surf = 0;
|
||||
static int g_pe_surf_w = 0, g_pe_surf_h = 0;
|
||||
static int g_pe_state_dummy = 0; /* draw_desktop_page 要求 app_state 非空 */
|
||||
|
||||
#define PE_SURFACE_ADDR 0x7800000ULL /* 120MB:sprite buf(112MB, ≤8.3MB) 之上 */
|
||||
#define PE_SURFACE_MAX (8u * 1024u * 1024u)
|
||||
#define PE_WM_CLOSE 0x0010u /* shim Win32 WM_CLOSE */
|
||||
|
||||
/* 鼠标状态 */
|
||||
static int g_mouse_x = 400, g_mouse_y = 300;
|
||||
static int g_mouse_btn = 0; /* bit0=左 bit1=右 */
|
||||
@@ -240,7 +285,7 @@ static int g_drag_win = -1;
|
||||
static int g_drag_off_x, g_drag_off_y;
|
||||
|
||||
/* 应用表 */
|
||||
#define MAX_APPS 8
|
||||
#define MAX_APPS 16
|
||||
static app_descriptor g_apps[MAX_APPS];
|
||||
static int g_app_count = 0;
|
||||
|
||||
@@ -719,6 +764,17 @@ static int win_hit_titlebar(desktop_window *w, int mx, int my) {
|
||||
my >= w->y && my < w->y + KATE_TITLEBAR_H;
|
||||
}
|
||||
|
||||
/* P5d: 关闭请求。运行中的 PE 窗口不能直接 win_destroy(run_windowed 阻塞中,
|
||||
* pump 持有窗口 id)——按 pe_window_host 协作契约 inject WM_CLOSE,
|
||||
* PE 自愿退出后由 launch_pe_app 收尾销毁。 */
|
||||
static void win_close_request(desktop_window *w) {
|
||||
if (g_pe_running && w->id == g_pe_win_id && g_pe_svc && g_pe_svc->inject_input) {
|
||||
g_pe_svc->inject_input(PE_WM_CLOSE, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
win_destroy(w);
|
||||
}
|
||||
|
||||
static int win_hit_close_btn(desktop_window *w, int mx, int my) {
|
||||
int bx = w->x + w->w - CLOSE_BTN_OFFSET;
|
||||
int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2;
|
||||
@@ -869,6 +925,15 @@ static int ps2_mouse_poll(void) {
|
||||
if (!(g_mouse_btn & 1) && (old_btn & 1)) g_left_released = 1;
|
||||
if ((g_mouse_btn & 2) && !(old_btn & 2)) g_right_pressed = 1;
|
||||
if (dx == 0 && dy == 0 && g_mouse_btn == old_btn) return 0;
|
||||
|
||||
/* VSCode Phase 2: IDE attached 时把相对位移 + 按钮状态镜像到 Linux guest。
|
||||
* dy 已在上方取反为屏幕坐标系(正=下),与 Linux REL_Y 约定一致;
|
||||
* input_forward_mouse 内部与上次状态比较,仅产生变化按钮事件 + SYN。
|
||||
* 即使宿主光标已抵边界被钳位,原始 dx/dy 仍转发,guest 光标自由移动。 */
|
||||
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_mouse) {
|
||||
g_lxc_svc->input_forward_mouse(dx, dy, (u8)g_mouse_btn);
|
||||
}
|
||||
|
||||
g_mouse_has_pkt = 1;
|
||||
return 1;
|
||||
}
|
||||
@@ -894,6 +959,36 @@ static char scan_to_ascii(u8 sc, int shift) {
|
||||
return shift ? shifted[sc] : normal[sc];
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: PS/2 set-1 扫描码 → Linux keycode(linux/input-event-codes.h)。
|
||||
* base : 已去除 0x80 release 位的基础扫描码(0x01..0x39 主键区 + 少数扩展)
|
||||
* e0 : 上一字节为 0xE0 前缀时置 1,选取扩展键变体
|
||||
* 返回 0 表示该扫描码无映射(调用方应丢弃)。
|
||||
*
|
||||
* 巧合:主键区 0x01..0x39 的 Linux keycode 与扫描码数值完全一致
|
||||
* (IBM XT 布局历史遗留),故直接透传;仅 E0 扩展键需单独查表。 */
|
||||
static u16 scancode_to_linux_keycode(u8 base, int e0) {
|
||||
if (e0) {
|
||||
switch (base) {
|
||||
case 0x1D: return 97; /* KEY_RIGHTCTRL */
|
||||
case 0x38: return 100; /* KEY_RIGHTALT */
|
||||
case 0x48: return 103; /* KEY_UP */
|
||||
case 0x4B: return 105; /* KEY_LEFT */
|
||||
case 0x4D: return 106; /* KEY_RIGHT */
|
||||
case 0x50: return 108; /* KEY_DOWN */
|
||||
case 0x52: return 110; /* KEY_INSERT */
|
||||
case 0x53: return 111; /* KEY_DELETE */
|
||||
case 0x47: return 102; /* KEY_HOME */
|
||||
case 0x4F: return 107; /* KEY_END */
|
||||
case 0x49: return 104; /* KEY_PAGEUP */
|
||||
case 0x51: return 109; /* KEY_PAGEDOWN */
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
/* 主键区:0x01..0x39 透传,超出范围无映射 */
|
||||
if (base >= 0x01 && base <= 0x39) return (u16)base;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 终端 (Bash) — 面板/窗口共用核心
|
||||
* ============================================================ */
|
||||
@@ -1423,6 +1518,161 @@ static void calc_on_destroy(void *state) { (void)state; }
|
||||
* 应用注册
|
||||
* ============================================================ */
|
||||
|
||||
/* ---- VSCode Phase 5: LINUXAPP.CNF Linux 应用注册表 ----
|
||||
* FAT32 根目录 LINUXAPP.CNF(8.3: LINUXAPPCNF),每行一个 Linux guest 应用:
|
||||
* NAME|DISPLAY|/guest/path|args(args 可含空格,可省略)
|
||||
* '#' 开头为注释。解析结果存静态池,app_descriptor.linux_path 指入。 */
|
||||
#define LINUXAPP_MAX 4
|
||||
static char g_lapp_name[LINUXAPP_MAX][12];
|
||||
static char g_lapp_disp[LINUXAPP_MAX][16];
|
||||
static char g_lapp_path[LINUXAPP_MAX][96];
|
||||
static char g_lapp_args[LINUXAPP_MAX][96];
|
||||
static int g_vscode_app_id = -1; /* 第一个名为 "vscode" 的注册项 */
|
||||
|
||||
/* 字段拷贝:src[0..n) → dst(NUL 结尾),返回拷贝长度 */
|
||||
static int lapp_field_copy(char *dst, int dst_cap, const char *src, int n) {
|
||||
if (n >= dst_cap) n = dst_cap - 1;
|
||||
for (int i = 0; i < n; i++) dst[i] = src[i];
|
||||
dst[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void register_linux_apps(void) {
|
||||
u8 *data = 0;
|
||||
u32 size = 0;
|
||||
char n11[11];
|
||||
if (f32_name_to_83("LINUXAPP.CNF", n11) != 0) return;
|
||||
if (f32_read_root_file(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */
|
||||
|
||||
int li = 0; /* 已注册数量 */
|
||||
u32 pos = 0;
|
||||
while (pos < size && li < LINUXAPP_MAX && g_app_count < MAX_APPS) {
|
||||
/* 取一行 */
|
||||
u32 eol = pos;
|
||||
while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++;
|
||||
u32 line_len = eol - pos;
|
||||
/* 跳过到下一行 */
|
||||
u32 next = eol;
|
||||
while (next < size && (data[next] == '\n' || data[next] == '\r')) next++;
|
||||
|
||||
if (line_len == 0 || data[pos] == '#') { pos = next; continue; }
|
||||
|
||||
/* 按 '|' 切 4 段:NAME|DISPLAY|PATH|ARGS */
|
||||
const char *line = (const char *)data + pos;
|
||||
int f0 = -1, f1 = -1, f2 = -1; /* 三个分隔符位置 */
|
||||
for (u32 i = 0; i < line_len; i++) {
|
||||
if (line[i] == '|') {
|
||||
if (f0 < 0) f0 = (int)i;
|
||||
else if (f1 < 0) f1 = (int)i;
|
||||
else if (f2 < 0) { f2 = (int)i; break; }
|
||||
}
|
||||
}
|
||||
if (f0 > 0 && f1 > f0) {
|
||||
int path_beg = f1 + 1;
|
||||
int path_end = (f2 > path_beg) ? f2 : (int)line_len;
|
||||
int args_beg = (f2 > path_beg) ? f2 + 1 : (int)line_len;
|
||||
|
||||
lapp_field_copy(g_lapp_name[li], 12, line, f0);
|
||||
lapp_field_copy(g_lapp_disp[li], 16, line + f0 + 1, f1 - f0 - 1);
|
||||
lapp_field_copy(g_lapp_path[li], 96, line + path_beg, path_end - path_beg);
|
||||
if (args_beg < (int)line_len)
|
||||
lapp_field_copy(g_lapp_args[li], 96, line + args_beg, (int)line_len - args_beg);
|
||||
else
|
||||
g_lapp_args[li][0] = 0;
|
||||
|
||||
if (g_lapp_path[li][0] == '/') {
|
||||
g_apps[g_app_count] = (app_descriptor){
|
||||
g_lapp_name[li], g_lapp_disp[li], 1024, 768,
|
||||
0, 0, 0, 0, 0,
|
||||
g_lapp_path[li], g_lapp_args[li]
|
||||
};
|
||||
/* 记录 VSCode app id(IDE attach 共享启动路径用)。
|
||||
* P7.4 匹配收紧:精确匹配 "VSCODE"(第 7 字符须为 NUL),
|
||||
* VSCODE2 等多实例条目不占用共享启动入口。 */
|
||||
if (g_vscode_app_id < 0 &&
|
||||
g_lapp_name[li][0] == 'V' && g_lapp_name[li][1] == 'S' &&
|
||||
g_lapp_name[li][2] == 'C' && g_lapp_name[li][3] == 'O' &&
|
||||
g_lapp_name[li][4] == 'D' && g_lapp_name[li][5] == 'E' &&
|
||||
g_lapp_name[li][6] == 0)
|
||||
g_vscode_app_id = g_app_count;
|
||||
g_app_count++;
|
||||
li++;
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- P5d: PEAPPS.CNF Windows PE 应用注册表 ----
|
||||
* FAT32 根目录 PEAPPS.CNF(8.3: PEAPPS CNF),每行一个 Windows PE 应用:
|
||||
* NAME|DISPLAY|path/file.exe|args('/' 分隔长路径,args 可含空格可省略)
|
||||
* '#' 开头为注释。解析结果存静态池,app_descriptor.pe_file 指入。
|
||||
* 仅适用于 GUI PE(消息循环驱动 pump);纯控制台 PE 不会触发消息空转,
|
||||
* 窗口模式下桌面将无响应直到进程退出——控制台程序请走 CMD。 */
|
||||
#define PEAPP_MAX 4
|
||||
static char g_papp_name[PEAPP_MAX][12];
|
||||
static char g_papp_disp[PEAPP_MAX][16];
|
||||
static char g_papp_file[PEAPP_MAX][96];
|
||||
static char g_papp_args[PEAPP_MAX][96];
|
||||
|
||||
static void pe_win_on_draw(void *state, app_ctx *ctx); /* 定义于 P5d 运行时段 */
|
||||
static void launch_pe_app(int app_id);
|
||||
|
||||
static void register_pe_apps(void) {
|
||||
u8 *data = 0;
|
||||
u32 size = 0;
|
||||
char n11[11];
|
||||
if (f32_name_to_83("PEAPPS.CNF", n11) != 0) return;
|
||||
if (f32_read_root_file(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */
|
||||
|
||||
int pi = 0;
|
||||
u32 pos = 0;
|
||||
while (pos < size && pi < PEAPP_MAX && g_app_count < MAX_APPS) {
|
||||
u32 eol = pos;
|
||||
while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++;
|
||||
u32 line_len = eol - pos;
|
||||
u32 next = eol;
|
||||
while (next < size && (data[next] == '\n' || data[next] == '\r')) next++;
|
||||
|
||||
if (line_len == 0 || data[pos] == '#') { pos = next; continue; }
|
||||
|
||||
/* 按 '|' 切 4 段:NAME|DISPLAY|FILE|ARGS */
|
||||
const char *line = (const char *)data + pos;
|
||||
int f0 = -1, f1 = -1, f2 = -1;
|
||||
for (u32 i = 0; i < line_len; i++) {
|
||||
if (line[i] == '|') {
|
||||
if (f0 < 0) f0 = (int)i;
|
||||
else if (f1 < 0) f1 = (int)i;
|
||||
else if (f2 < 0) { f2 = (int)i; break; }
|
||||
}
|
||||
}
|
||||
if (f0 > 0 && f1 > f0) {
|
||||
int file_beg = f1 + 1;
|
||||
int file_end = (f2 > file_beg) ? f2 : (int)line_len;
|
||||
int args_beg = (f2 > file_beg) ? f2 + 1 : (int)line_len;
|
||||
|
||||
lapp_field_copy(g_papp_name[pi], 12, line, f0);
|
||||
lapp_field_copy(g_papp_disp[pi], 16, line + f0 + 1, f1 - f0 - 1);
|
||||
lapp_field_copy(g_papp_file[pi], 96, line + file_beg, file_end - file_beg);
|
||||
if (args_beg < (int)line_len)
|
||||
lapp_field_copy(g_papp_args[pi], 96, line + args_beg, (int)line_len - args_beg);
|
||||
else
|
||||
g_papp_args[pi][0] = 0;
|
||||
|
||||
if (g_papp_file[pi][0]) {
|
||||
g_apps[g_app_count] = (app_descriptor){
|
||||
g_papp_name[pi], g_papp_disp[pi], 720, 540,
|
||||
0, 0, pe_win_on_draw, 0, 0, 0, 0,
|
||||
g_papp_file[pi], g_papp_args[pi]
|
||||
};
|
||||
g_app_count++;
|
||||
pi++;
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void register_apps(void) {
|
||||
/* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/
|
||||
g_apps[0] = (app_descriptor){
|
||||
@@ -1462,6 +1712,16 @@ static void register_apps(void) {
|
||||
0, 0, 0, 0, "PROEDIT ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* Settings — 系统设置(内核 FUCK 配置 + 网络 NETCONF 配置)*/
|
||||
g_apps[7] = (app_descriptor){
|
||||
"settings", "Settings", 760, 540,
|
||||
0, 0, 0, 0, "SETTINGSELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* VSCode Phase 5: Linux guest 应用(VSCode 等),来自 LINUXAPP.CNF */
|
||||
register_linux_apps();
|
||||
/* P5d: Windows PE 应用,来自 PEAPPS.CNF */
|
||||
register_pe_apps();
|
||||
}
|
||||
|
||||
/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px,
|
||||
@@ -1476,7 +1736,10 @@ static void setup_desktop_icons(void) {
|
||||
int y = KATE_TOPBAR_H + KATE_PAGE_PAD + pad;
|
||||
int max_x = (int)g_fb_w - pad; /* canvas 右缘 */
|
||||
for (int i = 0; i < g_app_count; i++) {
|
||||
if (!g_apps[i].on_create && !g_apps[i].elf_name) continue;
|
||||
if (g_icon_count >= MAX_ICONS) break;
|
||||
/* P7.4: Linux 应用(linux_path)也要出图标——否则 VSCODE 不可见 */
|
||||
if (!g_apps[i].on_create && !g_apps[i].elf_name &&
|
||||
!g_apps[i].linux_path && !g_apps[i].pe_file) 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;
|
||||
@@ -1511,7 +1774,7 @@ typedef struct { u32 type,flags; u64 offset,vaddr,paddr,filesz,memsz,align; } dt
|
||||
typedef struct { i64 tag; u64 val; } dt_elf64_dyn;
|
||||
typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela;
|
||||
|
||||
static u8 g_elf_image[262144];
|
||||
static u8 g_elf_image[1048576]; /* 1 MiB:settings.elf 内存映像 ~493KB(中文字体位图 BSS),256KB 不够 */
|
||||
|
||||
static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) {
|
||||
const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data;
|
||||
@@ -1553,11 +1816,15 @@ static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) {
|
||||
if (rela_off && rela_sz) {
|
||||
const dt_elf64_rela *r = (const dt_elf64_rela *)(data + rela_off);
|
||||
u32 rn = (u32)(rela_sz / sizeof(dt_elf64_rela));
|
||||
u64 load_bias = (u64)image - min_vaddr; /* 与 DSK loader 一致 */
|
||||
for (u32 j = 0; j < rn; j++) {
|
||||
u32 type = (u32)(r[j].info & 0xFFFFFFFF);
|
||||
if (type == DT_R_X86_64_RELATIVE) {
|
||||
/* 原 BUG: val = min_vaddr + addend,指针被写成低地址绝对值,
|
||||
* 导致 settings.elf 等模块内 .data 中的字符串指针全部失效 */
|
||||
if (r[j].offset < min_vaddr || r[j].offset >= min_vaddr + isize) continue;
|
||||
u64 target = r[j].offset - min_vaddr;
|
||||
u64 val = min_vaddr + (u64)r[j].addend;
|
||||
u64 val = load_bias + (u64)r[j].addend;
|
||||
*(u64 *)(image + target) = val;
|
||||
}
|
||||
}
|
||||
@@ -1605,10 +1872,24 @@ static void fill_app_ctx(app_ctx *ctx, desktop_window *win) {
|
||||
ctx->kernel_api = g_kernel_api;
|
||||
}
|
||||
|
||||
static void launch_linux_app(int app_id); /* VSCode Phase 5(定义于 IDE 区) */
|
||||
|
||||
static void launch_app(int app_id) {
|
||||
if (app_id < 0 || app_id >= g_app_count) return;
|
||||
app_descriptor *app = &g_apps[app_id];
|
||||
|
||||
/* VSCode Phase 5: Linux guest 应用走 exec_async 共享启动路径 */
|
||||
if (app->linux_path) {
|
||||
launch_linux_app(app_id);
|
||||
return;
|
||||
}
|
||||
|
||||
/* P5d: Windows PE 应用走 run_windowed 桌面嵌入路径 */
|
||||
if (app->pe_file) {
|
||||
launch_pe_app(app_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (app->elf_name) {
|
||||
launch_external_elf(app->elf_name);
|
||||
return;
|
||||
@@ -2228,6 +2509,383 @@ static void draw_dashboard(void) {
|
||||
|
||||
/* ---- 2·IDE ---- */
|
||||
static int g_ide_attached = 0;
|
||||
static void page_switch(int p); /* 定义于工作区段,launch_linux_app 前向引用 */
|
||||
|
||||
/* VSCode Phase 6: guest GUI 状态机。
|
||||
* 0 = 未运行(IDE host 显示启动提示)
|
||||
* 1 = 启动中(X server/VSCode spawn 已发出,等 scanout 上线)
|
||||
* 2 = 运行中(scanout enabled,blit guest 帧) */
|
||||
static int g_vscode_state = 0;
|
||||
|
||||
/* P8.1: VSCode 启动插桩--分阶段状态跟踪 + 串口日志 + 屏幕显示。
|
||||
* g_launch_phase: 0=idle 1=X启动 2=X socket等待 3=app spawn 4=pgrep验证 5=等scanout 6=running 7=失败
|
||||
* g_launch_status: 屏幕显示的状态字符串(IDE host 区域) */
|
||||
static int g_launch_phase = 0;
|
||||
static char g_launch_status[64] = "";
|
||||
static u64 g_launch_tsc = 0; /* 启动开始 TSC(测各阶段耗时) */
|
||||
static int g_launch_exec_rc = 0; /* exec_async 返回码 */
|
||||
static int g_launch_pgrep_rc = -1; /* pgrep 验证结果(0=找到, 1=未找到)*/
|
||||
|
||||
/* P7.4 多实例:IDE tab 动态标签——显示最近启动实例的 DISPLAY 名
|
||||
* (单 X :0 共享 scanout,多 VSCode 进程同屏;标签指示最后 attach 者)。 */
|
||||
static char g_ide_tab_label[16] = "VSCODE";
|
||||
|
||||
/* P7.9: 性能 profiling(TSC 计时,每秒更新 FPS 显示)。
|
||||
* 在 IDE 页面叠加显示 FPS / blit 耗时 / dirty 跳过率。 */
|
||||
static u64 g_prof_frames = 0; /* 自上次 FPS 更新以来的帧数 */
|
||||
static u64 g_prof_fps_tsc = 0; /* 上次 FPS 计算的 TSC */
|
||||
static int g_prof_fps = 0; /* 当前 FPS(每秒更新) */
|
||||
static u64 g_prof_blit_total = 0; /* 累计 blit TSC */
|
||||
static u64 g_prof_blit_count = 0; /* blit 调用次数 */
|
||||
static u64 g_prof_blit_skip = 0; /* dirty=0 跳过的次数 */
|
||||
static u64 g_prof_blit_full = 0; /* full repaint 次数 */
|
||||
static int g_prof_last_blit_us = 0; /* 上次 blit 耗时(微秒) */
|
||||
|
||||
/* P7.3: attach/detach 时使 scanout 缓存失效并请求全量重绘(定义于 blit 区) */
|
||||
static void ide_attach_invalidate(void);
|
||||
|
||||
/* VSCode Phase 3: IDE attach 时在 Linux guest 中启动 X server(Xfbdev)。
|
||||
* Xfbdev 通过 virtio-gpu 2D 设备合成帧到 surface pool scanout 影子缓冲,
|
||||
* desktop 每帧 blit 到 IDE host 区域。exec 用 sh -c "... &" 后台启动,
|
||||
* shell 立即退出被 daemon 回收,Xfbdev 由 init 收养继续运行。
|
||||
* Phase 6: pgrep 守卫保证幂等(多实例/重复 attach 不重起 X)。 */
|
||||
/* P8.1: slog + 数字(用于 rc/ec 日志,负数正确显示前导 -) */
|
||||
static void slog_num(const char *prefix, int rc) {
|
||||
char buf[48];
|
||||
kstrcpy(buf, prefix, 48);
|
||||
int n = kstrlen(buf);
|
||||
if (rc < 0) { buf[n++] = '-'; rc = -rc; }
|
||||
u32dec((u32)rc, buf + n);
|
||||
slog(buf);
|
||||
}
|
||||
|
||||
static void ide_launch_guest_x(void) {
|
||||
if (!g_lxc_svc || !g_lxc_svc->exec) { slog("ide: lxc exec unavailable"); return; }
|
||||
slog("[launch] phase 1: launching Xfbdev");
|
||||
g_launch_phase = 1;
|
||||
kstrcpy(g_launch_status, "LAUNCHING X SERVER . . .", 64);
|
||||
const char *argv[] = {
|
||||
"sh", "-c",
|
||||
"pgrep -x Xfbdev >/dev/null 2>&1 || "
|
||||
"Xfbdev :0 -geometry 1024x768 -depth 32 >/dev/null 2>&1 &"
|
||||
};
|
||||
u64 exit_code = 0;
|
||||
int rc = g_lxc_svc->exec("/bin/sh", 3, argv, 0, 0, 0, &exit_code);
|
||||
slog_num("[launch] Xfbdev exec rc=", rc);
|
||||
if (rc != 0) {
|
||||
slog("[launch] WARNING: Xfbdev launch rc!=0 (may still be OK if already running)");
|
||||
}
|
||||
}
|
||||
|
||||
/* P8.1 Fix: lxc guest ping -- exec "echo OK" 快速检测 Linux guest 是否运行。
|
||||
* 返回 0=guest 可用,非 0=不可用(VMX 未启用/guest 未 park/IPC 不通)。 */
|
||||
static int lxc_guest_ping(void) {
|
||||
if (!g_lxc_svc || !g_lxc_svc->exec) return -1;
|
||||
const char *pargv[] = { "sh", "-c", "echo OK" };
|
||||
char out[8] = {0};
|
||||
u64 ec = 0;
|
||||
int rc = g_lxc_svc->exec("/bin/sh", 3, pargv, out, 7, 0, &ec);
|
||||
if (rc != 0) return rc;
|
||||
if (out[0] == 'O' && out[1] == 'K') return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* VSCode Phase 6: 共享 guest GUI 启动路径(IDE +NEW/双击 host 与桌面图标共用)。
|
||||
* 首次调用确保 X server 在线(幂等启动 + 有界等待 X socket),然后
|
||||
* exec_async 目标程序(daemon fork+setsid+detach,立即返回)。
|
||||
* 后续调用(多实例)跳过 X 准备直接 spawn。path 为 NULL 时仅确保 X 在线。
|
||||
* P8.1 Fix: 启动前 ping 检测 guest 可用性;失败时快速短路,不浪费 5s X socket 等待。 */
|
||||
static void guest_gui_launch(const char *path, int argc, const char *const *argv) {
|
||||
if (!g_lxc_svc) {
|
||||
slog("[launch] lxc service null");
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "LXC SERVICE UNAVAILABLE", 64);
|
||||
return;
|
||||
}
|
||||
if (g_vscode_state == 0) {
|
||||
g_vscode_state = 1; /* 启动中 */
|
||||
g_launch_tsc = rdtsc();
|
||||
slog("[launch] === guest GUI launch sequence start ===");
|
||||
|
||||
/* P8.1 Fix: Phase 0 - ping guest 检测可用性 */
|
||||
g_launch_phase = 0;
|
||||
kstrcpy(g_launch_status, "CHECKING GUEST . . .", 64);
|
||||
int prc = lxc_guest_ping();
|
||||
slog_num("[launch] guest ping rc=", prc);
|
||||
if (prc != 0) {
|
||||
slog("[launch] FAIL: Linux guest not running (VMX/guest unavailable)");
|
||||
g_vscode_state = 0; /* 回到未启动状态 */
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "GUEST NOT RUNNING - NEED KVM/VMX", 64);
|
||||
return;
|
||||
}
|
||||
slog("[launch] guest ping OK - Linux guest is running");
|
||||
|
||||
/* Phase 1: launch X server */
|
||||
ide_launch_guest_x();
|
||||
|
||||
/* Phase 2: wait for X socket (bounded ~5s) */
|
||||
g_launch_phase = 2;
|
||||
kstrcpy(g_launch_status, "WAITING FOR X SOCKET . . .", 64);
|
||||
slog("[launch] phase 2: waiting for X socket");
|
||||
if (g_lxc_svc->exec) {
|
||||
const char *wargv[] = {
|
||||
"sh", "-c",
|
||||
"i=0; while [ ! -S /tmp/.X11-unix/X0 ] && [ $i -lt 25 ]; "
|
||||
"do sleep 0.2; i=$((i+1)); done; [ -S /tmp/.X11-unix/X0 ] && echo OK || echo FAIL"
|
||||
};
|
||||
/* 用 exec + stdout 捕获验证 X socket 是否就绪 */
|
||||
char xout[16] = {0};
|
||||
u64 ec = 0;
|
||||
int rc = g_lxc_svc->exec("/bin/sh", 3, wargv, xout, 15, 0, &ec);
|
||||
slog_num("[launch] X socket wait rc=", rc);
|
||||
if (xout[0] == 'O' && xout[1] == 'K') {
|
||||
slog("[launch] X socket READY (/tmp/.X11-unix/X0 exists)");
|
||||
} else {
|
||||
slog("[launch] WARNING: X socket NOT ready after ~5s timeout");
|
||||
kstrcpy(g_launch_status, "X SOCKET TIMEOUT - RETRYING SPAWN", 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 3: exec_async spawn the app */
|
||||
if (path && g_lxc_svc->exec_async) {
|
||||
g_launch_phase = 3;
|
||||
kstrcpy(g_launch_status, "SPAWNING APPLICATION . . .", 64);
|
||||
slog("[launch] phase 3: exec_async spawn");
|
||||
slog(path);
|
||||
int rc = g_lxc_svc->exec_async(path, argc, argv);
|
||||
g_launch_exec_rc = rc;
|
||||
slog_num("[launch] exec_async rc=", rc);
|
||||
if (rc == 0) {
|
||||
slog("[launch] exec_async OK - process spawned");
|
||||
} else {
|
||||
slog("[launch] exec_async FAILED - process not spawned");
|
||||
g_vscode_state = 0; /* P8.1 Fix: 回到未启动,UI 显示错误而非永远 STARTING */
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "SPAWN FAILED - CHECK SERIAL LOG", 64);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Phase 4: pgrep verify process exists (bounded wait ~1s) */
|
||||
g_launch_phase = 4;
|
||||
kstrcpy(g_launch_status, "VERIFYING PROCESS . . .", 64);
|
||||
slog("[launch] phase 4: pgrep verify");
|
||||
if (g_lxc_svc->exec) {
|
||||
/* 用 basename 做 pgrep(code -> pgrep code) */
|
||||
const char *pargv[] = {
|
||||
"sh", "-c",
|
||||
"sleep 0.5; pgrep -x code >/dev/null 2>&1 && echo FOUND || echo NOPE"
|
||||
};
|
||||
char pout[16] = {0};
|
||||
u64 pec = 0;
|
||||
int prc = g_lxc_svc->exec("/bin/sh", 3, pargv, pout, 15, 0, &pec);
|
||||
if (pout[0] == 'F') {
|
||||
g_launch_pgrep_rc = 0;
|
||||
slog("[launch] pgrep: VSCode process FOUND");
|
||||
} else if (pout[0] == 'N') {
|
||||
g_launch_pgrep_rc = 1;
|
||||
slog("[launch] pgrep: VSCode process NOT found (may still be starting)");
|
||||
} else {
|
||||
g_launch_pgrep_rc = -1;
|
||||
slog_num("[launch] pgrep: no output, exec rc=", prc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 5: wait for scanout */
|
||||
g_launch_phase = 5;
|
||||
kstrcpy(g_launch_status, "WAITING FOR SCANOUT . . .", 64);
|
||||
slog("[launch] phase 5: waiting for scanout to come online");
|
||||
} else if (path && !g_lxc_svc->exec_async) {
|
||||
slog("[launch] exec_async unavailable - cannot spawn app");
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "EXEC_ASYNC UNAVAILABLE", 64);
|
||||
}
|
||||
}
|
||||
|
||||
/* VSCode Phase 5/6: 桌面图标启动 Linux 应用(LINUXAPP.CNF 注册项)。
|
||||
* 解析 args(空格分隔)后经共享路径 exec_async,并切到 IDE 页 attach。 */
|
||||
static void launch_linux_app(int app_id) {
|
||||
if (app_id < 0 || app_id >= g_app_count) return;
|
||||
app_descriptor *app = &g_apps[app_id];
|
||||
if (!app->linux_path) return;
|
||||
if (!g_lxc_svc || !g_lxc_svc->exec_async) {
|
||||
slog("linuxapp: lxc service unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
/* argv: [name, ...args](args 在本地缓冲内原地切分) */
|
||||
const char *argv[16];
|
||||
int argc = 0;
|
||||
argv[argc++] = app->name;
|
||||
char abuf[96];
|
||||
if (app->linux_args && app->linux_args[0]) {
|
||||
int n = kstrlen(app->linux_args);
|
||||
if (n > (int)sizeof(abuf) - 1) n = (int)sizeof(abuf) - 1;
|
||||
for (int i = 0; i < n; i++) abuf[i] = app->linux_args[i];
|
||||
abuf[n] = 0;
|
||||
char *p = abuf;
|
||||
while (*p && argc < 15) {
|
||||
while (*p == ' ') p++;
|
||||
if (!*p) break;
|
||||
argv[argc++] = p;
|
||||
while (*p && *p != ' ') p++;
|
||||
if (*p) *p++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
guest_gui_launch(app->linux_path, argc, argv);
|
||||
|
||||
/* P7.4: tab 标签跟随最近启动实例(DISPLAY 名,≤15 字符) */
|
||||
{
|
||||
int i = 0;
|
||||
const char *dn = app->display_name;
|
||||
while (dn && dn[i] && i + 1 < (int)sizeof(g_ide_tab_label)) {
|
||||
g_ide_tab_label[i] = dn[i];
|
||||
i++;
|
||||
}
|
||||
g_ide_tab_label[i] = 0;
|
||||
}
|
||||
|
||||
/* 切到 IDE 页并 attach(VSCode 窗口出现在 IDE host 区域) */
|
||||
g_ide_attached = 1;
|
||||
g_input_forward_enabled = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
page_switch(2);
|
||||
slog("linuxapp launched -> ide attached");
|
||||
}
|
||||
|
||||
/* VSCode Phase 6: IDE attach 共享入口——启动(或复用)VSCode。 */
|
||||
static void vscode_launch(void) {
|
||||
if (g_vscode_app_id >= 0) {
|
||||
launch_linux_app(g_vscode_app_id);
|
||||
return;
|
||||
}
|
||||
/* 未注册 VSCode(CNF 缺失):仅确保 X server 在线,保持 Phase 3 行为 */
|
||||
guest_gui_launch(0, 0, 0);
|
||||
}
|
||||
|
||||
/* VSCode Phase 3/7: 把 virtio-gpu scanout 影子缓冲 blit 到 IDE host 区域。
|
||||
* scanout 为 1024×768 XRGB8888(stride=4096),与 framebuffer 同为 32bpp。
|
||||
* Phase 7 dirty rect 优化:主循环每帧把 scanout 状态缓存进 g_so(查询会
|
||||
* 清零 UTSM 侧 dirty),blit 只拷贝累积 dirty rect(scanout 坐标系并集),
|
||||
* sprite 双缓冲持久保留未变区域;attach/首帧/状态切换经 g_so_full_repaint
|
||||
* 全量重绘一次。 */
|
||||
static struct linux_compat_scanout_info g_so;
|
||||
static int g_so_valid = 0; /* 1 = g_so 为本帧主循环 poll 的缓存 */
|
||||
static int g_so_full_repaint = 1; /* 1 = 下次 blit 全量拷贝(初始/attach/上线) */
|
||||
|
||||
/* attach/detach 时使 scanout 缓存失效并请求全量重绘 */
|
||||
static void ide_attach_invalidate(void) {
|
||||
g_so_full_repaint = 1;
|
||||
g_so_valid = 0;
|
||||
}
|
||||
|
||||
/* 获取 scanout 状态:优先本帧缓存;缓存无效时查询并缓存。 */
|
||||
static const struct linux_compat_scanout_info *ide_scanout_state(void) {
|
||||
if (g_so_valid) return &g_so;
|
||||
if (g_lxc_svc && g_lxc_svc->gpu_get_scanout_info &&
|
||||
g_lxc_svc->gpu_get_scanout_info(&g_so) == 0) {
|
||||
g_so_valid = 1;
|
||||
return &g_so;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void blit_scanout_to_ide(int hx, int hy, int hw, int hh) {
|
||||
const struct linux_compat_scanout_info *si = ide_scanout_state();
|
||||
if (!si || !si->enabled || !si->host_vaddr) {
|
||||
/* scanout 未就绪:X server 尚未 SET_SCANOUT,显示等待提示 */
|
||||
du_fill_rect(&g_fb, hx, hy, hw, hh, 0xFF051828u);
|
||||
draw_centered("WAITING FOR GUEST X SERVER . . .",
|
||||
hx + hw / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int sw = (int)si->width, sh = (int)si->height;
|
||||
int sstride = (int)si->stride; /* bytes/row */
|
||||
int dstride = (int)g_fb_pitch; /* bytes/row */
|
||||
u8 *src = (u8 *)si->host_vaddr;
|
||||
u8 *dst_base = (u8 *)g_fb.fb;
|
||||
|
||||
/* 拷贝区域:默认仅累积 dirty rect;full repaint 时整帧 */
|
||||
int full = g_so_full_repaint;
|
||||
int cx = 0, cy = 0, cw = sw, ch = sh;
|
||||
if (!full) {
|
||||
if (!si->dirty || si->dirty_w == 0 || si->dirty_h == 0) {
|
||||
g_prof_blit_skip++; /* P7.9: dirty=0 零拷贝跳过 */
|
||||
return;
|
||||
}
|
||||
cx = (int)si->dirty_x; cy = (int)si->dirty_y;
|
||||
cw = (int)si->dirty_w; ch = (int)si->dirty_h;
|
||||
}
|
||||
g_so_full_repaint = 0;
|
||||
|
||||
/* P7.9: TSC 计时 blit */
|
||||
u64 _tsc0 = rdtsc();
|
||||
if (full) g_prof_blit_full++;
|
||||
|
||||
/* clamp 到 host 区域与 scanout 边界 */
|
||||
if (cx + cw > hw) cw = hw - cx;
|
||||
if (cy + ch > hh) ch = hh - cy;
|
||||
if (cw > sw - cx) cw = sw - cx;
|
||||
if (ch > sh - cy) ch = sh - cy;
|
||||
if (cw <= 0 || ch <= 0) return;
|
||||
|
||||
for (int row = 0; row < ch; row++) {
|
||||
u32 *s = (u32 *)(src + (u64)(cy + row) * sstride + (u64)cx * 4);
|
||||
u32 *d = (u32 *)(dst_base + (u64)(hy + cy + row) * dstride
|
||||
+ (u64)(hx + cx) * 4);
|
||||
for (int col = 0; col < cw; col++) d[col] = s[col];
|
||||
}
|
||||
|
||||
/* 全量重绘时,scanout 未覆盖的 host 剩余区域填深色背景 */
|
||||
if (full) {
|
||||
int cov_w = (hw < sw) ? hw : sw;
|
||||
int cov_h = (hh < sh) ? hh : sh;
|
||||
if (cov_w < hw)
|
||||
du_fill_rect(&g_fb, hx + cov_w, hy, hw - cov_w, cov_h, 0xFF051828u);
|
||||
if (cov_h < hh)
|
||||
du_fill_rect(&g_fb, hx, hy + cov_h, hw, hh - cov_h, 0xFF051828u);
|
||||
}
|
||||
|
||||
/* P7.6: 叠加 guest 硬件光标(ARGB alpha-blend 到 framebuffer)。
|
||||
* 光标位图 row stride = 64(virtio-gpu 规范 cursor max 64x64)。
|
||||
* 坐标从 scanout 坐标系映射到 framebuffer:(hx + cursor_x - hot_x, ...) */
|
||||
if (si->cursor_visible && si->cursor_bitmap) {
|
||||
int cw = (int)si->cursor_w, ch = (int)si->cursor_h;
|
||||
if (cw > 0 && ch > 0) {
|
||||
int fx = hx + (int)si->cursor_x - (int)si->cursor_hot_x;
|
||||
int fy = hy + (int)si->cursor_y - (int)si->cursor_hot_y;
|
||||
u32 *cbmp = (u32 *)si->cursor_bitmap;
|
||||
for (int row = 0; row < ch; row++) {
|
||||
int dy = fy + row;
|
||||
if (dy < 0 || (u64)dy >= g_fb_h) continue;
|
||||
u32 *d = (u32 *)((u8 *)g_fb.fb + (u64)dy * (u64)g_fb_pitch);
|
||||
const u32 *s = &cbmp[row * 64]; /* row stride = VGPU_CURSOR_MAX */
|
||||
for (int col = 0; col < cw; col++) {
|
||||
int dx = fx + col;
|
||||
if (dx < 0 || (u64)dx >= g_fb_w) continue;
|
||||
u32 src = s[col];
|
||||
u32 a = (src >> 24) & 0xFF;
|
||||
if (a == 0) continue;
|
||||
if (a == 255) { d[dx] = src; continue; }
|
||||
u32 dst = d[dx];
|
||||
u32 rr = (((src >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255;
|
||||
u32 gg = (((src >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255;
|
||||
u32 bb = ( (src & 0xFF) * a + ( dst & 0xFF) * (255 - a)) / 255;
|
||||
d[dx] = 0xFF000000u | (rr << 16) | (gg << 8) | bb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* P7.9: 记录 blit 耗时 */
|
||||
g_prof_blit_total += rdtsc() - _tsc0;
|
||||
g_prof_blit_count++;
|
||||
if (g_tsc_per_sec)
|
||||
g_prof_last_blit_us = (int)((rdtsc() - _tsc0) * 1000000 / g_tsc_per_sec);
|
||||
}
|
||||
|
||||
static void draw_ide(void) {
|
||||
int W = (int)g_fb_w;
|
||||
@@ -2243,9 +2901,9 @@ static void draw_ide(void) {
|
||||
du_fill_rect(&g_fb, x, top + 14, 8, 8, KS_ACCENT2);
|
||||
x += 16;
|
||||
|
||||
/* 实例标签 EDITOR ×(Kate .ide-tab) */
|
||||
/* 实例标签(Kate .ide-tab):P7.4 动态标签 = 最近启动实例 DISPLAY 名 */
|
||||
{
|
||||
const char *label = "EDITOR";
|
||||
const char *label = g_ide_tab_label;
|
||||
int bw = kstrlen(label) * (int)DU_ASCII_STEP + 32;
|
||||
if (g_ide_attached) {
|
||||
du_fill_rect(&g_fb, x, top + 5, bw, 26, KS_ACCENT);
|
||||
@@ -2292,12 +2950,90 @@ static void draw_ide(void) {
|
||||
draw_dashed_rect(hx, hy, hw, hh, KS_BORDER);
|
||||
|
||||
if (g_ide_attached) {
|
||||
editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4);
|
||||
if (g_input_forward_enabled) {
|
||||
/* VSCode Phase 6 状态机:启动中探测 scanout 上线 → 运行中 */
|
||||
if (g_vscode_state == 1) {
|
||||
const struct linux_compat_scanout_info *si = ide_scanout_state();
|
||||
if (si && si->enabled) {
|
||||
g_vscode_state = 2;
|
||||
g_launch_phase = 6;
|
||||
g_so_full_repaint = 1; /* 上线首帧全量重绘 */
|
||||
/* P8.1: scanout 上线日志 + 耗时 */
|
||||
slog("[launch] phase 6: SCANOUT ONLINE");
|
||||
if (g_tsc_per_sec && g_launch_tsc) {
|
||||
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
||||
slog_num("[launch] total launch time ms=", (int)ms);
|
||||
}
|
||||
{
|
||||
char sbuf[48];
|
||||
kstrcpy(sbuf, "scanout: ", 48);
|
||||
u32dec(si->width, sbuf + kstrlen(sbuf));
|
||||
kstrcat(sbuf, "x", 48);
|
||||
u32dec(si->height, sbuf + kstrlen(sbuf));
|
||||
slog(sbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_vscode_state == 2) {
|
||||
/* 运行中:blit guest 合成帧 */
|
||||
blit_scanout_to_ide(hx + 2, hy + 2, hw - 4, hh - 4);
|
||||
} else {
|
||||
/* P8.1: 启动中--显示分阶段状态 + 详细信息 */
|
||||
du_fill_rect(&g_fb, hx + 2, hy + 2, hw - 4, hh - 4, 0xFF051828u);
|
||||
draw_centered(g_launch_status[0] ? g_launch_status :
|
||||
"STARTING VSCODE . . .",
|
||||
W / 2, hy + hh / 2 - 20, KS_TEXT_DIM, 0);
|
||||
/* 详细信息行(phase/exec_rc/pgrep/耗时) */
|
||||
{
|
||||
char dbuf[80];
|
||||
int dp = 0;
|
||||
kstrcpy(dbuf + dp, "PHASE:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)g_launch_phase, dbuf + dp);
|
||||
if (g_launch_exec_rc != 0) {
|
||||
kstrcpy(dbuf + dp, " RC:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)(g_launch_exec_rc < 0 ?
|
||||
(u32)(-g_launch_exec_rc) : (u32)g_launch_exec_rc),
|
||||
dbuf + dp);
|
||||
}
|
||||
if (g_launch_pgrep_rc >= 0) {
|
||||
kstrcpy(dbuf + dp, " PGREP:", 80); dp = kstrlen(dbuf);
|
||||
kstrcpy(dbuf + dp, g_launch_pgrep_rc == 0 ? "FOUND" : "MISSING",
|
||||
80);
|
||||
dp = kstrlen(dbuf);
|
||||
}
|
||||
if (g_tsc_per_sec && g_launch_tsc) {
|
||||
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
||||
kstrcpy(dbuf + dp, " T:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)ms, dbuf + dp);
|
||||
kstrcpy(dbuf + dp, "ms", 80);
|
||||
}
|
||||
draw_centered(dbuf, W / 2, hy + hh / 2 + 4, KS_TEXT_DIM, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4);
|
||||
}
|
||||
} else {
|
||||
/* embed-empty(Kate:未启动 VSCode 提示) */
|
||||
draw_centered("EDITOR NOT ATTACHED . DOUBLE-CLICK OR + NEW IDE",
|
||||
draw_centered("VSCODE NOT RUNNING . DOUBLE-CLICK OR + NEW IDE TO START",
|
||||
W / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
|
||||
}
|
||||
|
||||
/* P7.9: 性能 profiling 叠加(IDE 页面左下角) */
|
||||
if (g_ide_attached && g_input_forward_enabled) {
|
||||
char buf[80];
|
||||
int p = 0;
|
||||
kstrcpy(buf + p, "FPS:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_fps, buf + p);
|
||||
kstrcpy(buf + p, " BLT:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_last_blit_us, buf + p);
|
||||
kstrcpy(buf + p, "us SKP:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_blit_skip, buf + p);
|
||||
kstrcpy(buf + p, " FUL:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_blit_full, buf + p);
|
||||
du_draw_string(&g_fb, buf, hx + 6, hy + hh - 16,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 3·DESKTOP ---- */
|
||||
@@ -2836,10 +3572,33 @@ static int dash_click(int mx, int my, int dbl) {
|
||||
|
||||
/* ---- 2·IDE 点击 ---- */
|
||||
static int ide_click(int mx, int my, int dbl) {
|
||||
if (g_ide_attached && rect_hit(g_ide_tabx, mx, my)) { g_ide_attached = 0; return 1; }
|
||||
if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) { g_ide_attached = 1; return 1; }
|
||||
if (g_ide_attached && rect_hit(g_ide_tabx, mx, my)) {
|
||||
g_ide_attached = 0;
|
||||
/* VSCode Phase 2: detach 时停止向 guest 转发输入 */
|
||||
g_input_forward_enabled = 0;
|
||||
ide_attach_invalidate(); /* P7.3: detach 后下次 attach 全量重绘 */
|
||||
slog("ide detached, input forwarding off");
|
||||
return 1;
|
||||
}
|
||||
if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) {
|
||||
g_ide_attached = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
/* attach 时仅当 Linux guest 服务可用才开启转发;否则降级为本地 editor */
|
||||
g_input_forward_enabled = (g_lxc_svc != 0);
|
||||
slog(g_input_forward_enabled ? "ide attached, forwarding to guest"
|
||||
: "ide attached, lxc unavailable (local editor)");
|
||||
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
||||
if (g_input_forward_enabled) vscode_launch();
|
||||
return 1;
|
||||
}
|
||||
if (!g_ide_attached && dbl && rect_hit(g_ide_host, mx, my)) {
|
||||
g_ide_attached = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
g_input_forward_enabled = (g_lxc_svc != 0);
|
||||
slog(g_input_forward_enabled ? "ide attached (dbl), forwarding to guest"
|
||||
: "ide attached (dbl), lxc unavailable (local editor)");
|
||||
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
||||
if (g_input_forward_enabled) vscode_launch();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
@@ -2863,7 +3622,7 @@ static int desktop_click(int mx, int my, int dbl) {
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue;
|
||||
if (rect_hit(g_tb_close_rect[i], mx, my)) {
|
||||
win_destroy(&g_windows[i]);
|
||||
win_close_request(&g_windows[i]);
|
||||
} else {
|
||||
win_focus(&g_windows[i]);
|
||||
}
|
||||
@@ -2879,7 +3638,7 @@ static int desktop_click(int mx, int my, int dbl) {
|
||||
win_focus(w);
|
||||
w = win_find(wid); /* focus 重排后重新定位 */
|
||||
if (!w) return 1;
|
||||
if (win_hit_close_btn(w, mx, my)) { win_destroy(w); return 1; }
|
||||
if (win_hit_close_btn(w, mx, my)) { win_close_request(w); return 1; }
|
||||
if (win_hit_titlebar(w, mx, my)) {
|
||||
g_dragging = 1;
|
||||
g_drag_win = w->id;
|
||||
@@ -3038,6 +3797,21 @@ static int on_right_press(void) {
|
||||
|
||||
/* ---- 键盘 ---- */
|
||||
static int handle_key(u8 sc) {
|
||||
/* VSCode Phase 2: IDE attached 且在 IDE 页时,把按键序列转发到 Linux guest。
|
||||
* 0xE0 前缀字节本身不转发(无对应 keycode),仅靠 g_e0 标志让下一字节选取扩展键变体。
|
||||
* 转发在修饰键跟踪与页面路由之前完成,保证 VSCode 收到完整 press/release
|
||||
* 序列(含 Shift/Ctrl/Alt/方向键)。宿主修饰键跟踪继续运行,Ctrl+Tab 切页、
|
||||
* Ctrl+S 等热键仍可在宿主侧生效,便于用户随时切离 IDE 页。 */
|
||||
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_keyboard &&
|
||||
sc != 0xE0 && g_page == 2) {
|
||||
u8 base = (u8)(sc & 0x7F);
|
||||
u16 code = scancode_to_linux_keycode(base, g_e0);
|
||||
if (code) {
|
||||
u32 value = (sc & 0x80) ? 0 : 1; /* 0x80 位 = release */
|
||||
g_lxc_svc->input_forward_keyboard(code, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == 0xE0) { g_e0 = 1; return 0; }
|
||||
if (sc == 0x2A || sc == 0x36) { g_shift = 1; return 0; }
|
||||
if (sc == 0xAA || sc == 0xB6) { g_shift = 0; return 0; }
|
||||
@@ -3094,6 +3868,11 @@ static int handle_key(u8 sc) {
|
||||
}
|
||||
if (g_page == 2) {
|
||||
if (g_ide_attached) {
|
||||
if (g_input_forward_enabled) {
|
||||
/* VSCode Phase 2: 键盘已转发到 guest 中的 VSCode,
|
||||
* 本地 editor 不再重复处理,避免双输入。 */
|
||||
return 1;
|
||||
}
|
||||
editor_key((editor_state *)ADDR_EDDASH, sc, g_shift);
|
||||
return 1;
|
||||
}
|
||||
@@ -3118,6 +3897,210 @@ static int handle_key(u8 sc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* P5d: PE 窗口运行时(run_windowed 桌面嵌入)
|
||||
*
|
||||
* 协作模型(契约见 utsm/pe.h pe_window_host):
|
||||
* launch_pe_app 创建受管窗口并把客户区大小的内存 surface 交给 shim,
|
||||
* 随后阻塞在 pe_service.run_windowed。PE 消息空转(GetMessage/
|
||||
* PeekMessage 无消息)时 shim 回调 pe_pump_cb,由它驱动 desktop 一帧:
|
||||
* 采集 PS/2 输入(PE 窗口聚焦时经 inject_* 注入,否则走桌面正常路径)、
|
||||
* redraw_all + flip。关闭 = inject WM_CLOSE(win_close_request),
|
||||
* PE 自愿退出后 run_windowed 返回,launch_pe_app 收尾销毁窗口。
|
||||
*
|
||||
* 注意:pump 运行在 PE 的 1MB 大栈上,desktop 帧代码栈用量远小于此;
|
||||
* pump 内禁止再次调用 run/run_windowed(launch_pe_app 以 g_pe_running 拒绝)。
|
||||
* ============================================================ */
|
||||
|
||||
/* PE 窗口 on_draw:把 shim 渲染的 surface blit 到客户区(32bpp 行拷贝) */
|
||||
static void pe_win_on_draw(void *state, app_ctx *ctx) {
|
||||
(void)state;
|
||||
if (!g_pe_surf) return;
|
||||
int w = (g_pe_surf_w < ctx->client_w) ? g_pe_surf_w : ctx->client_w;
|
||||
int h = (g_pe_surf_h < ctx->client_h) ? g_pe_surf_h : ctx->client_h;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
u8 *dst0 = (u8 *)g_fb.fb + (u64)ctx->client_y * g_fb_pitch
|
||||
+ (u64)ctx->client_x * 4;
|
||||
u32 sp = (u32)g_pe_surf_w * 4;
|
||||
for (int r = 0; r < h; r++) {
|
||||
u32 *d = (u32 *)(dst0 + (u64)r * g_fb_pitch);
|
||||
const u32 *s = (const u32 *)(g_pe_surf + (u64)r * sp);
|
||||
for (int c = 0; c < w; c++) d[c] = s[c] | 0xFF000000u;
|
||||
}
|
||||
}
|
||||
|
||||
/* PE 消息空转让出点:驱动 desktop 一帧。返回 0 继续,非 0 请求 PE 退出。 */
|
||||
static int pe_pump_cb(void *ud) {
|
||||
int win_id = (int)(u64)ud;
|
||||
int moved = 0;
|
||||
while (ps2_mouse_poll()) moved = 1;
|
||||
|
||||
desktop_window *pw = win_find(win_id);
|
||||
if (!pw) return 1; /* 窗口已销毁:请求 PE 退出 */
|
||||
|
||||
int pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur &&
|
||||
!g_taskview && !g_ctx_open);
|
||||
|
||||
/* 鼠标注入:PE 窗口聚焦且光标在客户区内 → surface 局部坐标 + 按钮位 */
|
||||
int cx0 = pw->x + BORDER_W, cy0 = pw->y + KATE_TITLEBAR_H;
|
||||
int ccw = pw->w - 2 * BORDER_W, cch = pw->h - KATE_TITLEBAR_H - BORDER_W;
|
||||
if (pe_focused && g_pe_svc->inject_pointer &&
|
||||
g_mouse_x >= cx0 && g_mouse_x < cx0 + ccw &&
|
||||
g_mouse_y >= cy0 && g_mouse_y < cy0 + cch) {
|
||||
g_pe_svc->inject_pointer(g_mouse_x - cx0, g_mouse_y - cy0,
|
||||
(u32)(g_mouse_btn & 3));
|
||||
}
|
||||
|
||||
/* 点击/释放走桌面正常路径(聚焦、标题栏拖拽、关闭按钮→win_close_request、
|
||||
* 其他窗口/图标)。双击其他 PE 图标会被 launch_pe_app 的 g_pe_running 拒绝。 */
|
||||
if (g_left_pressed) { g_left_pressed = 0; on_left_press(); }
|
||||
if (g_right_pressed) { g_right_pressed = 0; on_right_press(); }
|
||||
if (g_left_released) {
|
||||
g_left_released = 0;
|
||||
if (g_dragging || g_drag_slider) {
|
||||
g_dragging = 0; g_drag_win = -1; g_drag_slider = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pw = win_find(win_id); /* 点击可能重排窗口数组 */
|
||||
if (!pw) return 1;
|
||||
pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur &&
|
||||
!g_taskview && !g_ctx_open);
|
||||
|
||||
/* 窗口/滑块拖拽(与主循环相同的钳位) */
|
||||
if (moved && g_dragging && g_drag_win == win_id) {
|
||||
pw->x = g_mouse_x - g_drag_off_x;
|
||||
pw->y = g_mouse_y - g_drag_off_y;
|
||||
if (pw->y < KATE_TOPBAR_H) pw->y = KATE_TOPBAR_H;
|
||||
if (pw->x < 0 - pw->w + 40) pw->x = 0 - pw->w + 40;
|
||||
if (pw->x > (int)g_fb_w - 40) pw->x = (int)g_fb_w - 40;
|
||||
if (pw->y > (int)g_fb_h - KATE_TASKBAR_H - 20)
|
||||
pw->y = (int)g_fb_h - KATE_TASKBAR_H - 20;
|
||||
}
|
||||
if (moved && g_drag_slider == 1) slider_set(g_vol_rect, &g_vol, g_mouse_x);
|
||||
if (moved && g_drag_slider == 2) slider_set(g_bri_rect, &g_bright, g_mouse_x);
|
||||
|
||||
/* 键盘:PE 聚焦时注入(shim 内部合成 KEYUP/CHAR/修饰键),
|
||||
* Ctrl+Tab 保留给宿主切页;否则走桌面正常路径。 */
|
||||
for (;;) {
|
||||
u8 st = inb(0x64);
|
||||
if (!(st & 1) || (st & 0x20)) break;
|
||||
u8 sc = inb(0x60);
|
||||
if (pe_focused && g_pe_svc->inject_scancode) {
|
||||
if (sc == 0xE0) { g_e0 = 1; continue; }
|
||||
/* 宿主侧同步跟踪修饰键(Ctrl+Tab 切页判定用) */
|
||||
if (sc == 0x2A || sc == 0x36) g_shift = 1;
|
||||
if (sc == 0xAA || sc == 0xB6) g_shift = 0;
|
||||
if (sc == 0x1D) g_ctrl = 1;
|
||||
if (sc == 0x9D) g_ctrl = 0;
|
||||
if (g_ctrl && sc == 0x0F) {
|
||||
/* 切页前补修饰键释放,防止 PE 侧卡键 */
|
||||
g_pe_svc->inject_scancode(0x9D, 0);
|
||||
g_pe_svc->inject_scancode(0xAA, 0);
|
||||
g_pe_svc->inject_scancode(0xB8, 0);
|
||||
g_e0 = 0;
|
||||
handle_key(sc);
|
||||
continue;
|
||||
}
|
||||
g_pe_svc->inject_scancode(sc, g_e0);
|
||||
g_e0 = 0;
|
||||
} else {
|
||||
handle_key(sc);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_quit) return 1; /* 任务栏 QUIT:请求 PE 退出 */
|
||||
|
||||
/* 驱动一帧(redraw_all 内含 PE surface blit 与光标绘制) */
|
||||
redraw_all();
|
||||
flip_buffer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 启动 PE 应用:读 exe → 建窗口 → run_windowed 阻塞运行 → 退出后销毁窗口 */
|
||||
static void launch_pe_app(int app_id) {
|
||||
app_descriptor *app = &g_apps[app_id];
|
||||
if (!g_pe_svc || !g_pe_svc->run_windowed) {
|
||||
slog("pe: service unavailable");
|
||||
return;
|
||||
}
|
||||
if (g_pe_running) {
|
||||
slog("pe: another PE window is running");
|
||||
return;
|
||||
}
|
||||
|
||||
u8 *data = 0; u32 size = 0;
|
||||
if (f32_read_path_lfn(app->pe_file, &data, &size) != 0) {
|
||||
slog("pe: exe not found");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 窗口布局:与 launch_app 相同的级联策略 */
|
||||
int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
int wx = ICON_PAD + g_win_count * 30;
|
||||
int wy = top + 8 + g_win_count * 30;
|
||||
if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD;
|
||||
if (wy + (int)app->default_h > bot - 20) wy = top + 8;
|
||||
|
||||
desktop_window *win = win_create(app_id, app->display_name,
|
||||
wx, wy, (int)app->default_w, (int)app->default_h);
|
||||
if (!win) return;
|
||||
int win_id = win->id;
|
||||
|
||||
int cw = (int)app->default_w - 2 * BORDER_W;
|
||||
int ch = (int)app->default_h - KATE_TITLEBAR_H - BORDER_W;
|
||||
if (cw <= 0 || ch <= 0 || (u64)cw * (u64)ch * 4 > PE_SURFACE_MAX) {
|
||||
win_destroy(win);
|
||||
slog("pe: client area too large for surface");
|
||||
return;
|
||||
}
|
||||
g_pe_surf = (u8 *)PE_SURFACE_ADDR;
|
||||
g_pe_surf_w = cw; g_pe_surf_h = ch;
|
||||
/* 初始化为深色底:PE 窗口未覆盖区域不残留脏数据 */
|
||||
{
|
||||
u32 *p = (u32 *)g_pe_surf;
|
||||
u64 n = (u64)cw * (u64)ch;
|
||||
for (u64 i = 0; i < n; i++) p[i] = 0xFF101418u;
|
||||
}
|
||||
|
||||
win->app_state = &g_pe_state_dummy; /* draw_desktop_page 要求非空才调 on_draw */
|
||||
g_pe_win_id = win_id;
|
||||
g_pe_running = 1;
|
||||
slog("pe: run_windowed");
|
||||
|
||||
/* cmdline:"file.exe args"(GetCommandLineA 返回值) */
|
||||
static char cmdline[160];
|
||||
kstrcpy(cmdline, app->pe_file, (int)sizeof(cmdline));
|
||||
if (app->pe_args && app->pe_args[0]) {
|
||||
kstrcat(cmdline, " ", (int)sizeof(cmdline));
|
||||
kstrcat(cmdline, app->pe_args, (int)sizeof(cmdline));
|
||||
}
|
||||
|
||||
pe_window_host host;
|
||||
host.pump = pe_pump_cb;
|
||||
host.ud = (void *)(u64)win_id; /* 传 id:窗口数组重排不悬空 */
|
||||
host.surface = g_pe_surf;
|
||||
host.width = (u32)cw;
|
||||
host.height = (u32)ch;
|
||||
host.pitch = (u32)cw * 4;
|
||||
|
||||
u64 exit_code = 0;
|
||||
g_pe_svc->run_windowed(data, size, cmdline, &exit_code, &host);
|
||||
|
||||
g_pe_running = 0;
|
||||
g_pe_win_id = -1;
|
||||
g_pe_surf = 0;
|
||||
|
||||
/* PE 已退出:销毁宿主窗口并全屏重绘 */
|
||||
desktop_window *w2 = win_find(win_id);
|
||||
if (w2) win_destroy(w2);
|
||||
files_refresh(); /* PE 可能改动了磁盘 */
|
||||
g_full_redraw = 1;
|
||||
redraw_all();
|
||||
flip_buffer();
|
||||
slog("pe: exited");
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主入口
|
||||
* ============================================================ */
|
||||
@@ -3146,6 +4129,26 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
g_block_write = blk ? (desktop_block_write_fn)*(u64 *)(blk + 24) : 0;
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: Linux 兼容层服务指针(dsk_loader 在 reserved[5] 写入)。
|
||||
* 为 NULL 表示 Linux guest 未启动 / IPC 未就绪,IDE attach 时降级为本地 editor。 */
|
||||
g_lxc_svc = (const linux_compat_service *)ctx->reserved[5];
|
||||
if (g_lxc_svc && g_lxc_svc->magic == LINUX_COMPAT_MAGIC) {
|
||||
slog("lxc service available");
|
||||
} else {
|
||||
g_lxc_svc = 0;
|
||||
slog("lxc service unavailable (IDE will use local editor)");
|
||||
}
|
||||
|
||||
/* P5d: PE 兼容层服务指针(dsk_loader 在 reserved[4] 写入)。
|
||||
* 为 NULL 表示 PE 服务未注册,PEAPPS.CNF 应用启动时降级报错。 */
|
||||
g_pe_svc = (const pe_service *)ctx->reserved[4];
|
||||
if (g_pe_svc && g_pe_svc->magic == PE_SERVICE_MAGIC) {
|
||||
slog("pe service available");
|
||||
} else {
|
||||
g_pe_svc = 0;
|
||||
slog("pe service unavailable");
|
||||
}
|
||||
|
||||
du_context_init(&g_fb, g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch);
|
||||
/* 双缓冲:保存真实 fb,切换到 sprite buffer 渲染 */
|
||||
g_real_fb = (u8 *)g_fb_addr;
|
||||
@@ -3194,10 +4197,53 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
slog("desktop ready");
|
||||
|
||||
/* 主事件循环 */
|
||||
/* P8.1: dev 自动 VSCode 启动测试--无需 GUI 交互即可验证 launch 链路 */
|
||||
static int g_autotest_state = 0;
|
||||
static u64 g_autotest_tsc = 0;
|
||||
|
||||
while (!g_quit) {
|
||||
int need_redraw = 0;
|
||||
int moved = 0;
|
||||
|
||||
/* P8.1: 自动启动测试(先 ping guest,不可用直接报告,不浪费 3s 等待) */
|
||||
if (g_tsc_per_sec && g_autotest_state == 0) {
|
||||
g_autotest_tsc = rdtsc();
|
||||
g_autotest_state = 1;
|
||||
/* P8.1 Fix: 先检测 guest 可用性 */
|
||||
int prc = lxc_guest_ping();
|
||||
if (prc != 0) {
|
||||
slog("[autotest] SKIP: Linux guest not running (no VMX/KVM)");
|
||||
slog("[autotest] To test VSCode: use WSL2+KVM or Linux host with KVM");
|
||||
g_autotest_state = 3; /* 跳过测试 */
|
||||
} else {
|
||||
slog("[autotest] guest OK, waiting 3s before vscode_launch");
|
||||
}
|
||||
}
|
||||
if (g_autotest_state == 1 && g_tsc_per_sec) {
|
||||
u64 elapsed = rdtsc() - g_autotest_tsc;
|
||||
if (elapsed >= (u64)g_tsc_per_sec * 3) {
|
||||
slog("[autotest] triggering vscode_launch");
|
||||
g_autotest_state = 2;
|
||||
vscode_launch();
|
||||
}
|
||||
}
|
||||
if (g_autotest_state == 2 && g_tsc_per_sec) {
|
||||
u64 elapsed = rdtsc() - g_autotest_tsc;
|
||||
/* 15s 后报告结果 */
|
||||
if (elapsed >= (u64)g_tsc_per_sec * 15) {
|
||||
if (g_vscode_state == 2) {
|
||||
slog("[autotest] SUCCESS: scanout online, VSCode running");
|
||||
} else if (g_vscode_state == 1) {
|
||||
slog("[autotest] TIMEOUT: still launching");
|
||||
slog_num("[autotest] phase=", g_launch_phase);
|
||||
} else {
|
||||
slog("[autotest] FAIL: vscode_state=0");
|
||||
slog_num("[autotest] phase=", g_launch_phase);
|
||||
}
|
||||
g_autotest_state = 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* 鼠标:排空所有数据包 */
|
||||
while (ps2_mouse_poll()) moved = 1;
|
||||
|
||||
@@ -3255,6 +4301,31 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
/* VSCode Phase 3/7: IDE attached 时轮询 guest scanout dirty,
|
||||
* 有新帧则触发重绘以 blit 到 framebuffer。查询结果缓存进 g_so
|
||||
* (查询清零 UTSM 侧 dirty + dirty rect),blit 据此局部拷贝。 */
|
||||
if (g_page == 2 && g_ide_attached && g_input_forward_enabled &&
|
||||
g_lxc_svc && g_lxc_svc->gpu_get_scanout_info) {
|
||||
if (g_lxc_svc->gpu_get_scanout_info(&g_so) == 0) {
|
||||
g_so_valid = 1;
|
||||
if (g_so.dirty) need_redraw = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* P7.9: FPS 统计(每秒更新,基于 TSC) */
|
||||
if (g_tsc_per_sec && g_prof_fps_tsc) {
|
||||
u64 elapsed = rdtsc() - g_prof_fps_tsc;
|
||||
if (elapsed >= g_tsc_per_sec) {
|
||||
g_prof_fps = (int)(g_prof_frames * g_tsc_per_sec / elapsed);
|
||||
g_prof_frames = 0;
|
||||
g_prof_fps_tsc = rdtsc();
|
||||
if (g_page == 2) need_redraw = 1; /* 刷新叠加显示 */
|
||||
}
|
||||
} else if (g_tsc_per_sec) {
|
||||
g_prof_fps_tsc = rdtsc();
|
||||
}
|
||||
if (need_redraw && g_page == 2) g_prof_frames++;
|
||||
|
||||
if (need_redraw) {
|
||||
/* 脏矩形渲染:合并脏矩形,只 flip 变化区域 */
|
||||
if (g_full_redraw || g_dirty_count == 0) {
|
||||
|
||||
+106
-34
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../UTSM/include/utsm/dsk.h"
|
||||
#include "port.h"
|
||||
#include "../tools/fat32_lfn.h"
|
||||
|
||||
/* Embedded Logo.png RGBA data (auto-generated by png_to_rgba.py) */
|
||||
#include "logo_data.c"
|
||||
@@ -104,10 +105,13 @@ static block_write_fn g_block_write;
|
||||
static u64 g_tsc_per_ms = 0;
|
||||
static u8 g_disk[131072];
|
||||
static u8 g_cluster[4096];
|
||||
static u8 g_fdata[2097152]; /* 2MB file data buffer (与 UTSM 端一致) */
|
||||
static u8 g_fdata[10485760]; /* 10MB file data buffer (与 UTSM 端一致, 支持 SYSTEM/lib 大型 DLL) */
|
||||
/* BUG-019 修复: FAT 按需扇区读取缓冲区(仅 512 字节) */
|
||||
static u8 g_fat_sec[512];
|
||||
static u8 g_milestone_buf[4096]; /* milestone write cluster buffer */
|
||||
/* 网络租约缓冲:DSK 常驻持有,调度 netman 前经 reserved[7] 暴露指针,
|
||||
* netman 写入 DHCP/静态配置,后续模块(shell/desktop/工具)经同一指针读取。 */
|
||||
static net_lease_info g_net_lease;
|
||||
|
||||
DSK_STAT_DECL(fat32_read);
|
||||
DSK_STAT_DECL(fat32_write);
|
||||
@@ -229,16 +233,23 @@ static int dsk_ini_get_bool(const dsk_ini_config *cfg, const char *sec, const ch
|
||||
|
||||
/* ---- DSK FAT32 子目录遍历 ---- */
|
||||
|
||||
/* 在指定起始 cluster 的目录中查找 name11。
|
||||
/* 在指定起始 cluster 的目录中查找 name11(8.3)或 long_name(LFN 长名)。
|
||||
* long_name 为 NULL 时仅按 8.3 匹配(向后兼容)。
|
||||
* is_dir=1 只匹配子目录,is_dir=0 只匹配文件。
|
||||
* 注意:需先调用 fat32_read_sectors(0,256,g_disk) 确保 BPB 缓冲可用。 */
|
||||
static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir, u32 *out_clus, u32 *out_size) {
|
||||
* 注意:需先调用 fat32_read_sectors(0,256,g_disk) 确保 BPB 缓冲可用。
|
||||
* LFN 缓冲跨簇遍历持续存活,支持跨簇边界的 LFN 链。 */
|
||||
static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir,
|
||||
u32 *out_clus, u32 *out_size, const char *long_name) {
|
||||
const fat32_bpb *bpb = (const fat32_bpb *)g_disk;
|
||||
if(bpb->boot_sig!=0xAA55||bpb->bps!=512) return -2;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
|
||||
/* LFN 缓冲必须在簇循环外声明:跨簇 LFN 链需要持续存活 */
|
||||
fat32_lfn_buf lfn;
|
||||
fat32_lfn_init(&lfn);
|
||||
|
||||
u32 clus = start_clus;
|
||||
while(clus>=2 && clus<0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus-2)*spc;
|
||||
@@ -249,18 +260,40 @@ static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir, u32
|
||||
if(fat32_read_sectors(lba,8,g_cluster)!=0) return -3;
|
||||
cb = g_cluster;
|
||||
}
|
||||
const fat32_de *dir = (const fat32_de *)cb;
|
||||
for(u32 e=0; e*32<spc*512; e++) {
|
||||
if(dir[e].name[0]==0) break;
|
||||
if((u8)dir[e].name[0]==0xE5) continue;
|
||||
if(dir[e].attr==0x0F) continue;
|
||||
if(dir[e].attr&0x08) continue;
|
||||
if(!neq11(dir[e].name,name11)) continue;
|
||||
int entry_is_dir = (dir[e].attr&0x10)?1:0;
|
||||
if(entry_is_dir!=is_dir) continue;
|
||||
*out_clus = r16((const u8*)&dir[e].clow)|((u32)r16((const u8*)&dir[e].chigh)<<16);
|
||||
*out_size = dir[e].fsize;
|
||||
return 0;
|
||||
const u8 *entries = (const u8 *)cb;
|
||||
u32 entry_count = (spc*512)/32;
|
||||
for(u32 e=0; e<entry_count; e++) {
|
||||
const u8 *entry = entries + e*32;
|
||||
if(entry[0]==0) break;
|
||||
if((u8)entry[0]==0xE5) { fat32_lfn_init(&lfn); continue; }
|
||||
const fat32_de *de = (const fat32_de *)entry;
|
||||
int is_short = fat32_lfn_process(&lfn, entry);
|
||||
if(!is_short) continue; /* LFN 片段 */
|
||||
if(de->attr&0x08) { fat32_lfn_init(&lfn); continue; } /* 卷标 */
|
||||
int entry_is_dir = (de->attr&0x10)?1:0;
|
||||
if(entry_is_dir!=is_dir) { fat32_lfn_init(&lfn); continue; }
|
||||
/* 匹配检查:先 8.3 短名,再 LFN 长名 */
|
||||
int matched = 0;
|
||||
if(name11 && neq11(de->name, name11)) {
|
||||
matched = 1;
|
||||
} else if(long_name) {
|
||||
if(lfn.valid) {
|
||||
char ascii[FAT32_LFN_MAX];
|
||||
int n = fat32_lfn_to_ascii(&lfn, ascii, sizeof(ascii));
|
||||
if(n>=0 && fat32_lfn_streq_ci(ascii, long_name)) matched = 1;
|
||||
}
|
||||
if(!matched) {
|
||||
char short_disp[13];
|
||||
fat32_lfn_short_to_str((const u8 *)de->name, short_disp);
|
||||
if(fat32_lfn_streq_ci(short_disp, long_name)) matched = 1;
|
||||
}
|
||||
}
|
||||
if(matched) {
|
||||
*out_clus = r16((const u8*)&de->clow)|((u32)r16((const u8*)&de->chigh)<<16);
|
||||
*out_size = de->fsize;
|
||||
return 0;
|
||||
}
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
/* BUG-019: 使用按需 FAT 读取 */
|
||||
clus = fat32_get_next_cluster(fat_byte_off, clus, bpb->rsvd);
|
||||
@@ -268,7 +301,7 @@ static int fat32_find_in_dir(u32 start_clus, const char *name11, int is_dir, u32
|
||||
return -4;
|
||||
}
|
||||
|
||||
/* 按 '/' 分隔的 8.3 路径读取文件(如 "SYSTEM /DESHAB64 /FUCK ")。
|
||||
/* 按 '/' 分隔的路径读取文件。支持 8.3 路径与长名路径两种格式。
|
||||
* 需先调用 fat32_read_sectors(0,256,g_disk) 确保 BPB 可用。 */
|
||||
static int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) {
|
||||
DSK_TS_DECL(fat32_path_read);
|
||||
@@ -285,15 +318,22 @@ static int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) {
|
||||
|
||||
while(pos<path_len) {
|
||||
char comp[12]; u32 ci=0;
|
||||
while(pos<path_len&&path[pos]!='/'&&ci<11) comp[ci++]=path[pos++];
|
||||
char orig[260]; u32 oi=0;
|
||||
while(pos<path_len&&path[pos]!='/') {
|
||||
if(ci<11) comp[ci++]=path[pos];
|
||||
if(oi+1<sizeof(orig)) orig[oi++]=path[pos];
|
||||
pos++;
|
||||
}
|
||||
while(ci<11) comp[ci++]=' ';
|
||||
comp[11]=0;
|
||||
orig[oi]=0;
|
||||
if(pos<path_len&&path[pos]=='/') pos++;
|
||||
int is_last = (pos>=path_len);
|
||||
|
||||
if(is_last) {
|
||||
u32 found_clus=0, found_size=0;
|
||||
if(fat32_find_in_dir(cur_clus,comp,0,&found_clus,&found_size)!=0) { logl("[DSK] fat32_read_path: file not found: "); swrite(path); swrite("\n"); return -4; }
|
||||
if(fat32_find_in_dir(cur_clus,comp,0,&found_clus,&found_size,
|
||||
(oi>0 && oi!=11)?orig:0)!=0) { logl("[DSK] fat32_read_path: file not found: "); swrite(path); swrite("\n"); return -4; }
|
||||
if(found_size>sizeof(g_fdata)) { logh("[DSK] fat32_read_path: file too large size=", (u64)found_size); return -5; }
|
||||
u8 *dst=g_fdata; u32 remaining=found_size; u32 fc=found_clus;
|
||||
while(fc>=2&&fc<0x0FFFFFF8&&remaining>0) {
|
||||
@@ -317,7 +357,8 @@ static int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) {
|
||||
return 0;
|
||||
} else {
|
||||
u32 sub_clus=0, sub_size=0;
|
||||
if(fat32_find_in_dir(cur_clus,comp,1,&sub_clus,&sub_size)!=0) { logl("[DSK] fat32_read_path: dir not found in path: "); swrite(path); swrite("\n"); return -7; }
|
||||
if(fat32_find_in_dir(cur_clus,comp,1,&sub_clus,&sub_size,
|
||||
(oi>0 && oi!=11)?orig:0)!=0) { logl("[DSK] fat32_read_path: dir not found in path: "); swrite(path); swrite("\n"); return -7; }
|
||||
cur_clus=sub_clus;
|
||||
}
|
||||
}
|
||||
@@ -325,7 +366,7 @@ static int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) {
|
||||
return -8;
|
||||
}
|
||||
|
||||
/* Navigate a '/'-separated 8.3 path to find a directory cluster.
|
||||
/* Navigate a '/'-separated path (8.3 or LFN) to find a directory cluster.
|
||||
* Returns the cluster of the final directory, or 0 on failure.
|
||||
* Requires g_disk to contain a valid BPB. */
|
||||
static u32 fat32_find_dir_cluster(const char *path) {
|
||||
@@ -336,12 +377,19 @@ static u32 fat32_find_dir_cluster(const char *path) {
|
||||
u32 cur_clus = bpb->root_clus;
|
||||
while (pos < path_len) {
|
||||
char comp[12]; u32 ci = 0;
|
||||
while (pos < path_len && path[pos] != '/' && ci < 11) comp[ci++] = path[pos++];
|
||||
char orig[260]; u32 oi = 0;
|
||||
while (pos < path_len && path[pos] != '/') {
|
||||
if (ci < 11) comp[ci++] = path[pos];
|
||||
if (oi + 1 < sizeof(orig)) orig[oi++] = path[pos];
|
||||
pos++;
|
||||
}
|
||||
while (ci < 11) comp[ci++] = ' ';
|
||||
comp[11] = 0;
|
||||
orig[oi] = 0;
|
||||
if (pos < path_len && path[pos] == '/') pos++;
|
||||
u32 sub_clus = 0, sub_size = 0;
|
||||
if (fat32_find_in_dir(cur_clus, comp, 1, &sub_clus, &sub_size) != 0) return 0;
|
||||
if (fat32_find_in_dir(cur_clus, comp, 1, &sub_clus, &sub_size,
|
||||
(oi > 0 && oi != 11) ? orig : 0) != 0) return 0;
|
||||
cur_clus = sub_clus;
|
||||
}
|
||||
return cur_clus;
|
||||
@@ -1233,6 +1281,28 @@ static void dsk_persist_userconf(const dsk_boot_context *ctx) {
|
||||
ctx_mut->reserved[1] = 0;
|
||||
}
|
||||
|
||||
/* 调度 netman.elf:先初始化租约缓冲并经 reserved[7] 暴露给 netman/后续工具。
|
||||
* 每次启动(含 is_first=0)都应调用,保证联网配置在进桌面前就绪。 */
|
||||
static void dsk_schedule_netman(const dsk_boot_context *ctx, int load_netman) {
|
||||
dsk_boot_context *ctx_mut = (dsk_boot_context *)ctx;
|
||||
g_net_lease.magic = NET_LEASE_MAGIC;
|
||||
g_net_lease.flags = 0;
|
||||
g_net_lease.ip = 0; g_net_lease.gateway = 0;
|
||||
g_net_lease.dns = 0; g_net_lease.server = 0;
|
||||
g_net_lease.reserved[0] = 0; g_net_lease.reserved[1] = 0;
|
||||
ctx_mut->reserved[7] = (u64)&g_net_lease;
|
||||
if (!load_netman) {
|
||||
logl("[DSK] netman skipped by FUCK config");
|
||||
return;
|
||||
}
|
||||
logl("[DSK:SCHED] scheduling netman.elf");
|
||||
char nn[12]; /* NETMAN ELF */
|
||||
nn[0]='N';nn[1]='E';nn[2]='T';nn[3]='M';nn[4]='A';nn[5]='N';nn[6]=' ';nn[7]=' ';nn[8]='E';nn[9]='L';nn[10]='F';
|
||||
logl("[DSK] loading netman");
|
||||
if (dsk_load_and_run(nn, ctx) == 0) logl("[DSK] netman returned");
|
||||
else logl("[DSK] netman not found");
|
||||
}
|
||||
|
||||
/* Draw the spinning loader (comet-tail arc) on the gradient background,
|
||||
* then run the firstInit check and dispatch system component loading.
|
||||
* FUCK config controls: show_logo, skip_login, mouse_init, netman, dev_mode. */
|
||||
@@ -1298,6 +1368,10 @@ static void dsk_spinner_and_check(const void *boot_ctx_ptr) {
|
||||
int load_mouse = dsk_cfg_ok ? dsk_ini_get_bool(&dsk_cfg, "dsk", "mouse_init", 1) : 1;
|
||||
int load_netman = dsk_cfg_ok ? dsk_ini_get_bool(&dsk_cfg, "dsk", "netman", 1) : 1;
|
||||
int dev_mode = dsk_cfg_ok ? dsk_ini_get_bool(&dsk_cfg, "boot", "dev_mode", 0) : 0;
|
||||
/* [compat] 段: DSK 侧只读不写,仅记录 PE DLL 搜索开关(实际加载在 UTSM 侧) */
|
||||
int pe_dll_search = dsk_cfg_ok ? dsk_ini_get_bool(&dsk_cfg, "compat", "pe_dll_search", 1) : 1;
|
||||
if (pe_dll_search) logl("[DSK] compat: PE DLL search enabled (UTSM side)");
|
||||
else logl("[DSK] compat: PE DLL search disabled");
|
||||
milestone_write(3, "FUCK config loaded");
|
||||
|
||||
/* Gradient background + centered Logo.png (5 seconds) */
|
||||
@@ -1356,6 +1430,9 @@ static void dsk_spinner_and_check(const void *boot_ctx_ptr) {
|
||||
/* 开发者模式:加载 shell.elf 自动测试命令序列(cp/mv/echo/ls/cat/rm) */
|
||||
if (dev_mode) {
|
||||
logl("[DSK:SCHED] decision: dev_mode -> shell.elf");
|
||||
/* dev_mode 同样需要网络就绪:先调度 netman(DHCP/静态 + ARP/DNS 自验),
|
||||
* 租约写入 g_net_lease 供 shell 内 ping/curl/browser 经 reserved[7] 取用。 */
|
||||
dsk_schedule_netman(ctx, load_netman);
|
||||
char sn[12]; /* SHELL ELF */
|
||||
sn[0]='S';sn[1]='H';sn[2]='E';sn[3]='L';sn[4]='L';sn[5]=' ';sn[6]=' ';sn[7]=' ';sn[8]='E';sn[9]='L';sn[10]='F';
|
||||
dsk_boot_context *ctx_mut = (dsk_boot_context *)ctx;
|
||||
@@ -1367,7 +1444,11 @@ static void dsk_spinner_and_check(const void *boot_ctx_ptr) {
|
||||
}
|
||||
|
||||
if (!is_first) {
|
||||
logl("[DSK:SCHED] decision: is_first=0 -> login then desktop");
|
||||
logl("[DSK:SCHED] decision: is_first=0 -> netman -> login then desktop");
|
||||
|
||||
/* netman.elf — 每次启动都调度,进桌面前获取 IP(可通过 FUCK netman=0 跳过) */
|
||||
dsk_schedule_netman(ctx, load_netman);
|
||||
milestone_write(7, "netman loaded");
|
||||
|
||||
/* login — 可通过 skip_login=1 跳过 */
|
||||
if (skip_login) {
|
||||
@@ -1453,16 +1534,7 @@ static void dsk_spinner_and_check(const void *boot_ctx_ptr) {
|
||||
}
|
||||
|
||||
/* netman.elf — 可通过 FUCK netman=0 跳过 */
|
||||
if (load_netman) {
|
||||
logl("[DSK:SCHED] scheduling netman.elf");
|
||||
char nn[12]; /* NETMAN ELF */
|
||||
nn[0]='N';nn[1]='E';nn[2]='T';nn[3]='M';nn[4]='A';nn[5]='N';nn[6]=' ';nn[7]=' ';nn[8]='E';nn[9]='L';nn[10]='F';
|
||||
logl("[DSK] loading netman");
|
||||
if (dsk_load_and_run(nn, ctx) == 0) logl("[DSK] netman returned");
|
||||
else logl("[DSK] netman not found");
|
||||
} else {
|
||||
logl("[DSK] netman skipped by FUCK config");
|
||||
}
|
||||
dsk_schedule_netman(ctx, load_netman);
|
||||
milestone_write(7, "netman loaded");
|
||||
|
||||
/* FirstInit.elf — user setup wizard; returns to DSK when done */
|
||||
|
||||
Binary file not shown.
@@ -196,6 +196,30 @@ echo "[arch-utsm] Arch Linux rootfs booted"
|
||||
echo "[arch-utsm] Kernel: $(uname -r)"
|
||||
echo "[arch-utsm] Architecture: $(uname -m)"
|
||||
|
||||
# ===== VSCode Phase 4: persistent rw volume (/dev/vdc) =====
|
||||
# The extra-rootfs image is exposed as a WRITABLE memory-backed virtio-blk
|
||||
# device (slot6, GPA 0xF4006000, IRQ11) by the UTSM extra_rootfs backend.
|
||||
# We mount it at /mnt/persist and bind-mount its subtrees over the volatile
|
||||
# paths VSCode and user data live in, so they survive tmpfs cleanup and
|
||||
# (Phase 7) can be written back to the FAT32 ESP for true persistence.
|
||||
if [ -b /dev/vdc ]; then
|
||||
mkdir -p /mnt/persist
|
||||
if mount -t ext4 -o rw /dev/vdc /mnt/persist 2>/dev/null; then
|
||||
echo "[arch-utsm] /dev/vdc mounted rw at /mnt/persist (extra-rootfs)"
|
||||
# Persistent subtrees: /opt (VSCode install), /home (user data),
|
||||
# /root (daemon cwd), /var/lib/pacman is left on tmpfs (rebuildable).
|
||||
for d in opt home root; do
|
||||
mkdir -p "/mnt/persist/$d"
|
||||
mount --bind "/mnt/persist/$d" "/$d" 2>/dev/null && \
|
||||
echo "[arch-utsm] bind: /$d → persist" || true
|
||||
done
|
||||
else
|
||||
echo "[arch-utsm] WARNING: /dev/vdc mount failed (not ext4?) — rw layer disabled"
|
||||
fi
|
||||
else
|
||||
echo "[arch-utsm] no /dev/vdc — running fully volatile (tmpfs only)"
|
||||
fi
|
||||
|
||||
# Check if pacman is available
|
||||
if command -v pacman &>/dev/null; then
|
||||
echo "[arch-utsm] pacman available: $(pacman --version | head -1)"
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build the persistent rw ext4 volume for the UTSM Linux guest (VSCode Phase 4).
|
||||
#
|
||||
# Produces a sparse-friendly ext4 image exposed to the guest as a WRITABLE
|
||||
# virtio-blk device (/dev/vdc, slot6 @ GPA 0xF4006000 IRQ11) by the UTSM
|
||||
# extra_rootfs backend. The Arch /sbin/init mounts it at /mnt/persist and
|
||||
# bind-mounts its subtrees over /opt, /home and /root, giving VSCode and
|
||||
# user data a persistent home across the read-only rootfs.
|
||||
#
|
||||
# Output:
|
||||
# SYSTEM/boot/linux-extra-rootfs.img (ext4 image, default 1024MB)
|
||||
#
|
||||
# After generating, uncomment the two extra-rootfs module lines in
|
||||
# SYSTEM/limine/limine.conf so Limine preloads it as a boot module.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - WSL or native Linux with root access
|
||||
# - mkfs.ext4, losetup, mount (or debugfs for unprivileged population)
|
||||
#
|
||||
# Usage:
|
||||
# sudo bash CODE/linux/build_extra_rootfs.sh # 1GB default
|
||||
# sudo IMG_SIZE_MB=2048 bash CODE/linux/build_extra_rootfs.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot"
|
||||
OUTPUT_IMG="$OUTPUT_DIR/linux-extra-rootfs.img"
|
||||
IMG_SIZE_MB=${IMG_SIZE_MB:-1024}
|
||||
|
||||
WORK_DIR="${EXTRA_ROOTFS_WORK:-/root/extra_rootfs_work}"
|
||||
MNT_DIR="$WORK_DIR/mnt"
|
||||
|
||||
echo "[extra-rootfs] Building persistent rw volume"
|
||||
echo "[extra-rootfs] Work dir: $WORK_DIR (native FS)"
|
||||
echo "[extra-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)"
|
||||
|
||||
mkdir -p "$WORK_DIR" "$MNT_DIR" "$OUTPUT_DIR"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "[extra-rootfs] ERROR: must run as root (loop mount required)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Allocate the image file (seek-truncate: sparse on supporting filesystems)
|
||||
echo "[extra-rootfs] Allocating ${IMG_SIZE_MB}MB image..."
|
||||
dd if=/dev/zero of="$WORK_DIR/extra-rootfs.img" bs=1M count=0 seek="$IMG_SIZE_MB" status=none
|
||||
|
||||
# 2. Create the ext4 filesystem.
|
||||
# -N 1048576 caps inode count (1M inodes is plenty for VSCode + user data
|
||||
# and keeps the inode table small); -O ^has_journal skips the journal —
|
||||
# the volume is memory-backed (writes are volatile until Phase 7 writeback),
|
||||
# so journal overhead buys nothing.
|
||||
echo "[extra-rootfs] mkfs.ext4..."
|
||||
mkfs.ext4 -q -F -N 1048576 -O ^has_journal -L deshab-persist "$WORK_DIR/extra-rootfs.img"
|
||||
|
||||
# 3. Populate the base directory skeleton via loop mount.
|
||||
echo "[extra-rootfs] Populating directory skeleton..."
|
||||
LOOP_DEV="$(losetup --find --show "$WORK_DIR/extra-rootfs.img")"
|
||||
trap 'losetup -d "$LOOP_DEV" 2>/dev/null || true' EXIT
|
||||
|
||||
mount -t ext4 "$LOOP_DEV" "$MNT_DIR"
|
||||
mkdir -p "$MNT_DIR/opt" "$MNT_DIR/home" "$MNT_DIR/root" "$MNT_DIR/vscode"
|
||||
# /mnt/persist/vscode will hold the VSCode tarball extraction target marker;
|
||||
# /opt bind-mount covers /opt/vscode at runtime.
|
||||
touch "$MNT_DIR/vscode/.keep"
|
||||
sync
|
||||
umount "$MNT_DIR"
|
||||
losetup -d "$LOOP_DEV"
|
||||
trap - EXIT
|
||||
|
||||
# 4. Copy the final image to the Windows side (SYSTEM/boot).
|
||||
echo "[extra-rootfs] Copying to $OUTPUT_IMG ..."
|
||||
cp "$WORK_DIR/extra-rootfs.img" "$OUTPUT_IMG"
|
||||
|
||||
echo "[extra-rootfs] Done: $OUTPUT_IMG ($(du -h "$OUTPUT_IMG" | cut -f1) on disk)"
|
||||
echo "[extra-rootfs] Next: uncomment the extra-rootfs module lines in"
|
||||
echo "[extra-rootfs] SYSTEM/limine/limine.conf, then rebuild the ESP image."
|
||||
@@ -130,6 +130,34 @@ CONFIG_VIRTIO_CONSOLE=y
|
||||
CONFIG_HW_RANDOM=y
|
||||
CONFIG_HW_RANDOM_VIRTIO=y
|
||||
|
||||
# DRM + virtio-gpu (VSCode integration Phase 3: guest graphical output)
|
||||
# Provides /dev/dri/card0 for Xorg modesetting + scanout to host framebuffer
|
||||
CONFIG_DRM=y
|
||||
CONFIG_DRM_VIRTIO_GPU=y
|
||||
CONFIG_DRM_FBDEV_EMULATION=y
|
||||
CONFIG_FB=y
|
||||
CONFIG_FB_DEFERRED_IO=y
|
||||
CONFIG_FRAMEBUFFER_CONSOLE=y
|
||||
CONFIG_FB_TILEBLITTING=y
|
||||
|
||||
# VT + console (X server needs /dev/tty0 + VT_SWITCH)
|
||||
CONFIG_VT=y
|
||||
CONFIG_VT_CONSOLE=y
|
||||
CONFIG_HW_CONSOLE=y
|
||||
CONFIG_VT_HW_CONSOLE_BINDING=y
|
||||
|
||||
# Input evdev (VSCode integration Phase 2: virtio-input event devices)
|
||||
# Required for /dev/input/eventX — X server evdev driver reads these
|
||||
CONFIG_INPUT_EVDEV=y
|
||||
|
||||
# OverlayFS (VSCode integration Phase 4: rw layer over read-only rootfs)
|
||||
# /sbin/init mounts overlay -o lowerdir=/,upperdir=/tmp/rw/upper,workdir=/tmp/rw/work /
|
||||
CONFIG_OVERLAY_FS=y
|
||||
|
||||
# tmpfs xattr (overlayfs workdir needs xattr support)
|
||||
CONFIG_TMPFS_XATTR=y
|
||||
CONFIG_TMPFS_POSIX_ACL=y
|
||||
|
||||
# UTSM dual-kernel hypercall and IPC driver (Phase 1.3)
|
||||
CONFIG_UTSM_HCALL=y
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
|
||||
/* Shared IPC protocol — must match UTSM side */
|
||||
#include "../../../utsm-ipc/ipc_proto.h"
|
||||
@@ -57,6 +58,38 @@ struct utsm_ioctl_pool {
|
||||
|
||||
static int g_utsm_fd = -1;
|
||||
|
||||
/* pool_write 定义在文件传输段(下文),exec 同步路径复用同一通道 */
|
||||
static int pool_write(unsigned int offset, const void *buf, unsigned int len);
|
||||
static int send_msg(unsigned int type, const void *data, unsigned int len);
|
||||
static void err_write(const char *s, unsigned int len);
|
||||
|
||||
/* ===== exec 超时看门狗(P3 补完) =====
|
||||
* park-and-resume 模型下 UTSM 在 guest 运行期间完全不执行,宿主侧无法
|
||||
* 看门狗。子进程超时必须 guest 内兜底:alarm + SIGALRM → SIGKILL,
|
||||
* 保证 daemon 在 UTSM_EXEC_TIMEOUT_SEC 内一定 park,整系统不冻结。 */
|
||||
static volatile sig_atomic_t g_exec_timed_out = 0;
|
||||
static volatile sig_atomic_t g_exec_child = -1;
|
||||
|
||||
static void on_exec_alarm(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
g_exec_timed_out = 1;
|
||||
if (g_exec_child > 0)
|
||||
kill((pid_t)g_exec_child, SIGKILL);
|
||||
}
|
||||
|
||||
/* EXEC_EXIT 必须送达:ring 满(-EAGAIN)时短暂重试。新协议下 stdout
|
||||
* 走 payload_pool,ring 内通常只有这一条消息,满的概率极低。 */
|
||||
static void send_exit_msg(const struct ipc_exec_exit *ex)
|
||||
{
|
||||
for (int t = 0; t < 200; t++) {
|
||||
if (send_msg(UTSM_MSG_EXEC_EXIT, ex, sizeof(*ex)) == 0)
|
||||
return;
|
||||
usleep(1000);
|
||||
}
|
||||
err_write("utsm_exec_daemon: EXEC_EXIT send failed (ring full)\n", 52);
|
||||
}
|
||||
|
||||
/* Send a typed message to UTSM via the linux_to_utsm ring */
|
||||
static int send_msg(unsigned int type, const void *data, unsigned int len)
|
||||
{
|
||||
@@ -111,8 +144,9 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
int argc = (int)req->argc;
|
||||
if (argc > UTSM_EXEC_MAX_ARGS) argc = UTSM_EXEC_MAX_ARGS;
|
||||
if (argc < 1) {
|
||||
struct ipc_exec_exit ex = { .exit_code = 1, .reserved = 0 };
|
||||
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
|
||||
struct ipc_exec_exit ex = { .exit_code = 1, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,11 +163,49 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
}
|
||||
argv[argc] = NULL;
|
||||
|
||||
/* VSCode Phase 5: ASYNC launch path.
|
||||
* The caller (desktop IDE attach / app icon) wants to start a long-running
|
||||
* GUI program (X server, VSCode) without blocking the UTSM side for its
|
||||
* whole lifetime. We fork, detach the child into its own session with
|
||||
* stdio routed to /dev/null, reply EXEC_EXIT(0) immediately (0 = spawned
|
||||
* ok), and never waitpid — SIGCHLD is SIG_IGN so the kernel reaps it. */
|
||||
if (req->flags & UTSM_EXEC_FLAG_ASYNC) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
struct ipc_exec_exit ex = { .exit_code = 127, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
if (pid == 0) {
|
||||
/* Child: new session, no controlling tty, stdio → /dev/null */
|
||||
setsid();
|
||||
int nullfd = open("/dev/null", O_RDWR);
|
||||
if (nullfd >= 0) {
|
||||
dup2(nullfd, STDIN_FILENO);
|
||||
dup2(nullfd, STDOUT_FILENO);
|
||||
dup2(nullfd, STDERR_FILENO);
|
||||
if (nullfd > STDERR_FILENO) close(nullfd);
|
||||
}
|
||||
/* 与同步路径一致的库搜索路径;DISPLAY 指向 virtio-gpu X server */
|
||||
setenv("LD_LIBRARY_PATH", "/usr/lib/deshab", 1);
|
||||
setenv("DISPLAY", ":0", 1);
|
||||
execv(req->path, argv);
|
||||
_exit(127); /* execv 仅失败时返回 */
|
||||
}
|
||||
/* Parent: report successful spawn immediately */
|
||||
struct ipc_exec_exit ex = { .exit_code = 0, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a pipe for child stdout+stderr */
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) != 0) {
|
||||
struct ipc_exec_exit ex = { .exit_code = 126, .reserved = 0 };
|
||||
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
|
||||
struct ipc_exec_exit ex = { .exit_code = 126, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,8 +214,9 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
/* fork failed */
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
struct ipc_exec_exit ex = { .exit_code = 127, .reserved = 0 };
|
||||
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
|
||||
struct ipc_exec_exit ex = { .exit_code = 127, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,6 +227,9 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
|
||||
/* 设置 LD_LIBRARY_PATH,让 ld-linux.so 在 /usr/lib/deshab 查找 .so */
|
||||
setenv("LD_LIBRARY_PATH", "/usr/lib/deshab", 1);
|
||||
|
||||
execv(req->path, argv);
|
||||
/* execv only returns on failure */
|
||||
const char *err1 = "utsm_exec_daemon: exec failed: ";
|
||||
@@ -167,30 +243,80 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
/* Parent: read child output from pipe, stream to UTSM */
|
||||
/* Parent: read child output from pipe, stage into payload_pool.
|
||||
* P3 大输出改造:旧路径逐条 EXEC_STDOUT 入 ring(64 槽 × 240B ≈ 15KB,
|
||||
* 满则 -EAGAIN 静默丢弃);现直写 payload_pool(~1MB),EXEC_EXIT
|
||||
* 携带 stdout_len/stdout_total,UTSM 侧按新协议从 pool 拷出。 */
|
||||
close(pipefd[1]);
|
||||
|
||||
char buf[UTSM_IPC_MSG_DATA_SIZE];
|
||||
/* 安装超时看门狗:UTSM_EXEC_TIMEOUT_SEC 内子进程不退 → SIGKILL。
|
||||
* 不设 SA_RESTART:SIGALRM 中断 read(EINTR),随后子进程死亡 pipe EOF。 */
|
||||
g_exec_timed_out = 0;
|
||||
g_exec_child = pid;
|
||||
{
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = on_exec_alarm;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGALRM, &sa, NULL);
|
||||
alarm(UTSM_EXEC_TIMEOUT_SEC);
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
unsigned int pool_off = 0;
|
||||
unsigned int total = 0;
|
||||
int truncated = 0;
|
||||
ssize_t n;
|
||||
while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
|
||||
send_msg(UTSM_MSG_EXEC_STDOUT, buf, (unsigned int)n);
|
||||
for (;;) {
|
||||
n = read(pipefd[0], buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
total += (unsigned int)n;
|
||||
if (!truncated) {
|
||||
unsigned int room = (pool_off < UTSM_IPC_PAYLOAD_SIZE)
|
||||
? (UTSM_IPC_PAYLOAD_SIZE - pool_off) : 0;
|
||||
unsigned int chunk = ((unsigned int)n < room) ? (unsigned int)n : room;
|
||||
if (chunk > 0) {
|
||||
if (pool_write(pool_off, buf, chunk) != 0) {
|
||||
/* pool 写入失败:后续输出全部丢弃,记截断 */
|
||||
truncated = 1;
|
||||
} else {
|
||||
pool_off += chunk;
|
||||
}
|
||||
}
|
||||
if (chunk < (unsigned int)n)
|
||||
truncated = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue; /* SIGALRM 打断;子进程已被 SIGKILL,下轮 EOF */
|
||||
break; /* EOF 或真实错误 */
|
||||
}
|
||||
close(pipefd[0]);
|
||||
|
||||
/* Wait for child and send exit code */
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
alarm(0);
|
||||
g_exec_child = -1;
|
||||
|
||||
struct ipc_exec_exit ex;
|
||||
ex.reserved = 0;
|
||||
if (WIFEXITED(status)) {
|
||||
ex.flags = UTSM_EXEC_EXIT_F_POOL;
|
||||
ex.stdout_len = pool_off;
|
||||
ex.stdout_total = total;
|
||||
if (truncated)
|
||||
ex.flags |= UTSM_EXEC_EXIT_F_TRUNC;
|
||||
if (g_exec_timed_out) {
|
||||
ex.flags |= UTSM_EXEC_EXIT_F_TIMEOUT;
|
||||
ex.exit_code = 124; /* GNU timeout 惯例 */
|
||||
} else if (WIFEXITED(status)) {
|
||||
ex.exit_code = (unsigned int)WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
ex.exit_code = 128u + (unsigned int)WTERMSIG(status);
|
||||
} else {
|
||||
ex.exit_code = 1;
|
||||
}
|
||||
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
|
||||
send_exit_msg(&ex);
|
||||
}
|
||||
|
||||
/* ===== File transfer handlers (Phase 3) =====
|
||||
@@ -423,6 +549,10 @@ static void handle_file_write(const struct ipc_file_request *req)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* ASYNC children (VSCode Phase 5: X server / VSCode) are never waitpid'd;
|
||||
* SIG_IGN lets the kernel reap them automatically so they don't zombie. */
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
g_utsm_fd = open("/dev/utsm", O_RDWR);
|
||||
if (g_utsm_fd < 0) {
|
||||
/* Cannot communicate with UTSM — exit */
|
||||
|
||||
@@ -63,6 +63,36 @@ if [ $VDB_OK -eq 1 ]; then
|
||||
mount --move /dev /mnt/root/dev 2>/dev/null
|
||||
mount --move /proc /mnt/root/proc 2>/dev/null
|
||||
mount --move /sys /mnt/root/sys 2>/dev/null
|
||||
|
||||
# ===== VSCode Phase 4: overlay 持久化(/dev/vdc extra-rootfs 可写卷)=====
|
||||
# rootfs 以 ro 挂载;/opt(VSCode 安装位置)、/home、/root 通过
|
||||
# overlayfs 获得可写层,upperdir/workdir 落在 /dev/vdc(virtio-blk
|
||||
# slot6 可写后端,内容跨启动持久)。vdc 缺失时保持 ro,安全降级。
|
||||
if [ -b /dev/vdc ]; then
|
||||
echo "[utsm-linux] /dev/vdc found, setting up overlay persistence..."
|
||||
modprobe overlay 2>/dev/null
|
||||
mkdir -p /mnt/persist
|
||||
if mount -t ext4 -o rw /dev/vdc /mnt/persist 2>/dev/null; then
|
||||
for d in opt home root; do
|
||||
mkdir -p /mnt/persist/overlay/$d/upper \
|
||||
/mnt/persist/overlay/$d/work \
|
||||
/mnt/root/$d
|
||||
if mount -t overlay overlay \
|
||||
-o lowerdir=/mnt/root/$d,upperdir=/mnt/persist/overlay/$d/upper,workdir=/mnt/persist/overlay/$d/work \
|
||||
/mnt/root/$d 2>/dev/null; then
|
||||
echo "[utsm-linux] overlay: /$d rw (persist)"
|
||||
else
|
||||
echo "[utsm-linux] WARNING: overlay /$d failed (stays ro)"
|
||||
fi
|
||||
done
|
||||
# persist 卷搬进新 root,switch_root 后可在 /mnt/persist 访问
|
||||
mkdir -p /mnt/root/mnt/persist
|
||||
mount --move /mnt/persist /mnt/root/mnt/persist 2>/dev/null
|
||||
else
|
||||
echo "[utsm-linux] WARNING: /dev/vdc mount failed (no persistence)"
|
||||
fi
|
||||
fi
|
||||
|
||||
exec switch_root /mnt/root /sbin/init
|
||||
# switch_root does not return
|
||||
else
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
#define UTSM_IOCTL_READ_POOL _IOWR('U', 6, struct utsm_ioctl_pool)
|
||||
#define UTSM_IOCTL_HOST_READ _IOWR('U', 7, struct utsm_ioctl_hostfile)
|
||||
#define UTSM_IOCTL_HOST_WRITE _IOW('U', 8, struct utsm_ioctl_hostfile)
|
||||
/* VSCode integration Phase 1: query graphics surface pool GPA + size.
|
||||
* Userspace then mmaps /dev/utsm (offset 0, len = size) to obtain a
|
||||
* writable scanout buffer shared with the UTSM host. */
|
||||
#define UTSM_IOCTL_SURFACE_INFO _IOR('U', 9, struct utsm_ioctl_surface)
|
||||
|
||||
/* Ioctl message wrapper: carries type + data in a single call */
|
||||
struct utsm_ioctl_msg {
|
||||
@@ -89,6 +93,15 @@ struct utsm_ioctl_hostfile {
|
||||
char name[64]; /* UTSM FAT32 root file name (NUL-terminated) */
|
||||
};
|
||||
|
||||
/* VSCode integration Phase 1: graphics surface pool descriptor.
|
||||
* Returned by UTSM_IOCTL_SURFACE_INFO. Userspace then mmaps /dev/utsm
|
||||
* (offset 0, len = size) to obtain a writable scanout buffer that UTSM
|
||||
* reads to blit guest graphics to the Deshab framebuffer. */
|
||||
struct utsm_ioctl_surface {
|
||||
u64 gpa; /* out: guest physical address of surface pool */
|
||||
u64 size; /* out: surface pool size in bytes */
|
||||
};
|
||||
|
||||
/* ===== Hypercall ABI =====
|
||||
*
|
||||
* Linux calls VMCALL with:
|
||||
@@ -143,6 +156,22 @@ static inline long utsm_hcall_shm_info(u64 *gpa_out, u64 *size_out)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* VSCode integration Phase 1: query graphics surface pool.
|
||||
* Same GVA==GPA convention as utsm_hcall_shm_info. Returns 0 on success
|
||||
* and fills *gpa_out + *size_out; negative UTSM_HCALL_* on error. */
|
||||
static inline long utsm_hcall_surface_info(u64 *gpa_out, u64 *size_out)
|
||||
{
|
||||
u64 gpa = 0, size = 0;
|
||||
long ret;
|
||||
|
||||
ret = utsm_hcall(UTSM_HCALL_SURFACE_INFO, (u64)&gpa, (u64)&size, 0);
|
||||
if (ret == 0) {
|
||||
*gpa_out = gpa;
|
||||
*size_out = size;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long utsm_hcall_console_write(const char *buf, u64 len)
|
||||
{
|
||||
return utsm_hcall(UTSM_HCALL_CONSOLE_WRITE, (u64)buf, len, 0);
|
||||
@@ -173,6 +202,14 @@ static u64 g_shm_size; /* size of SHM region */
|
||||
static int g_utsm_present; /* 1 = UTSM detected */
|
||||
static int g_shm_mapped;
|
||||
|
||||
/* VSCode integration Phase 1: graphics surface pool state.
|
||||
* Queried via UTSM_HCALL_SURFACE_INFO at init; exposed to userspace via
|
||||
* UTSM_IOCTL_SURFACE_INFO + mmap(/dev/utsm). g_surface_kaddr is the kernel
|
||||
* virtual mapping used internally by the mmap handler to validate requests. */
|
||||
static u64 g_surface_gpa; /* GPA of surface pool (0 = none) */
|
||||
static u64 g_surface_size; /* size in bytes */
|
||||
static int g_surface_present; /* 1 = surface pool available */
|
||||
|
||||
/* ===== /dev/utsm character device ===== */
|
||||
|
||||
static ssize_t utsm_dev_write(struct file *f, const char __user *buf,
|
||||
@@ -433,6 +470,21 @@ static long utsm_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
case UTSM_IOCTL_SURFACE_INFO: {
|
||||
/* VSCode integration Phase 1: return surface pool GPA + size.
|
||||
* Userspace then mmaps /dev/utsm (offset 0, len = size) to obtain
|
||||
* a writable scanout buffer. */
|
||||
struct utsm_ioctl_surface su;
|
||||
|
||||
if (!g_surface_present)
|
||||
return -ENODEV;
|
||||
su.gpa = g_surface_gpa;
|
||||
su.size = g_surface_size;
|
||||
if (copy_to_user((void __user *)arg, &su, sizeof(su)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
@@ -450,17 +502,51 @@ static __poll_t utsm_dev_poll(struct file *f, struct poll_table_struct *wait)
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
|
||||
/* Always writable (ring push may still fail with -EAGAIN) */
|
||||
mask |= EPOLLOUT | EPOLLWRNORM;
|
||||
mask |= EPOLLOUT | EPOLLRDNORM;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/* VSCode integration Phase 1: mmap the graphics surface pool into userspace.
|
||||
*
|
||||
* Userspace (e.g. the X server or a blit helper) calls:
|
||||
* void *p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
* to obtain a writable scanout buffer whose contents UTSM reads and blits to
|
||||
* the Deshab framebuffer. The surface pool is a separate DMA region EPT-mapped
|
||||
* into the guest at LINUX_GUEST_SURFACE_GPA and marked E820_TYPE_RESERVED, so
|
||||
* it has no struct page and we map it via remap_pfn_range.
|
||||
*
|
||||
* We allow mapping any sub-range of the pool (offset+size validated against
|
||||
* g_surface_size). Cacheable access is used because the surface pool is
|
||||
* regular RAM shared with the UTSM host over a coherent CPU cache domain;
|
||||
* switch to pgprot_writecombine() if DMA coherency issues ever appear. */
|
||||
static int utsm_dev_mmap(struct file *f, struct vm_area_struct *vma)
|
||||
{
|
||||
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
|
||||
unsigned long size = vma->vm_end - vma->vm_start;
|
||||
|
||||
if (!g_surface_present)
|
||||
return -ENODEV;
|
||||
|
||||
/* The pool is a single contiguous region; allow any sub-range within it. */
|
||||
if (off >= g_surface_size || size > g_surface_size - off)
|
||||
return -EINVAL;
|
||||
|
||||
if (remap_pfn_range(vma, vma->vm_start,
|
||||
(g_surface_gpa + off) >> PAGE_SHIFT,
|
||||
size, vma->vm_page_prot))
|
||||
return -EAGAIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations utsm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = utsm_dev_write,
|
||||
.read = utsm_dev_read,
|
||||
.unlocked_ioctl = utsm_dev_ioctl,
|
||||
.poll = utsm_dev_poll,
|
||||
.mmap = utsm_dev_mmap,
|
||||
};
|
||||
|
||||
static struct miscdevice utsm_miscdev = {
|
||||
@@ -585,7 +671,28 @@ static int __init utsm_hcall_init(void)
|
||||
|
||||
pr_info("[utsm] /dev/%s registered (IPC ready)\n", UTSM_DEV_NAME);
|
||||
|
||||
/* 8. Send a HELLO message via the ring buffer */
|
||||
/* 8. VSCode integration Phase 1: query graphics surface pool.
|
||||
* Non-fatal: if the surface pool is unavailable (older UTSM or alloc
|
||||
* failure), /dev/utsm still works for IPC — only mmap/SURFACE_INFO
|
||||
* ioctls return -ENODEV. On success, userspace can mmap(/dev/utsm)
|
||||
* to obtain a writable scanout buffer shared with the UTSM host. */
|
||||
{
|
||||
u64 surf_gpa = 0, surf_size = 0;
|
||||
long sret = utsm_hcall_surface_info(&surf_gpa, &surf_size);
|
||||
|
||||
if (sret == 0 && surf_gpa != 0 && surf_size != 0) {
|
||||
g_surface_gpa = surf_gpa;
|
||||
g_surface_size = surf_size;
|
||||
g_surface_present = 1;
|
||||
pr_info("[utsm] surface pool: gpa=0x%llx size=%llu KB\n",
|
||||
surf_gpa, surf_size / 1024);
|
||||
} else {
|
||||
pr_info("[utsm] surface pool not available (ret=%ld)\n",
|
||||
sret);
|
||||
}
|
||||
}
|
||||
|
||||
/* 9. Send a HELLO message via the ring buffer */
|
||||
{
|
||||
const char *hello_msg = "Linux booted OK";
|
||||
ipc_shm_send_to_utsm(UTSM_MSG_DATA, hello_msg, 15);
|
||||
@@ -646,6 +753,14 @@ static void __exit utsm_hcall_exit(void)
|
||||
g_shm_mapped = 0;
|
||||
}
|
||||
|
||||
/* VSCode integration Phase 1: clear surface pool state.
|
||||
* No kernel resource to free — the pool is owned by UTSM and only ever
|
||||
* exposed via remap_pfn_range. Clearing the flag makes post-unload
|
||||
* ioctls deterministically return -ENODEV. */
|
||||
g_surface_present = 0;
|
||||
g_surface_gpa = 0;
|
||||
g_surface_size = 0;
|
||||
|
||||
pr_info("[utsm] driver unloaded\n");
|
||||
}
|
||||
|
||||
|
||||
+122
-2
@@ -577,6 +577,123 @@ static int verify_password(const u8 *conf_buf, u32 conf_size, const char *passwo
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- P8.2 Fix: PS/2 鼠标初始化(login 进入前 mouseInit 可能未运行) ----
|
||||
* release 版 is_first=0 路径中 DSK 不加载 mouseInit,login 需自行初始化鼠标
|
||||
* 端口轮询方能工作。与 mouseInit 同样的命令序列,但去掉 IRQ12 注册
|
||||
* (login 全程 cli,无需中断驱动)。单步失败只记日志,不阻塞登录。 */
|
||||
#define PS2_DATA 0x60
|
||||
#define PS2_STATUS 0x64
|
||||
#define PS2_CMD 0x64
|
||||
#define PS2_ST_OBF 0x01
|
||||
#define PS2_ST_IBF 0x02
|
||||
#define PS2_CMD_READ_CFG 0x20
|
||||
#define PS2_CMD_WRITE_CFG 0x60
|
||||
#define PS2_CMD_ENABLE_AUX 0xA8
|
||||
#define PS2_CMD_TO_MOUSE 0xD4
|
||||
#define MOUSE_CMD_RESET 0xFF
|
||||
#define MOUSE_CMD_DEFAULTS 0xF6
|
||||
#define MOUSE_CMD_RATE 0xF3
|
||||
#define MOUSE_CMD_RESOL 0xE8
|
||||
#define MOUSE_CMD_ENABLE 0xF4
|
||||
#define MOUSE_ACK 0xFA
|
||||
#define PS2_TIMEOUT 100000u
|
||||
|
||||
static int ps2_wait_write(void) {
|
||||
for (u32 t = 0; t < PS2_TIMEOUT; t++) {
|
||||
if (!(inb(PS2_STATUS) & PS2_ST_IBF)) return 0;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
static int ps2_wait_read(void) {
|
||||
for (u32 t = 0; t < PS2_TIMEOUT; t++) {
|
||||
if (inb(PS2_STATUS) & PS2_ST_OBF) return 0;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
static void ps2_drain(void) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!(inb(PS2_STATUS) & PS2_ST_OBF)) break;
|
||||
inb(PS2_DATA);
|
||||
}
|
||||
}
|
||||
static int ps2_cmd(u8 cmd) {
|
||||
if (ps2_wait_write() != 0) return -1;
|
||||
outb(PS2_CMD, cmd);
|
||||
return 0;
|
||||
}
|
||||
static int mouse_cmd(u8 cmd) {
|
||||
if (ps2_cmd(PS2_CMD_TO_MOUSE) != 0) return -1;
|
||||
if (ps2_wait_write() != 0) return -2;
|
||||
outb(PS2_DATA, cmd);
|
||||
if (ps2_wait_read() != 0) return -3;
|
||||
return inb(PS2_DATA) == MOUSE_ACK ? 0 : -4;
|
||||
}
|
||||
static int mouse_cmd_param(u8 cmd, u8 param) {
|
||||
int rc = mouse_cmd(cmd);
|
||||
if (rc != 0) return rc;
|
||||
if (ps2_cmd(PS2_CMD_TO_MOUSE) != 0) return -5;
|
||||
if (ps2_wait_write() != 0) return -6;
|
||||
outb(PS2_DATA, param);
|
||||
if (ps2_wait_read() != 0) return -7;
|
||||
return inb(PS2_DATA) == MOUSE_ACK ? 0 : -8;
|
||||
}
|
||||
static void login_mouse_init(void) {
|
||||
logl("[login] mouse init begin");
|
||||
|
||||
/* 1. 冲刷残留 */
|
||||
ps2_drain();
|
||||
|
||||
/* 2. 启用 AUX 端口 */
|
||||
{
|
||||
u8 cfg = 0;
|
||||
if (ps2_cmd(PS2_CMD_READ_CFG) == 0 && ps2_wait_read() == 0) {
|
||||
cfg = inb(PS2_DATA);
|
||||
cfg &= (u8)~0x20u; /* bit5=0: 启用 AUX 时钟 */
|
||||
cfg |= 0x02u | 0x40u; /* bit1=AUX IRQ, bit6=翻译 */
|
||||
if (ps2_cmd(PS2_CMD_WRITE_CFG) == 0 && ps2_wait_write() == 0) {
|
||||
outb(PS2_DATA, cfg);
|
||||
logl("[login] cfg byte written");
|
||||
} else {
|
||||
logl("[login] cfg write failed, continue");
|
||||
}
|
||||
} else {
|
||||
logl("[login] cfg read failed, continue");
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. 启用 AUX 端口 */
|
||||
if (ps2_cmd(PS2_CMD_ENABLE_AUX) != 0)
|
||||
logl("[login] enable AUX timeout, continue");
|
||||
|
||||
/* 4. 复位鼠标 + drain 应答 */
|
||||
{
|
||||
int rc = mouse_cmd(MOUSE_CMD_RESET);
|
||||
if (rc != 0) logl("[login] reset failed, continue");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (ps2_wait_read() != 0) break;
|
||||
inb(PS2_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
/* 5-8. Set Defaults -> 采样率 100 -> 分辨率 2 -> 启用数据报告 */
|
||||
{
|
||||
int rc;
|
||||
rc = mouse_cmd(MOUSE_CMD_DEFAULTS);
|
||||
if (rc != 0) logl("[login] set defaults failed, continue");
|
||||
rc = mouse_cmd_param(MOUSE_CMD_RATE, 100);
|
||||
if (rc != 0) logl("[login] sample rate failed, continue");
|
||||
rc = mouse_cmd_param(MOUSE_CMD_RESOL, 2);
|
||||
if (rc != 0) logl("[login] resolution failed, continue");
|
||||
rc = mouse_cmd(MOUSE_CMD_ENABLE);
|
||||
if (rc != 0) logl("[login] enable reporting failed, continue");
|
||||
else logl("[login] mouse data reporting enabled");
|
||||
}
|
||||
|
||||
logl("[login] mouse init complete");
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void dsk_entry(const dsk_boot_context *ctx) {
|
||||
__asm__ volatile("cli"); /* prevent IRQ1 (ps2kbd) from racing with our polling */
|
||||
@@ -592,6 +709,9 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
logl("[login] calibrating TSC");
|
||||
tsc_calibrate_fi();
|
||||
|
||||
/* P8.2 Fix: 确保 PS/2 鼠标已初始化(非首次启动时 DSK 不加载 mouseInit) */
|
||||
login_mouse_init();
|
||||
|
||||
if (!ctx || ctx->magic != 0x44534B31424F4F54ULL) {
|
||||
logl("[login] bad context");
|
||||
return;
|
||||
@@ -683,8 +803,8 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
|
||||
u8 st = inb(0x64);
|
||||
if (!(st & 1)) { __asm__("pause"); continue; }
|
||||
/* 跳过鼠标数据(由 ps2_mouse_poll 处理) */
|
||||
if (st & 0x20) { inb(0x60); continue; }
|
||||
/* 鼠标数据留给 ps2_mouse_poll 读取,不消费不丢弃 */
|
||||
if (st & 0x20) { __asm__("pause"); continue; }
|
||||
u8 data = inb(0x60);
|
||||
|
||||
u8 sc = data;
|
||||
|
||||
+111
-11
@@ -82,6 +82,7 @@ static net_device_count_fn g_net_device_count;
|
||||
static net_device_info_fn g_net_device_info;
|
||||
static net_tx_fn g_net_tx;
|
||||
static net_rx_poll_fn g_net_rx_poll;
|
||||
static const dsk_boot_context *g_ctx;
|
||||
static u8 g_disk[131072];
|
||||
static u8 g_cluster[4096];
|
||||
static char g_conf[4096];
|
||||
@@ -153,6 +154,7 @@ typedef struct {
|
||||
char name[64];
|
||||
char password[64];
|
||||
char ip[16];
|
||||
char gateway[16];
|
||||
char dns[16];
|
||||
} net_conf;
|
||||
|
||||
@@ -163,10 +165,30 @@ static void copy_value(char *dst, u32 cap, const char *src, u32 len) {
|
||||
dst[n]=0;
|
||||
}
|
||||
|
||||
/* 解析 "a.b.c.d" → 网络序 u32(10.0.2.15 = 0x0A00020F)。返回 0 成功。 */
|
||||
static int nm_parse_ip(const char *s, u32 *out) {
|
||||
u32 v = 0;
|
||||
for (int part = 0; part < 4; part++) {
|
||||
u32 n = 0; int digits = 0;
|
||||
while (*s >= '0' && *s <= '9') {
|
||||
n = n * 10 + (u32)(*s - '0');
|
||||
if (n > 255) return -1;
|
||||
s++; digits++;
|
||||
}
|
||||
if (!digits) return -1;
|
||||
v = (v << 8) | n;
|
||||
if (part < 3) { if (*s != '.') return -1; s++; }
|
||||
}
|
||||
if (*s) return -1;
|
||||
*out = v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void parse_conf(const char *txt, net_conf *cfg) {
|
||||
copy_value(cfg->mode, sizeof(cfg->mode), "dhcp", 4);
|
||||
copy_value(cfg->device, sizeof(cfg->device), "auto", 4);
|
||||
copy_value(cfg->ip, sizeof(cfg->ip), "dhcp", 4);
|
||||
copy_value(cfg->gateway, sizeof(cfg->gateway), "auto", 4);
|
||||
copy_value(cfg->dns, sizeof(cfg->dns), "auto", 4);
|
||||
cfg->name[0]=0; cfg->password[0]=0;
|
||||
const char *p = txt;
|
||||
@@ -185,6 +207,7 @@ static void parse_conf(const char *txt, net_conf *cfg) {
|
||||
else if (klen==12 && streqn(line,"network.name",12)) copy_value(cfg->name,sizeof(cfg->name),v,vlen);
|
||||
else if (klen==16 && streqn(line,"network.password",16)) copy_value(cfg->password,sizeof(cfg->password),v,vlen);
|
||||
else if (klen==10 && streqn(line,"network.ip",10)) copy_value(cfg->ip,sizeof(cfg->ip),v,vlen);
|
||||
else if (klen==15 && streqn(line,"network.gateway",15)) copy_value(cfg->gateway,sizeof(cfg->gateway),v,vlen);
|
||||
else if (klen==11 && streqn(line,"network.dns",11)) copy_value(cfg->dns,sizeof(cfg->dns),v,vlen);
|
||||
}
|
||||
p += len;
|
||||
@@ -198,6 +221,7 @@ static void log_cfg(const net_conf *cfg) {
|
||||
swrite("[netman] device="); logl(cfg->device);
|
||||
swrite("[netman] name="); logl(cfg->name[0] ? cfg->name : "<empty>");
|
||||
swrite("[netman] ip="); logl(cfg->ip);
|
||||
swrite("[netman] gateway="); logl(cfg->gateway);
|
||||
swrite("[netman] dns="); logl(cfg->dns);
|
||||
u32 pwlen=0; while(cfg->password[pwlen]) pwlen++;
|
||||
log_hex("[netman] password length=", pwlen);
|
||||
@@ -598,12 +622,58 @@ static int dns_query_a(u32 index, const u8 dst_mac[6], u32 dns_ip, const char *n
|
||||
return -2;
|
||||
}
|
||||
|
||||
static int network_full_connect(u32 index, const u8 mac[6]) {
|
||||
int rc = dhcp_run(index, mac);
|
||||
log_hex("[netman] DHCP final rc=", (u64)(i8)rc);
|
||||
if (rc != 0) return rc;
|
||||
/* 将当前租约发布到 boot context(reserved[7] 指向 DSK 持有的 net_lease_info)。
|
||||
* DHCP ACK 或静态配置确定后立即调用,保证后续 ARP/DNS 自验失败时
|
||||
* 用户态工具(ping/curl/browser)仍能拿到可用 IP 配置。 */
|
||||
static void lease_publish(int is_static) {
|
||||
if (!g_ctx) return;
|
||||
u64 ptr = g_ctx->reserved[7];
|
||||
if (!ptr) { logl("[netman] lease: reserved[7] empty (DSK too old?)"); return; }
|
||||
net_lease_info *li = (net_lease_info *)ptr;
|
||||
if (li->magic != NET_LEASE_MAGIC) { logl("[netman] lease: bad buffer magic"); return; }
|
||||
li->ip = g_lease_ip;
|
||||
li->gateway = g_gateway_ip;
|
||||
li->dns = g_dns_ip;
|
||||
li->server = g_server_ip;
|
||||
li->flags = NET_LEASE_F_VALID | (is_static ? NET_LEASE_F_STATIC : 0u);
|
||||
logl("[netman] lease published to boot context");
|
||||
}
|
||||
|
||||
static int network_full_connect(u32 index, const u8 mac[6], const net_conf *cfg) {
|
||||
/* 静态模式判定:mode=static,或 network.ip 为点分 IP(区别于 dhcp/auto) */
|
||||
int use_static = 0;
|
||||
u32 sip = 0;
|
||||
if (cfg) {
|
||||
if (str_eq(cfg->mode, "static")) use_static = 1;
|
||||
if (nm_parse_ip(cfg->ip, &sip) == 0) use_static = 1;
|
||||
}
|
||||
if (use_static) {
|
||||
if (nm_parse_ip(cfg->ip, &sip) != 0) {
|
||||
logl("[netman] static mode but network.ip invalid");
|
||||
return -10;
|
||||
}
|
||||
for (int i=0;i<6;i++) g_local_mac[i]=mac[i];
|
||||
g_lease_ip = sip;
|
||||
g_server_ip = 0;
|
||||
u32 gw = 0;
|
||||
if (nm_parse_ip(cfg->gateway, &gw) != 0) gw = (sip & 0xFFFFFF00u) | 0x02u; /* 约定 x.x.x.2 */
|
||||
g_gateway_ip = gw;
|
||||
u32 dn = 0;
|
||||
if (nm_parse_ip(cfg->dns, &dn) != 0) dn = gw;
|
||||
g_dns_ip = dn;
|
||||
log_ipv4("[netman] static ip=", g_lease_ip);
|
||||
log_ipv4("[netman] static gateway=", g_gateway_ip);
|
||||
log_ipv4("[netman] static dns=", g_dns_ip);
|
||||
lease_publish(1);
|
||||
} else {
|
||||
int rc = dhcp_run(index, mac);
|
||||
log_hex("[netman] DHCP final rc=", (u64)(i8)rc);
|
||||
if (rc != 0) return rc;
|
||||
lease_publish(0);
|
||||
}
|
||||
/* ARP 解析网关 + DNS 查询自验(静态/DHCP 共用) */
|
||||
u8 gw_mac[6];
|
||||
rc = arp_resolve_gateway(index, g_gateway_ip, gw_mac);
|
||||
int rc = arp_resolve_gateway(index, g_gateway_ip, gw_mac);
|
||||
log_hex("[netman] ARP final rc=", (u64)(i8)rc);
|
||||
if (rc != 0) return rc;
|
||||
u32 resolved=0;
|
||||
@@ -612,6 +682,21 @@ static int network_full_connect(u32 index, const u8 mac[6]) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 检查设备名是否匹配配置的 device 字段。
|
||||
* "auto" 或空匹配任意设备;否则精确匹配或前缀匹配(如 "e1000" 匹配 "e1000_0")。 */
|
||||
static int device_match(const char *configured, const char *actual) {
|
||||
if (!configured || !configured[0]) return 1;
|
||||
if (str_eq(configured, "auto")) return 1;
|
||||
if (!actual) return 0;
|
||||
if (str_eq(configured, actual)) return 1;
|
||||
/* 前缀匹配 */
|
||||
int i;
|
||||
for (i = 0; configured[i]; i++) {
|
||||
if (configured[i] != actual[i]) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void query_netdevs(const net_conf *cfg) {
|
||||
if (!g_net_device_count || !g_net_device_info) {
|
||||
logl("[netman] net API not ready; configuration staged only");
|
||||
@@ -619,9 +704,13 @@ static void query_netdevs(const net_conf *cfg) {
|
||||
}
|
||||
|
||||
u32 count = g_net_device_count();
|
||||
int dhcp_done = 0;
|
||||
int dhcp_enabled = cfg && !str_eq(cfg->mode, "disabled") && !str_eq(cfg->mode, "off") && !str_eq(cfg->ip, "static");
|
||||
int net_done = 0;
|
||||
int net_enabled = cfg && !str_eq(cfg->mode, "disabled") && !str_eq(cfg->mode, "off");
|
||||
int has_dev_filter = cfg && cfg->device[0] && !str_eq(cfg->device, "auto");
|
||||
log_hex("[netman] netdev_count=", count);
|
||||
if (has_dev_filter) {
|
||||
swrite("[netman] device filter: "); logl(cfg->device);
|
||||
}
|
||||
for (u32 i=0;i<count;i++) {
|
||||
nm_net_device_info info;
|
||||
memset_nm(&info, 0, sizeof(info));
|
||||
@@ -642,21 +731,32 @@ static void query_netdevs(const net_conf *cfg) {
|
||||
if (info.flags & DKM_NET_F_TX_READY) logl("[netman] tx=ready");
|
||||
if (info.flags & DKM_NET_F_RX_READY) logl("[netman] rx=ready");
|
||||
|
||||
if (!dhcp_done && dhcp_enabled &&
|
||||
/* 设备名过滤:配置了特定设备时跳过不匹配的设备 */
|
||||
if (has_dev_filter && !device_match(cfg->device, info.name)) {
|
||||
swrite("[netman] skipped (device filter: ");
|
||||
swrite(cfg->device);
|
||||
logl(")");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!net_done && net_enabled &&
|
||||
(info.flags & (DKM_NET_F_LINK_UP|DKM_NET_F_TX_READY|DKM_NET_F_RX_READY))
|
||||
== (DKM_NET_F_LINK_UP|DKM_NET_F_TX_READY|DKM_NET_F_RX_READY)) {
|
||||
int drc = network_full_connect(i, info.mac);
|
||||
int drc = network_full_connect(i, info.mac, cfg);
|
||||
log_hex("[netman] network final rc=", (u64)(i8)drc);
|
||||
dhcp_done = 1;
|
||||
net_done = 1;
|
||||
}
|
||||
}
|
||||
if (!dhcp_enabled) logl("[netman] DHCP skipped by config");
|
||||
if (!net_enabled) logl("[netman] networking disabled by config");
|
||||
if (has_dev_filter && !net_done && net_enabled)
|
||||
logl("[netman] no matching device found for configured device filter");
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void dsk_entry(const dsk_boot_context *ctx) {
|
||||
logl("[netman] boot");
|
||||
if (!ctx || ctx->magic != DSK_BOOT_MAGIC) { logl("[netman] bad context"); return; }
|
||||
g_ctx = ctx;
|
||||
logl("[netman] calibrating TSC (实机 CPU 频率计算)");
|
||||
tsc_calibrate_nm();
|
||||
u64 api = ctx->dkm_kernel_api;
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,542 @@
|
||||
# Deshab SDK 使用文档
|
||||
|
||||
## 1. 概述
|
||||
|
||||
Deshab SDK 是 Deshab 操作系统的应用与驱动统一开发工具包。在 SAS-R0 单地址空间 Ring0 模型下,应用(`.elf`)与驱动(`.drv`)共享同一套基础类型、ABI 契约和工具函数,仅链接形式不同:
|
||||
|
||||
- **应用**:PIE ELF,由 DSK 加载器加载执行,入口 `dsk_entry`。
|
||||
- **驱动**:ET_REL 可重定位对象,由 DKM loader 装载,导出 `driver_desc` / `driver_init` / `driver_exit`。
|
||||
|
||||
所有 SDK 头文件均为 `static inline` 实现,无库依赖,包含即用。
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
CODE/sdk/
|
||||
├── include/deshab/ SDK 头文件(单一包含入口)
|
||||
│ ├── deshab.h 伞头:基础层 + ABI 层 + 应用层(不含 fat32/net)
|
||||
│ ├── types.h 基础类型 u8/u16/u32/u64/i8/i16/i32/i64
|
||||
│ ├── portio.h 端口 I/O(dsb_inb/dsb_outb/inw/outw/inl/outl)
|
||||
│ ├── serial.h COM1 串口日志(dsb_slog/dsb_swrite/dsb_sputc)
|
||||
│ ├── tsc.h TSC 校准与延时(dsb_tsc_calibrate/dsb_delay_ms)
|
||||
│ ├── string.h 基础内存操作(dsb_memset/dsb_memcpy/dsb_strlen)
|
||||
│ ├── kernel_api.h dkm_kernel_api ABI 结构体(字段顺序不可变)
|
||||
│ ├── boot_context.h dsk_boot_context 启动上下文与入口契约
|
||||
│ ├── driver.h DKM 驱动描述符与状态机定义
|
||||
│ ├── block.h block provider 便捷读写层
|
||||
│ ├── app.h dsb_app_context 应用上下文初始化
|
||||
│ ├── fb.h 帧缓冲绘制原语(pixel/fill_rect/blend/rounded_rect)
|
||||
│ ├── font.h ASCII 位图字体渲染(dsb_draw_char/dsb_draw_string)
|
||||
│ ├── cursor.h 鼠标光标 save/restore/draw
|
||||
│ ├── input.h PS/2 键盘扫描码转 ASCII + 鼠标初始化与轮询
|
||||
│ ├── ui.h 色板常量 + 标题栏/状态栏装饰 + LINUXAPP.CNF 解析
|
||||
│ ├── rtc.h CMOS RTC 时分读取
|
||||
│ ├── fat32.h FAT32 读写库 + LFN 长文件名(含 256KB+4KB+256KB BSS)
|
||||
│ └── net.h 用户态网络协议栈 ARP/ICMP/DNS/TCP(含 64KB BSS)
|
||||
├── scaffold/ 脚手架模板
|
||||
│ ├── new-module.ps1 一键创建新应用/驱动模块
|
||||
│ ├── app/ 应用模板(main.c / MAKEFILE / linker.ld)
|
||||
│ └── driver/ 驱动模板(main.c)
|
||||
├── examples/ 示例代码
|
||||
│ ├── hello-app/ 最小应用示例
|
||||
│ └── hello-driver/ 最小驱动示例
|
||||
└── docs/
|
||||
└── SDK.md 本文档
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 快速开始
|
||||
|
||||
### 2.1 使用脚手架创建新模块
|
||||
|
||||
```powershell
|
||||
# 创建应用(默认输出到 CODE/tools/<Name>,产物到 SYSTEM/system/deshab64/tools/<Name>.elf)
|
||||
cd d:\Code\Deshab\CODE\sdk\scaffold
|
||||
.\new-module.ps1 -Name myapp
|
||||
|
||||
# 创建驱动(默认输出到 CODE/DKM/<Name>)
|
||||
.\new-module.ps1 -Name mydriver -Type driver -Class DKM_CLASS_NET -Stage 3
|
||||
```
|
||||
|
||||
脚本自动计算相对路径并替换模板占位符,生成 `main.c` / `MAKEFILE` / `linker.ld`(应用)或 `main.c`(驱动)。
|
||||
|
||||
可选参数:
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `-Name` | 模块名(必填) | — |
|
||||
| `-Type` | `app` 或 `driver` | `app` |
|
||||
| `-OutDir` | 输出目录 | app→`CODE/tools/$Name`,driver→`CODE/DKM/$Name` |
|
||||
| `-Class` | 驱动类别 `DKM_CLASS_*` | `DKM_CLASS_MISC` |
|
||||
| `-Stage` | 驱动加载阶段 0-3 | `3` |
|
||||
|
||||
### 2.2 构建命令
|
||||
|
||||
**应用**(需要 clang + ld.lld + make,make 需 `sh.exe` 在 PATH 中):
|
||||
|
||||
```powershell
|
||||
cd CODE\tools\myapp # 或 CODE\sdk\examples\hello-app
|
||||
make # 构建到 SYSTEM/system/deshab64/tools/myapp.elf
|
||||
make clean # 清理
|
||||
```
|
||||
|
||||
**驱动**(两步:编译 + 链接为可重定位对象):
|
||||
|
||||
```powershell
|
||||
cd CODE\DKM\mydriver
|
||||
clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin `
|
||||
-fno-stack-protector -fno-pic -fno-pie -mcmodel=kernel `
|
||||
-mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 `
|
||||
-Wall -Wextra -I..\sdk\include -O2 -c main.c -o mydriver.o
|
||||
ld.lld -r -o mydriver.drv mydriver.o
|
||||
```
|
||||
|
||||
> 提示:项目根目录的 `build.ps1` 会批量构建所有驱动并打包镜像,日常开发只需 `.\build.bat`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 应用开发
|
||||
|
||||
### 3.1 入口契约
|
||||
|
||||
DSK 加载 `.elf` 后调用 `dsk_entry(dsk_boot_context *ctx)`。应用必须:
|
||||
|
||||
1. **校验 magic**:`ctx->magic == DSK_BOOT_MAGIC`(`0x44534B31424F4F54`,即 `"DSK1BOOT"`)。
|
||||
2. **初始化上下文**:调用 `dsb_app_init(&ac, ctx)` 提取 framebuffer、block、kernel_api。
|
||||
3. (可选)校验 `ctx->abi_version == DSK_BOOT_ABI_VERSION`。
|
||||
|
||||
```c
|
||||
#include "../../../firstInit/ascii_bitmaps.c" /* 提供字体位图数据 */
|
||||
#include "deshab/deshab.h"
|
||||
#include "deshab/font.h" /* 需先有位图数据源 */
|
||||
|
||||
void dsk_entry(const dsk_boot_context *ctx) {
|
||||
if (ctx->magic != DSK_BOOT_MAGIC) return;
|
||||
dsb_app_context ac;
|
||||
dsb_app_init(&ac, ctx);
|
||||
/* ... 你的逻辑 ... */
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 dsb_app_context
|
||||
|
||||
`dsb_app_init` 从 boot context 一次性提取应用常用数据:
|
||||
|
||||
| 字段 | 类型 | 来源 |
|
||||
|------|------|------|
|
||||
| `fb` | `u32*` | `ctx->framebuffer_address`(32bpp BGRA) |
|
||||
| `fb_w` | `u64` | `ctx->framebuffer_width` |
|
||||
| `fb_h` | `u64` | `ctx->framebuffer_height` |
|
||||
| `fb_pitch` | `u64` | `ctx->framebuffer_pitch` |
|
||||
| `block` | `dsb_block` | 从 `ctx->dkm_kernel_api + DSB_KAPI_OFF_BLOCK` 解析 |
|
||||
| `api` | `const dkm_kernel_api*` | `ctx->dkm_kernel_api` |
|
||||
|
||||
### 3.3 绘制 API
|
||||
|
||||
所有绘制函数以 `dsb_app_context*` 为首参,假设 32bpp BGRA 帧缓冲:
|
||||
|
||||
| 函数 | 说明 |
|
||||
|------|------|
|
||||
| `dsb_fill_bg(ac, color)` | 全屏填充 |
|
||||
| `dsb_fill_rect(ac, x, y, w, h, color)` | 矩形填充 |
|
||||
| `dsb_fill_rounded_rect(ac, x, y, w, h, color, radius)` | 圆角矩形填充 |
|
||||
| `dsb_rect_outline(ac, x, y, w, h, color, radius)` | 矩形描边 |
|
||||
| `dsb_pixel(ac, x, y, color)` | 单像素(自动越界保护) |
|
||||
| `dsb_pixel_read(ac, x, y)` | 读单像素 |
|
||||
| `dsb_blend(bg, fg, alpha)` | Alpha 混合(0-255) |
|
||||
| `dsb_draw_char(ac, ch, x, y, fg, bg)` | 单字符(8 级灰度抗锯齿) |
|
||||
| `dsb_draw_string(ac, s, x, y, fg, bg, step)` | 字符串(step=字间距,12 为常规) |
|
||||
| `dsb_draw_titlebar(ac, title, w)` | 标题栏(含关闭按钮 + 分隔线) |
|
||||
| `dsb_draw_statusbar(ac, text, w, h)` | 底部状态栏 |
|
||||
|
||||
**色板常量**(`ui.h`):
|
||||
|
||||
| 常量 | 值 | 用途 |
|
||||
|------|----|------|
|
||||
| `DSB_BG_PRIMARY` | `0xFF0A1428` | 主背景(深蓝) |
|
||||
| `DSB_BG_SECONDARY` | `0xFF0F1E38` | 次背景(标题栏/状态栏) |
|
||||
| `DSB_TEXT_PRIMARY` | `0xFFE8E8F0` | 主文字 |
|
||||
| `DSB_TEXT_DIM` | `0xFF6A6A88` | 暗淡文字 |
|
||||
| `DSB_ACCENT` | `0xFF00A8CC` | 强调色 |
|
||||
| `DSB_ERROR` | `0xFFFF4466` | 错误/关闭按钮 |
|
||||
| `DSB_SUCCESS` | `0xFF40C880` | 成功 |
|
||||
| `DSB_WARNING` | `0xFFF0A030` | 警告 |
|
||||
|
||||
**字体前置条件**:`font.h` 依赖 `g_ascii[95]` / `g_ascii_w` / `g_ascii_h` 三个符号,必须先 `#include "ascii_bitmaps.c"`(或自定义位图源)再包含 `font.h`。
|
||||
|
||||
### 3.4 输入 API
|
||||
|
||||
**键盘**(轮询模式,scan code set 1):
|
||||
|
||||
```c
|
||||
u8 st = dsb_inb(0x64);
|
||||
if (st & 1) {
|
||||
u8 sc = dsb_inb(0x60);
|
||||
char ch = dsb_scan_to_ascii(sc, shift_pressed);
|
||||
/* sc == 1 为 Esc,sc == 0x36/0x2A 为 Shift */
|
||||
}
|
||||
```
|
||||
|
||||
**鼠标**(PS/2 轮询模式):
|
||||
|
||||
```c
|
||||
dsb_mouse mouse;
|
||||
dsb_cursor cursor;
|
||||
dsb_mouse_init(); /* 初始化 AUX 端口 + 流模式 */
|
||||
dsb_cursor_init(&cursor, ac.fb_w, ac.fb_h);
|
||||
|
||||
/* 主循环中 */
|
||||
if (dsb_mouse_poll(&mouse, &cursor, ac.fb_w, ac.fb_h)) {
|
||||
/* cursor.mx / cursor.my / cursor.btn 已更新 */
|
||||
dsb_cursor_save(&ac, &cursor); /* 先恢复旧位置背景 */
|
||||
dsb_cursor_restore(&ac, &cursor); /* (需先 save 过) */
|
||||
dsb_cursor_draw(&ac, &cursor, DSB_CURSOR_COLOR);
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:`dsb_mouse_poll` 先检查 AUX 位(`0x20`)再读数据,避免吞掉键盘扫描码。
|
||||
|
||||
### 3.5 FAT32 与网络(按需引入)
|
||||
|
||||
`fat32.h` 和 `net.h` 因含较大 static 缓冲(分别约 516KB 和 64KB BSS),**不在** `deshab.h` 伞头中默认包含,需显式 `#include`:
|
||||
|
||||
```c
|
||||
#include "deshab/deshab.h"
|
||||
#include "deshab/fat32.h" /* 需要文件读写时 */
|
||||
#include "deshab/net.h" /* 需要网络时 */
|
||||
```
|
||||
|
||||
**FAT32 用法**:
|
||||
|
||||
```c
|
||||
/* 从 block API 初始化(block_read/write 来自 kernel_api) */
|
||||
dsb_block blk;
|
||||
dsb_block_init(&blk, ctx->dkm_kernel_api);
|
||||
f32_init(blk.read, blk.write);
|
||||
|
||||
/* 读根目录文件 */
|
||||
u8 *data; u32 size;
|
||||
char name11[11];
|
||||
f32_name_to_83("CONFIG.TXT", name11);
|
||||
if (f32_read_root_file(name11, &data, &size) == 0) {
|
||||
/* data 指向 f32_data 内部缓冲,size 为字节数 */
|
||||
}
|
||||
|
||||
/* 写文件 */
|
||||
f32_write_root_file(name11, my_data, my_size);
|
||||
|
||||
/* 长路径 LFN 读取(支持子目录) */
|
||||
if (f32_read_path_lfn("system/deshab64/FUCK", &data, &size) == 0) { ... }
|
||||
```
|
||||
|
||||
**网络用法**(QEMU slirp 默认拓扑):
|
||||
|
||||
```c
|
||||
ns_init(ctx->dkm_kernel_api); /* 绑定第一个有线 tx/rx ready 设备 */
|
||||
ns_apply_lease_from_ctx((u64)ctx); /* 应用 netman DHCP 租约(若有) */
|
||||
|
||||
u32 ip;
|
||||
if (ns_dns_resolve("example.com", &ip) == 0) {
|
||||
u32 rtt; u8 ttl;
|
||||
if (ns_ping(ip, 1, 3000, &rtt, &ttl) == 0) { /* ping 成功 */ }
|
||||
}
|
||||
|
||||
/* TCP client */
|
||||
if (ns_tcp_connect(ip, 80, 5000) == 0) {
|
||||
ns_tcp_send((const u8*)"GET / HTTP/1.0\r\n\r\n", 18);
|
||||
u8 buf[4096];
|
||||
int n = ns_tcp_recv(buf, sizeof(buf), 5000);
|
||||
ns_tcp_close();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 驱动开发
|
||||
|
||||
### 4.1 三符号契约
|
||||
|
||||
DKM 驱动为 ET_REL 可重定位对象(`.drv`),必须导出三个符号:
|
||||
|
||||
| 符号 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `driver_desc` | `const dkm_driver_desc` | 描述符(magic/类别/阶段/依赖/提供) |
|
||||
| `driver_init` | `int (*)(const dkm_kernel_api *api, dkm_driver_handle *handle)` | 初始化,返回 0 成功 |
|
||||
| `driver_exit` | `int (*)(dkm_driver_handle *handle)` | 卸载清理,返回 0 成功 |
|
||||
|
||||
三个符号均需 `__attribute__((visibility("default")))` 以确保不被隐藏。
|
||||
|
||||
### 4.2 driver_desc 字段
|
||||
|
||||
```c
|
||||
const dkm_driver_desc driver_desc = {
|
||||
.magic = DKM_DRIVER_MAGIC, /* 0x444B4D31 "DKM1",必须 */
|
||||
.abi_version = DKM_ABI_VERSION, /* 1 */
|
||||
.desc_size = sizeof(dkm_driver_desc),
|
||||
.name = "hello",
|
||||
.version = "1.0",
|
||||
.vendor = "Deshab",
|
||||
.driver_class = DKM_CLASS_MISC, /* 类别,见下表 */
|
||||
.stage = 3, /* 加载阶段 0-3 */
|
||||
.flags = 0, /* DKM_F_* 标志 */
|
||||
.priority = 100,
|
||||
.depends = g_depends, /* 依赖的 provides 名 */
|
||||
.depends_count = 0,
|
||||
.provides = g_provides, /* 本驱动提供的能力名 */
|
||||
.provides_count = 1,
|
||||
.min_kernel_abi = DKM_KERNEL_API_VERSION,
|
||||
.feature_bits = 0,
|
||||
};
|
||||
```
|
||||
|
||||
**驱动类别**(`driver.h`):
|
||||
|
||||
| 常量 | 值 | 说明 |
|
||||
|------|----|------|
|
||||
| `DKM_CLASS_PLATFORM` | 1 | 平台发现 |
|
||||
| `DKM_CLASS_BUS` | 2 | 总线(PCI) |
|
||||
| `DKM_CLASS_INTERRUPT` | 3 | 中断控制器 |
|
||||
| `DKM_CLASS_TIMER` | 4 | 计时器 |
|
||||
| `DKM_CLASS_CONSOLE` | 5 | 控制台/帧缓冲 |
|
||||
| `DKM_CLASS_STORAGE` | 6 | 存储(AHCI/NVMe) |
|
||||
| `DKM_CLASS_FS` | 7 | 文件系统 |
|
||||
| `DKM_CLASS_NET` | 8 | 网络 |
|
||||
| `DKM_CLASS_INPUT` | 9 | 输入(键鼠) |
|
||||
| `DKM_CLASS_GPU` | 10 | GPU |
|
||||
| `DKM_CLASS_MISC` | 11 | 杂项 |
|
||||
|
||||
**驱动标志**(`flags`):
|
||||
|
||||
| 常量 | 说明 |
|
||||
|------|------|
|
||||
| `DKM_F_REQUIRED` | 当前 stage 失败则 DRR recovery 或 panic |
|
||||
| `DKM_F_BOOT_MODULE` | 可由 bootloader 预加载 |
|
||||
| `DKM_F_NO_UNLOAD` | 不允许卸载 |
|
||||
| `DKM_F_STRONG_RECOVERY` | data 段使用 UTSM 强恢复策略 |
|
||||
| `DKM_F_DMA_REQUIRED` | 需要 DMA API |
|
||||
| `DKM_F_EARLY_LOG` | init 期间允许 early log |
|
||||
|
||||
### 4.3 driver_init / driver_exit
|
||||
|
||||
```c
|
||||
int driver_init(const dkm_kernel_api *api, dkm_driver_handle *handle) {
|
||||
(void)handle;
|
||||
/* 校验 kernel_api 可用性 */
|
||||
if (!api || !api->log) return -1; /* 返回负数=失败 */
|
||||
|
||||
/* 通过 kernel_api 调用内核服务,不直接依赖内核符号 */
|
||||
api->log->info("[hello] init");
|
||||
|
||||
/* 注册 IRQ、映射 MMIO、注册 block/net 设备等 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int driver_exit(dkm_driver_handle *handle) {
|
||||
(void)handle;
|
||||
/* 清理资源 */
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
> `required` 驱动 init 失败会触发 DRR recovery 或 panic;非 required 驱动失败仅标记为 `DKM_STATE_FAILED` 并继续。
|
||||
|
||||
### 4.4 dkm_kernel_api 使用
|
||||
|
||||
驱动通过 `dkm_kernel_api` 调用内核服务,**不直接依赖任意内核符号**。主要能力:
|
||||
|
||||
| 字段 | 类型 | 用途 |
|
||||
|------|------|------|
|
||||
| `log` | `const dkm_log_api*` | 日志(info/warn/error/panic) |
|
||||
| `dma` | `const dkm_dma_api*` | DMA 缓冲分配 |
|
||||
| `net` | `const dkm_net_api*` | 网络设备注册/收发 |
|
||||
| `block` | `const dkm_block_api*` | block 设备注册/读写 |
|
||||
| `mmio` | `const dkm_mmio_api*` | MMIO 映射查询 |
|
||||
| `mm_map_mmio` | `fn` | MMIO 独立窗口映射(PCD\|PWT) |
|
||||
| `mm_unmap_mmio` | `fn` | 解除 MMIO 窗口映射 |
|
||||
| `irq_register` | `fn` | 注册 IRQ handler |
|
||||
| `register_apic_eoi` | `fn` | 注册 LAPIC EOI 钩子 |
|
||||
| `irq_vector_alloc/free` | `fn` | 动态 IDT 向量分配(MSI/MSI-X) |
|
||||
| `rsdp_address` | `const void*` | ACPI RSDP 物理地址 |
|
||||
| `fb_address` / `fb_*` | — | 帧缓冲信息 |
|
||||
| `hhdm_offset` | `u64` | 物理→虚拟偏移 |
|
||||
| `boot_modules_response` | `const void*` | Limine boot module 响应 |
|
||||
|
||||
### 4.5 manifest 注册
|
||||
|
||||
驱动构建为 `.drv` 后,需在 `SYSTEM/driver/manifest.json` 注册:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "hello",
|
||||
"path": "driver/misc/hello.drv",
|
||||
"class": "misc",
|
||||
"required": false,
|
||||
"depends": [],
|
||||
"provides": ["hello"]
|
||||
}
|
||||
```
|
||||
|
||||
条目放在对应 stage 的 `drivers` 数组中(stage 0-3)。`required: true` 的驱动 init 失败会触发 DRR recovery 或 panic。
|
||||
|
||||
如需 bootloader 预加载(stage0/stage1),还需在 `SYSTEM/boot/limine.conf` 添加 `module_path`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 头文件索引
|
||||
|
||||
### 基础层
|
||||
|
||||
| 头文件 | 用途 | 关键 API |
|
||||
|--------|------|----------|
|
||||
| `types.h` | 基础类型 | `u8/u16/u32/u64/i8/i16/i32/i64/NULL` |
|
||||
| `portio.h` | 端口 I/O | `dsb_inb/dsb_outb/dsb_inw/dsb_outw/dsb_inl/dsb_outl` |
|
||||
| `serial.h` | COM1 串口日志 | `dsb_slog(tag,msg)` / `dsb_swrite(s)` / `dsb_sputc(c)` |
|
||||
| `tsc.h` | TSC 校准与延时 | `dsb_tsc_calibrate()` / `dsb_rdtsc()` / `dsb_delay_ms/us()` |
|
||||
| `string.h` | 基础内存操作 | `dsb_memset/dsb_memcpy/dsb_strlen` |
|
||||
|
||||
### ABI 层
|
||||
|
||||
| 头文件 | 用途 | 关键 API |
|
||||
|--------|------|----------|
|
||||
| `kernel_api.h` | kernel_api ABI 结构体 | `dkm_kernel_api` / `dkm_log_api` / `dkm_dma_api` / `dkm_net_api` / `dkm_block_api` / `dkm_mmio_api` |
|
||||
| `boot_context.h` | 启动上下文 | `dsk_boot_context` / `DSK_BOOT_MAGIC` / `dsk_entry_fn` / `probe_info` / `net_lease_info` |
|
||||
| `driver.h` | 驱动描述符 | `dkm_driver_desc` / `DKM_DRIVER_MAGIC` / `DKM_CLASS_*` / `DKM_F_*` / `dkm_driver_handle` |
|
||||
| `block.h` | block 便捷层 | `dsb_block` / `dsb_block_init()` / `dsb_block_read/write()` |
|
||||
|
||||
### 应用层
|
||||
|
||||
| 头文件 | 用途 | 关键 API |
|
||||
|--------|------|----------|
|
||||
| `app.h` | 应用上下文 | `dsb_app_context` / `dsb_app_init()` |
|
||||
| `fb.h` | 帧缓冲绘制 | `dsb_pixel/fill_rect/fill_bg/fill_rounded_rect/rect_outline/blend` |
|
||||
| `font.h` | 字体渲染 | `dsb_draw_char/dsb_draw_string`(需先提供 `g_ascii` 位图源) |
|
||||
| `cursor.h` | 鼠标光标 | `dsb_cursor` / `dsb_cursor_init/save/restore/draw` |
|
||||
| `input.h` | 键鼠输入 | `dsb_scan_to_ascii()` / `dsb_mouse` / `dsb_mouse_init/poll()` |
|
||||
| `ui.h` | 色板与装饰 | `DSB_BG_PRIMARY` 等色板 / `dsb_draw_titlebar/statusbar` / `dsb_linuxapp_parse()` |
|
||||
| `rtc.h` | RTC 时钟 | `dsb_rtc_time(h, m)` |
|
||||
|
||||
### 领域层(按需包含,含大 static 缓冲)
|
||||
|
||||
| 头文件 | 用途 | BSS 占用 | 关键 API |
|
||||
|--------|------|----------|----------|
|
||||
| `fat32.h` | FAT32 读写 + LFN | ~516KB | `f32_init/read_root_file/write_root_file/delete_root_file/list_root/list_dir/read_path_lfn/write_path_lfn` |
|
||||
| `net.h` | 网络协议栈 | ~64KB | `ns_init/ping/dns_resolve/tcp_connect/tcp_send/tcp_recv/tcp_close` |
|
||||
|
||||
### 伞头
|
||||
|
||||
| 头文件 | 包含范围 |
|
||||
|--------|----------|
|
||||
| `deshab.h` | 基础层 + ABI 层 + 应用层(不含 `fat32.h` / `net.h` / `font.h`) |
|
||||
|
||||
> `font.h` 需先提供位图数据源(`#include "ascii_bitmaps.c"`),故不在伞头中默认包含。
|
||||
|
||||
---
|
||||
|
||||
## 6. ABI 约束
|
||||
|
||||
### 6.1 kernel_api 布局不可变
|
||||
|
||||
`dkm_kernel_api` 结构体的**字段顺序和类型不可调整**。UTSM 端按固定偏移填充各 API 指针,DKM loader 和 DSK 按相同偏移读取。修改字段顺序会破坏所有已编译的驱动和应用。
|
||||
|
||||
### 6.2 字段只追加
|
||||
|
||||
新增能力只允许在结构体**末尾追加**字段(尾部追加保持 ABI 兼容)。旧驱动访问不到新字段不受影响;新驱动可通过 `api->size` 判断字段是否存在。
|
||||
|
||||
### 6.3 偏移常量
|
||||
|
||||
供不便通过结构体解引用的场景(如早期 boot context 解析、汇编代码)使用,消除散落的硬编码偏移:
|
||||
|
||||
| 常量 | 值 | 对应字段 |
|
||||
|------|----|----------|
|
||||
| `DSB_KAPI_OFF_NET` | `0x48` | `const dkm_net_api *net` |
|
||||
| `DSB_KAPI_OFF_BLOCK` | `0xA8` | `const dkm_block_api *block` |
|
||||
|
||||
常规代码应直接通过 `api->net` / `api->block` 访问,仅在早期启动或指针不可解引用时使用偏移常量。
|
||||
|
||||
### 6.4 dsk_boot_context 同理
|
||||
|
||||
`dsk_boot_context` 的 `reserved[8]` 槽位有约定索引:
|
||||
|
||||
| 索引 | 常量 | 用途 |
|
||||
|------|------|------|
|
||||
| 4 | `DSB_BOOT_RESERVED_PE_SERVICE` | PE/EXE 兼容层服务指针 |
|
||||
| 5 | `DSB_BOOT_RESERVED_LINUX_COMPAT` | Linux 兼容层服务指针 |
|
||||
| 6 | `DSB_BOOT_RESERVED_PROBE_INFO` | 插桩 probe 缓冲指针 |
|
||||
| 7 | `DSB_BOOT_RESERVED_NET_LEASE` | netman 网络租约指针 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 构建参数速查
|
||||
|
||||
### 7.1 应用(PIE ELF)
|
||||
|
||||
应用编译为 PIE ELF,由 DSK 的 PIE ELF loader 加载(支持 `R_X86_64_RELATIVE` 重定位)。
|
||||
|
||||
**clang 编译参数**:
|
||||
|
||||
```
|
||||
-target x86_64-unknown-none
|
||||
-std=c11
|
||||
-ffreestanding
|
||||
-fno-builtin
|
||||
-fno-stack-protector
|
||||
-fpie # 位置无关可执行文件
|
||||
-mno-red-zone
|
||||
-mno-sse -mno-sse2 -mno-mmx -msoft-float # 未启用 FPU/SSE 前必须
|
||||
-m64
|
||||
-Wall -Wextra
|
||||
-I<sdk/include> # SDK 头文件路径
|
||||
-O2
|
||||
```
|
||||
|
||||
**ld.lld 链接参数**:
|
||||
|
||||
```
|
||||
-nostdlib
|
||||
-static
|
||||
-pie
|
||||
-z max-page-size=0x1000
|
||||
-T linker.ld # PIE ELF 布局,ENTRY(dsk_entry)
|
||||
```
|
||||
|
||||
**linker.ld 布局**:text(PF_R\|PF_X)→ rodata(PF_R)→ data(PF_R\|PF_W)→ .rela.dyn → .dynamic → .bss(NOBITS)。
|
||||
|
||||
### 7.2 驱动(ET_REL)
|
||||
|
||||
驱动编译为可重定位对象(ET_REL),由 DKM loader 装载并做 ELF64 relocation(`R_X86_64_64/32/32S/PC32`)。
|
||||
|
||||
**clang 编译参数**(与应用的差异):
|
||||
|
||||
```
|
||||
-fno-pic -fno-pie # 不可重定位代码(非 PIE)
|
||||
-mcmodel=kernel # 内核代码模型
|
||||
# 无 -fpie,无 linker.ld,无链接步骤
|
||||
```
|
||||
|
||||
其余参数(`-target -std -ffreestanding -fno-builtin -mno-red-zone -mno-sse ...`)与应用一致。
|
||||
|
||||
**ld.lld 链接参数**:
|
||||
|
||||
```
|
||||
-r # 合并为单个可重定位对象(不生成可执行文件)
|
||||
-o <name>.drv
|
||||
```
|
||||
|
||||
### 7.3 参数差异对照
|
||||
|
||||
| 参数 | 应用 | 驱动 |
|
||||
|------|------|------|
|
||||
| 位置无关 | `-fpie` | `-fno-pic -fno-pie` |
|
||||
| 代码模型 | (默认 small) | `-mcmodel=kernel` |
|
||||
| 链接 | `-nostdlib -static -pie -T linker.ld` | `-r`(仅合并) |
|
||||
| 产物 | PIE ELF(`.elf`) | ET_REL(`.drv`) |
|
||||
| 入口 | `dsk_entry` | `driver_desc` / `driver_init` / `driver_exit` |
|
||||
| 重定位 | `R_X86_64_RELATIVE`(加载时) | `R_X86_64_64/32/32S/PC32`(装载时) |
|
||||
|
||||
### 7.4 通用约束
|
||||
|
||||
- **进入 C 前必须 `cli/cld`**(由 loader 保证)。
|
||||
- **未启用 FPU/SSE 前编译必须** `-mno-sse -mno-sse2 -mno-mmx -msoft-float`。
|
||||
- **DATA/BSS 段使用 `PF_R|PF_W|PF_X`**(SAS-R0 下驱动模块内存需可执行)。
|
||||
- **无 libc**:所有内存操作使用 `dsb_memset/dsb_memcpy/dsb_strlen`,不可调用标准库函数。
|
||||
@@ -0,0 +1,25 @@
|
||||
CC := clang
|
||||
LD := ld.lld
|
||||
|
||||
ROOT := ../../../..
|
||||
OUT := $(ROOT)/SYSTEM/system/deshab64/tools/hello.elf
|
||||
|
||||
CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I../../include -O2
|
||||
LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld
|
||||
|
||||
C_SOURCES := main.c
|
||||
OBJECTS := $(C_SOURCES:.c=.o)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT)
|
||||
|
||||
$(OUT): $(OBJECTS) linker.ld
|
||||
@mkdir -p "$(dir $(OUT))"
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }"
|
||||
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
ENTRY(dsk_entry)
|
||||
|
||||
PHDRS
|
||||
{
|
||||
text PT_LOAD FLAGS(5);
|
||||
rodata PT_LOAD FLAGS(4);
|
||||
data PT_LOAD FLAGS(6);
|
||||
dynamic PT_DYNAMIC FLAGS(4);
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = 0x1000;
|
||||
.text : ALIGN(4K) { *(.text*) } :text
|
||||
.rodata : ALIGN(4K) { *(.rodata*) } :rodata
|
||||
.data : ALIGN(4K) {
|
||||
*(.data*)
|
||||
*(.data.rel.ro*)
|
||||
*(COMMON)
|
||||
} :data
|
||||
.rela.dyn : ALIGN(8) { *(.rela.dyn*) } :data
|
||||
.dynamic : ALIGN(8) { *(.dynamic*) } :data :dynamic
|
||||
.bss : ALIGN(4K) {
|
||||
*(.bss*)
|
||||
} :data
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* hello-app - Deshab SDK 最小应用示例
|
||||
*
|
||||
* 展示完整的应用入口契约:校验 magic -> 初始化上下文 -> 绘制 UI -> 等待退出。
|
||||
* 构建产物:SYSTEM/system/deshab64/tools/hello.elf
|
||||
*/
|
||||
#include "../../../firstInit/ascii_bitmaps.c"
|
||||
#include "deshab/deshab.h"
|
||||
#include "deshab/font.h"
|
||||
|
||||
static dsb_app_context g_ac;
|
||||
|
||||
void dsk_entry(const dsk_boot_context *ctx) {
|
||||
/* 1. 校验启动上下文 magic */
|
||||
if (ctx->magic != DSK_BOOT_MAGIC) return;
|
||||
|
||||
/* 2. 初始化应用上下文(framebuffer + block + kernel_api) */
|
||||
dsb_app_init(&g_ac, ctx);
|
||||
|
||||
/* 3. 清屏 */
|
||||
dsb_fill_bg(&g_ac, DSB_BG_PRIMARY);
|
||||
|
||||
/* 4. 标题栏 + 状态栏 */
|
||||
dsb_draw_titlebar(&g_ac, "HelloApp", (i64)g_ac.fb_w);
|
||||
dsb_draw_statusbar(&g_ac, "Esc to exit", (i64)g_ac.fb_w, (i64)g_ac.fb_h);
|
||||
|
||||
/* 5. 居中显示欢迎文字 */
|
||||
const char *msg = "Hello, Deshab!";
|
||||
i64 msg_w = (i64)dsb_strlen(msg) * 12;
|
||||
i64 x = ((i64)g_ac.fb_w - msg_w) / 2;
|
||||
i64 y = ((i64)g_ac.fb_h) / 2;
|
||||
dsb_draw_string(&g_ac, msg, x, y, DSB_TEXT_PRIMARY, DSB_BG_PRIMARY, 12);
|
||||
|
||||
/* 6. 等待 Esc 键退出 */
|
||||
for (;;) {
|
||||
u8 st = dsb_inb(0x64);
|
||||
if (st & 1) {
|
||||
u8 sc = dsb_inb(0x60);
|
||||
if (sc == 1) break; /* Esc make code */
|
||||
}
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,50 @@
|
||||
/* hello-driver - Deshab SDK 最小驱动示例
|
||||
*
|
||||
* 展示 DKM 驱动三符号契约:driver_desc + driver_init + driver_exit。
|
||||
* 构建方式:clang -c main.c -o hello.o && ld.lld -r -o hello.drv hello.o
|
||||
* 注册方式:在 SYSTEM/driver/manifest.json 添加条目
|
||||
*/
|
||||
#include "deshab/driver.h"
|
||||
#include "deshab/serial.h"
|
||||
|
||||
static const dkm_log_api *g_log = 0;
|
||||
|
||||
static const char *const g_depends[] = { 0 };
|
||||
static const char *const g_provides[] = { "hello" };
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
const dkm_driver_desc driver_desc = {
|
||||
.magic = DKM_DRIVER_MAGIC,
|
||||
.abi_version = DKM_ABI_VERSION,
|
||||
.desc_size = sizeof(dkm_driver_desc),
|
||||
.name = "hello",
|
||||
.version = "1.0",
|
||||
.vendor = "Deshab",
|
||||
.driver_class = DKM_CLASS_MISC,
|
||||
.stage = 3,
|
||||
.flags = 0,
|
||||
.priority = 100,
|
||||
.depends = g_depends,
|
||||
.depends_count = 0,
|
||||
.provides = g_provides,
|
||||
.provides_count = 1,
|
||||
.min_kernel_abi = DKM_KERNEL_API_VERSION,
|
||||
.feature_bits = 0,
|
||||
};
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int driver_init(const dkm_kernel_api *api, dkm_driver_handle *handle) {
|
||||
(void)handle;
|
||||
if (!api || !api->log) return -1;
|
||||
g_log = api->log;
|
||||
g_log->info("[hello] init");
|
||||
g_log->info("[hello] driver ready");
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int driver_exit(dkm_driver_handle *handle) {
|
||||
(void)handle;
|
||||
if (g_log) g_log->info("[hello] exit");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* deshab/app.h - 应用上下文与初始化
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_app_context / da_init,提供从 dsk_boot_context
|
||||
* 提取常用数据(framebuffer + block + kernel_api)的便捷封装。
|
||||
*
|
||||
* 应用入口模式:
|
||||
* void dsk_entry(const dsk_boot_context *ctx) {
|
||||
* dsb_app_context ac;
|
||||
* dsb_app_init(&ac, ctx);
|
||||
* // 使用 ac.fb / ac.block / ac.api ...
|
||||
* }
|
||||
*/
|
||||
#ifndef DESHAB_APP_H
|
||||
#define DESHAB_APP_H
|
||||
|
||||
#include "types.h"
|
||||
#include "boot_context.h"
|
||||
#include "kernel_api.h"
|
||||
#include "block.h"
|
||||
|
||||
typedef struct dsb_app_context {
|
||||
u32 *fb; /* framebuffer 基址(32bpp BGRA) */
|
||||
u64 fb_w; /* 宽度(像素) */
|
||||
u64 fb_h; /* 高度(像素) */
|
||||
u64 fb_pitch; /* 行跨度(字节) */
|
||||
dsb_block block; /* block 便捷读写 */
|
||||
const dkm_kernel_api *api; /* kernel_api 指针 */
|
||||
} dsb_app_context;
|
||||
|
||||
/* 从 dsk_boot_context 初始化 dsb_app_context。
|
||||
* block API 从 kernel_api + DSB_KAPI_OFF_BLOCK 获取(封装在 dsb_block_init)。 */
|
||||
static inline void dsb_app_init(dsb_app_context *ac, const dsk_boot_context *ctx) {
|
||||
ac->fb = (u32 *)ctx->framebuffer_address;
|
||||
ac->fb_w = ctx->framebuffer_width;
|
||||
ac->fb_h = ctx->framebuffer_height;
|
||||
ac->fb_pitch = ctx->framebuffer_pitch;
|
||||
ac->api = (const dkm_kernel_api *)ctx->dkm_kernel_api;
|
||||
dsb_block_init(&ac->block, ctx->dkm_kernel_api);
|
||||
}
|
||||
|
||||
#endif /* DESHAB_APP_H */
|
||||
@@ -0,0 +1,53 @@
|
||||
/* deshab/block.h - block provider 便捷访问层
|
||||
*
|
||||
* 封装从 dkm_kernel_api 获取 block API 并调用 read/write 的样板代码,
|
||||
* 消除散落在各应用的硬编码偏移(+0xA8 / +0x10 / +0x18)。
|
||||
*
|
||||
* 用法:
|
||||
* dsb_block blk;
|
||||
* if (dsb_block_init(&blk, ctx->dkm_kernel_api) == 0) {
|
||||
* dsb_block_read(&blk, 0, 256, buf);
|
||||
* dsb_block_write(&blk, 0, 1, buf);
|
||||
* }
|
||||
*/
|
||||
#ifndef DESHAB_BLOCK_H
|
||||
#define DESHAB_BLOCK_H
|
||||
|
||||
#include "types.h"
|
||||
#include "kernel_api.h"
|
||||
|
||||
typedef struct dsb_block {
|
||||
const dkm_block_api *api;
|
||||
/* 便捷函数指针(从 block_api 解析)。index 固定 0,与现有应用一致。 */
|
||||
int (*read)(u32 index, u64 lba, u32 count, void *buffer);
|
||||
int (*write)(u32 index, u64 lba, u32 count, const void *buffer);
|
||||
} dsb_block;
|
||||
|
||||
/* 从 kernel_api 指针初始化 block 便捷结构。
|
||||
* kernel_api_u64 为 dsk_boot_context.dkm_kernel_api 的值(u64 指针)。
|
||||
* 返回 0 成功,-1 无 block API。 */
|
||||
static inline int dsb_block_init(dsb_block *b, u64 kernel_api_u64) {
|
||||
b->api = 0;
|
||||
b->read = 0;
|
||||
b->write = 0;
|
||||
if (!kernel_api_u64) return -1;
|
||||
/* block API 指针位于 kernel_api + DSB_KAPI_OFF_BLOCK */
|
||||
const dkm_block_api *blk_api = *(const dkm_block_api **)(kernel_api_u64 + DSB_KAPI_OFF_BLOCK);
|
||||
if (!blk_api) return -1;
|
||||
b->api = blk_api;
|
||||
/* read @ +0x10, write @ +0x18(dkm_block_api 结构内偏移) */
|
||||
b->read = (int (*)(u32, u64, u32, void *))(*(u64 *)((u64)blk_api + 0x10));
|
||||
b->write = (int (*)(u32, u64, u32, const void *))(*(u64 *)((u64)blk_api + 0x18));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 便捷读写(index 固定 0,与现有应用一致)。返回 0 成功。 */
|
||||
static inline int dsb_block_read(dsb_block *b, u64 lba, u32 count, void *buf) {
|
||||
return b->read ? b->read(0, lba, count, buf) : -1;
|
||||
}
|
||||
|
||||
static inline int dsb_block_write(dsb_block *b, u64 lba, u32 count, const void *buf) {
|
||||
return b->write ? b->write(0, lba, count, buf) : -1;
|
||||
}
|
||||
|
||||
#endif /* DESHAB_BLOCK_H */
|
||||
@@ -0,0 +1,99 @@
|
||||
/* deshab/boot_context.h - DSK 启动上下文与入口契约
|
||||
*
|
||||
* 整合 CODE/UTSM/include/utsm/dsk.h,为应用提供自包含的 boot context 定义。
|
||||
* DSK 加载 .elf 应用时,通过 dsk_entry(dsk_boot_context *ctx) 传递平台信息。
|
||||
*
|
||||
* reserved 槽位约定(消除散落的硬编码索引):
|
||||
* [4] pe_service PE/EXE 兼容层服务指针
|
||||
* [5] linux_compat_service Linux 兼容层服务指针
|
||||
* [6] probe_info 插桩 probe 缓冲指针
|
||||
* [7] net_lease_info netman 网络租约指针
|
||||
*/
|
||||
#ifndef DESHAB_BOOT_CONTEXT_H
|
||||
#define DESHAB_BOOT_CONTEXT_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define DSK_BOOT_MAGIC 0x44534B31424F4F54ULL /* "DSK1BOOT" */
|
||||
#define DSK_BOOT_ABI_VERSION 1u
|
||||
|
||||
#define DSK_BOOT_FLAG_FROM_UTSM (1ULL << 0)
|
||||
#define DSK_BOOT_FLAG_DKM_READY (1ULL << 1)
|
||||
#define DSK_BOOT_FLAG_FAT32_PATH (1ULL << 2)
|
||||
#define DSK_BOOT_FLAG_PROBE_INFO (1ULL << 3)
|
||||
|
||||
/* ---- reserved 槽位索引(dsk_boot_context.reserved[]) ---- */
|
||||
#define DSB_BOOT_RESERVED_PE_SERVICE 4u
|
||||
#define DSB_BOOT_RESERVED_LINUX_COMPAT 5u
|
||||
#define DSB_BOOT_RESERVED_PROBE_INFO 6u
|
||||
#define DSB_BOOT_RESERVED_NET_LEASE 7u
|
||||
|
||||
/* ---- Probe 信息结构(通过 reserved[6] 传递) ---- */
|
||||
#define PROBE_INFO_MAGIC 0x50524F42u /* "PRFB" */
|
||||
|
||||
typedef struct probe_info {
|
||||
u32 magic; /* PROBE_INFO_MAGIC */
|
||||
u32 version; /* ABI 版本,当前 1 */
|
||||
u64 probe_buf_addr; /* probe_entry 缓冲基地址 */
|
||||
u32 probe_cap; /* 缓冲容量(条目数) */
|
||||
u32 probe_head; /* 当前写入位置(递增取模) */
|
||||
u32 probe_count; /* 已写入总条目数 */
|
||||
u32 probe_dropped; /* 因缓冲满而覆盖的条目数 */
|
||||
u32 instr_enabled; /* g_instr_enabled 当前值 */
|
||||
u32 instr_probe_enable; /* g_instr_probe_enable 当前值 */
|
||||
u64 reserved[4];
|
||||
} probe_info;
|
||||
|
||||
/* ---- 网络租约信息(netman 写入,经 reserved[7] 指针传递) ---- */
|
||||
#define NET_LEASE_MAGIC 0x4E45544Cu /* "NETL" */
|
||||
#define NET_LEASE_F_VALID (1u << 0) /* ip/gateway/dns 有效 */
|
||||
#define NET_LEASE_F_STATIC (1u << 1) /* 静态配置(非 DHCP) */
|
||||
|
||||
typedef struct net_lease_info {
|
||||
u32 magic; /* NET_LEASE_MAGIC */
|
||||
u32 flags; /* NET_LEASE_F_* */
|
||||
u32 ip; /* guest IP(网络序) */
|
||||
u32 gateway; /* 网关(网络序) */
|
||||
u32 dns; /* DNS(网络序) */
|
||||
u32 server; /* DHCP server(网络序,调试) */
|
||||
u32 reserved[2];
|
||||
} net_lease_info;
|
||||
|
||||
typedef struct dsk_boot_context {
|
||||
u64 magic;
|
||||
u32 abi_version;
|
||||
u32 size;
|
||||
|
||||
u64 flags;
|
||||
|
||||
u64 hhdm_offset;
|
||||
u64 rsdp_address;
|
||||
|
||||
u64 framebuffer_address;
|
||||
u64 framebuffer_width;
|
||||
u64 framebuffer_height;
|
||||
u64 framebuffer_pitch;
|
||||
u32 framebuffer_bpp;
|
||||
u32 reserved_fb;
|
||||
|
||||
u64 boot_modules_response;
|
||||
|
||||
u64 dkm_kernel_api;
|
||||
u64 dkm_driver_table;
|
||||
u64 dkm_driver_count;
|
||||
|
||||
u64 utsm_state;
|
||||
u64 drr_state;
|
||||
|
||||
u64 memory_map;
|
||||
u64 memory_map_count;
|
||||
u64 memory_map_entry_size;
|
||||
|
||||
u64 kernel_stack_top;
|
||||
u64 reserved[8];
|
||||
} dsk_boot_context;
|
||||
|
||||
/* 应用入口点签名。DSK 加载 .elf 后调用 dsk_entry(context)。 */
|
||||
typedef void (*dsk_entry_fn)(const dsk_boot_context *context);
|
||||
|
||||
#endif /* DESHAB_BOOT_CONTEXT_H */
|
||||
@@ -0,0 +1,88 @@
|
||||
/* deshab/cursor.h - 鼠标光标管理
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_cursor 结构与 save/restore/draw 操作,统一前缀为 dsb_。
|
||||
* 采用 save/restore 模式实现光标移动:绘制前保存背景,移动后恢复。
|
||||
*/
|
||||
#ifndef DESHAB_CURSOR_H
|
||||
#define DESHAB_CURSOR_H
|
||||
|
||||
#include "types.h"
|
||||
#include "app.h"
|
||||
#include "fb.h"
|
||||
|
||||
#define DSB_CURSOR_SIZE 24
|
||||
|
||||
static const u8 dsb_cursor_shape[24][24] = {
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
};
|
||||
|
||||
typedef struct dsb_cursor {
|
||||
u32 save[DSB_CURSOR_SIZE * DSB_CURSOR_SIZE];
|
||||
int saved;
|
||||
int old_x, old_y;
|
||||
int mx, my;
|
||||
int btn;
|
||||
} dsb_cursor;
|
||||
|
||||
static inline void dsb_cursor_init(dsb_cursor *c, i64 fb_w, i64 fb_h) {
|
||||
for (int i = 0; i < DSB_CURSOR_SIZE * DSB_CURSOR_SIZE; i++) c->save[i] = 0;
|
||||
c->saved = 0;
|
||||
c->old_x = -1;
|
||||
c->old_y = -1;
|
||||
c->mx = (int)(fb_w / 2);
|
||||
c->my = (int)(fb_h / 2);
|
||||
c->btn = 0;
|
||||
}
|
||||
|
||||
static inline void dsb_cursor_save(dsb_app_context *ac, dsb_cursor *c) {
|
||||
for (int r = 0; r < DSB_CURSOR_SIZE; r++)
|
||||
for (int col = 0; col < DSB_CURSOR_SIZE; col++) {
|
||||
int x = c->mx + col, y = c->my + r;
|
||||
c->save[r * DSB_CURSOR_SIZE + col] = dsb_pixel_read(ac, x, y);
|
||||
}
|
||||
c->saved = 1;
|
||||
c->old_x = c->mx;
|
||||
c->old_y = c->my;
|
||||
}
|
||||
|
||||
static inline void dsb_cursor_restore(dsb_app_context *ac, dsb_cursor *c) {
|
||||
if (!c->saved) return;
|
||||
for (int r = 0; r < DSB_CURSOR_SIZE; r++)
|
||||
for (int col = 0; col < DSB_CURSOR_SIZE; col++) {
|
||||
int x = c->old_x + col, y = c->old_y + r;
|
||||
dsb_pixel(ac, x, y, c->save[r * DSB_CURSOR_SIZE + col]);
|
||||
}
|
||||
c->saved = 0;
|
||||
}
|
||||
|
||||
static inline void dsb_cursor_draw(dsb_app_context *ac, dsb_cursor *c, u32 color) {
|
||||
for (int r = 0; r < DSB_CURSOR_SIZE; r++)
|
||||
for (int col = 0; col < DSB_CURSOR_SIZE; col++)
|
||||
if (dsb_cursor_shape[r][col])
|
||||
dsb_pixel(ac, c->mx + col, c->my + r, color);
|
||||
}
|
||||
|
||||
#endif /* DESHAB_CURSOR_H */
|
||||
@@ -0,0 +1,48 @@
|
||||
/* deshab.h - Deshab SDK 伞头文件
|
||||
*
|
||||
* 一站式包含 SDK 基础层 + ABI 层 + 应用层轻量头文件。
|
||||
* fat32.h 与 net.h 因含较大 static 缓冲(256KB/64KB BSS),
|
||||
* 不在此默认包含,按需 #include "deshab/fat32.h" 或 "deshab/net.h"。
|
||||
*
|
||||
* 用法:
|
||||
* #include "deshab/deshab.h" // 基础+ABI+应用层
|
||||
* #include "deshab/fat32.h" // 需要 FAT32 读写时
|
||||
* #include "deshab/net.h" // 需要网络协议栈时
|
||||
*
|
||||
* 字体渲染需在 #include "deshab/font.h" 前提供位图数据源
|
||||
* (#include "ascii_bitmaps.c" 或自定义 g_ascii/g_ascii_w/g_ascii_h)。
|
||||
*/
|
||||
#ifndef DESHAB_H
|
||||
#define DESHAB_H
|
||||
|
||||
/* ---- 基础层 ---- */
|
||||
#include "types.h"
|
||||
#include "portio.h"
|
||||
#include "serial.h"
|
||||
#include "tsc.h"
|
||||
#include "string.h"
|
||||
|
||||
/* ---- ABI 层 ---- */
|
||||
#include "kernel_api.h"
|
||||
#include "boot_context.h"
|
||||
#include "driver.h"
|
||||
#include "block.h"
|
||||
|
||||
/* ---- 应用层 ---- */
|
||||
#include "app.h"
|
||||
#include "fb.h"
|
||||
#include "cursor.h"
|
||||
#include "input.h"
|
||||
#include "ui.h"
|
||||
#include "rtc.h"
|
||||
/* font.h 需先提供 g_ascii 位图数据源,不在此默认包含:
|
||||
* #include "ascii_bitmaps.c"
|
||||
* #include "deshab/font.h"
|
||||
*/
|
||||
|
||||
/* ---- 领域层(按需包含,含大 static 缓冲)----
|
||||
* #include "fat32.h"
|
||||
* #include "net.h"
|
||||
*/
|
||||
|
||||
#endif /* DESHAB_H */
|
||||
@@ -0,0 +1,97 @@
|
||||
/* deshab/driver.h - DKM 驱动模块 ABI
|
||||
*
|
||||
* 整合 CODE/UTSM/include/utsm/dkm.h 与 CODE/DKM/dkm_shared.h 的驱动描述符定义。
|
||||
* 驱动模块(.drv,ET_REL)导出 driver_desc / driver_init / driver_exit 三个符号,
|
||||
* 由 DKM loader 校验 magic、调用 driver_init(api, handle) 完成初始化。
|
||||
*
|
||||
* 约定:
|
||||
* - driver_init 返回 0 成功,负数失败。
|
||||
* - required 驱动 init 失败触发 DRR recovery 或 panic。
|
||||
* - driver_exit 仅用于非 NO_UNLOAD 驱动。
|
||||
* - 驱动通过 dkm_kernel_api 调用内核服务,不直接依赖任意内核符号。
|
||||
*/
|
||||
#ifndef DESHAB_DRIVER_H
|
||||
#define DESHAB_DRIVER_H
|
||||
|
||||
#include "types.h"
|
||||
#include "kernel_api.h"
|
||||
|
||||
#define DKM_DRIVER_MAGIC 0x444B4D31u /* "DKM1" */
|
||||
#define DKM_ABI_VERSION 1u
|
||||
|
||||
/* ---- 驱动类别(driver_desc.driver_class) ---- */
|
||||
#define DKM_CLASS_PLATFORM 1u
|
||||
#define DKM_CLASS_BUS 2u
|
||||
#define DKM_CLASS_INTERRUPT 3u
|
||||
#define DKM_CLASS_TIMER 4u
|
||||
#define DKM_CLASS_CONSOLE 5u
|
||||
#define DKM_CLASS_STORAGE 6u
|
||||
#define DKM_CLASS_FS 7u
|
||||
#define DKM_CLASS_NET 8u
|
||||
#define DKM_CLASS_INPUT 9u
|
||||
#define DKM_CLASS_GPU 10u
|
||||
#define DKM_CLASS_MISC 11u
|
||||
|
||||
/* ---- 驱动标志(driver_desc.flags) ---- */
|
||||
#define DKM_F_REQUIRED (1u << 0) /* 当前 stage 失败则 DRR recovery 或 panic */
|
||||
#define DKM_F_BOOT_MODULE (1u << 1) /* 可由 bootloader 预加载 */
|
||||
#define DKM_F_NO_UNLOAD (1u << 2) /* 不允许卸载 */
|
||||
#define DKM_F_STRONG_RECOVERY (1u << 3) /* 驱动 data 段使用 UTSM 强恢复策略 */
|
||||
#define DKM_F_DMA_REQUIRED (1u << 4) /* 驱动需要 DMA API */
|
||||
#define DKM_F_EARLY_LOG (1u << 5) /* 驱动 init 期间允许使用 early log */
|
||||
|
||||
typedef struct dkm_driver_desc {
|
||||
u32 magic; /* DKM_DRIVER_MAGIC */
|
||||
u16 abi_version; /* DKM_ABI_VERSION */
|
||||
u16 desc_size;
|
||||
|
||||
const char *name;
|
||||
const char *version;
|
||||
const char *vendor;
|
||||
|
||||
u32 driver_class; /* DKM_CLASS_* */
|
||||
u32 stage; /* 加载阶段 0-3 */
|
||||
u32 flags; /* DKM_F_* */
|
||||
u32 priority;
|
||||
|
||||
const char *const *depends;
|
||||
u32 depends_count;
|
||||
|
||||
const char *const *provides;
|
||||
u32 provides_count;
|
||||
|
||||
u64 min_kernel_abi;
|
||||
u64 feature_bits;
|
||||
|
||||
u64 reserved0;
|
||||
u64 reserved1;
|
||||
} dkm_driver_desc;
|
||||
|
||||
struct dkm_driver_handle;
|
||||
|
||||
typedef int (*dkm_driver_init_fn)(const dkm_kernel_api *api, struct dkm_driver_handle *handle);
|
||||
typedef int (*dkm_driver_exit_fn)(struct dkm_driver_handle *handle);
|
||||
|
||||
typedef enum dkm_driver_state {
|
||||
DKM_STATE_DISCOVERED = 0,
|
||||
DKM_STATE_ABI_CHECKED,
|
||||
DKM_STATE_INITING,
|
||||
DKM_STATE_ACTIVE,
|
||||
DKM_STATE_FAILED
|
||||
} dkm_driver_state;
|
||||
|
||||
typedef struct dkm_driver_handle {
|
||||
const dkm_driver_desc *desc;
|
||||
dkm_driver_state state;
|
||||
i32 init_status;
|
||||
u32 load_stage;
|
||||
u32 flags;
|
||||
} dkm_driver_handle;
|
||||
|
||||
typedef struct dkm_builtin_driver {
|
||||
const dkm_driver_desc *desc;
|
||||
dkm_driver_init_fn init;
|
||||
dkm_driver_exit_fn exit;
|
||||
} dkm_builtin_driver;
|
||||
|
||||
#endif /* DESHAB_DRIVER_H */
|
||||
@@ -0,0 +1,1233 @@
|
||||
/* deshab/fat32.h - Deshab SDK FAT32 读写库 + VFAT LFN 长文件名解析(static inline,无库)
|
||||
*
|
||||
* 本头为 Deshab SDK 的一部分,整合自:
|
||||
* - CODE/tools/fat32_lfn.h(LFN 长文件名解析)
|
||||
* - CODE/tools/fat32_io.h(FAT32 读写库)
|
||||
* 合并为单一头文件,基础类型 u8/u16/u32/u64/i64 由 "types.h" 提供。
|
||||
*/
|
||||
|
||||
#ifndef DESHAB_FAT32_H
|
||||
#define DESHAB_FAT32_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* =====================================================================
|
||||
* 第一部分:LFN 长文件名解析(原 fat32_lfn.h)
|
||||
* =====================================================================
|
||||
*
|
||||
* FAT32 LFN 原理:
|
||||
* 目录项中 attr==0x0F 的是 LFN 片段项。每项含 13 个 UTF-16 LE 字符
|
||||
* (name1[5] + name2[6] + name3[2]),按逆序存储——序号最高(带 0x40
|
||||
* 标志)的项最先出现,序号递减,最后紧跟一个 8.3 短目录项作为“主项”。
|
||||
* 短名项的 checksum(由 name11 计算)用于校验整条 LFN 链完整性。
|
||||
*
|
||||
* 用法(在目录遍历循环中):
|
||||
* fat32_lfn_buf lfn;
|
||||
* fat32_lfn_init(&lfn);
|
||||
* // 外层簇遍历循环——lfn 必须跨簇存活(LFN 链可能跨簇边界)
|
||||
* while (遍历目录簇) {
|
||||
* for (每个 32 字节目录项 e) {
|
||||
* if (name[0]==0) 目录结束;
|
||||
* if (name[0]==0xE5) { fat32_lfn_init(&lfn); continue; }
|
||||
* int is_short = fat32_lfn_process(&lfn, entry_ptr);
|
||||
* if (is_short) {
|
||||
* // lfn.valid==1 时有完整 LFN,可 fat32_lfn_to_ascii 取名
|
||||
* // 用 fat32_lfn_match 匹配长名或 8.3 短名
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 注意:fat32_lfn_buf 必须在簇遍历循环**外**声明,跨簇切换时不清空,
|
||||
* 以支持跨簇边界的 LFN 链。
|
||||
*/
|
||||
|
||||
/* LFN 最大字符数(FAT32 规范最多 20 个 LFN 项 = 260 字符) */
|
||||
#define FAT32_LFN_MAX 260
|
||||
|
||||
/* LFN 收集缓冲(栈/局部变量分配) */
|
||||
typedef struct {
|
||||
u16 chars[FAT32_LFN_MAX]; /* UTF-16 LE 累积字符(正序) */
|
||||
int count; /* 有效字符数(不含 0x0000 终止符/0xFFFF 填充) */
|
||||
u8 checksum; /* LFN 链 checksum(来自 0x40 标志项) */
|
||||
int valid; /* 1=LFN 链完整且 checksum 与短名项匹配 */
|
||||
} fat32_lfn_buf;
|
||||
|
||||
/* 初始化/清空 LFN 缓冲 */
|
||||
static inline void fat32_lfn_init(fat32_lfn_buf *buf) {
|
||||
buf->count = 0;
|
||||
buf->valid = 0;
|
||||
buf->checksum = 0;
|
||||
for (int i = 0; i < FAT32_LFN_MAX; i++) buf->chars[i] = 0;
|
||||
}
|
||||
|
||||
/* 计算 8.3 短名(11 字节)的 LFN checksum。
|
||||
* 标准 FAT32 算法:cksum = ((cksum & 1) << 7) + (cksum >> 1) + name[i] */
|
||||
static inline u8 fat32_lfn_checksum(const u8 name11[11]) {
|
||||
u8 cksum = 0;
|
||||
for (int i = 0; i < 11; i++) {
|
||||
cksum = (u8)(((cksum & 1) << 7) + (cksum >> 1) + name11[i]);
|
||||
}
|
||||
return cksum;
|
||||
}
|
||||
|
||||
/* 处理一个 32 字节目录项。自动判断 LFN 项 / 短名项 / 已删除项。
|
||||
*
|
||||
* 调用方对目录遍历中的每个条目(跳过 name[0]==0 结束符后)调用此函数。
|
||||
* - LFN 项(attr==0x0F):收集 13 个 UTF-16 字符到 buf,返回 0
|
||||
* - 已删除项(name[0]==0xE5):清空 buf,返回 0
|
||||
* - 短名项:校验 checksum,设 buf->valid,返回 1
|
||||
*
|
||||
* 返回 1=刚处理的是短名项(此时可检查 buf->valid 判断是否有有效 LFN),
|
||||
* 0=LFN 项或已删除项。
|
||||
*
|
||||
* 注意:遇到带 0x40 标志的 LFN 项(链开始)时自动重置 buf。
|
||||
* 遇到已删除项时自动清空 buf(防止残留 LFN 污染下一个文件)。 */
|
||||
static inline int fat32_lfn_process(fat32_lfn_buf *buf, const u8 *entry) {
|
||||
u8 attr = entry[11];
|
||||
|
||||
/* 已删除项:清空 LFN 缓冲 */
|
||||
if (entry[0] == 0xE5) {
|
||||
fat32_lfn_init(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* LFN 项(attr == 0x0F) */
|
||||
if (attr == 0x0F) {
|
||||
u8 seq = entry[0];
|
||||
u8 ord = (u8)(seq & 0x3F); /* 序号 1-20 */
|
||||
if (ord == 0 || ord > 20) {
|
||||
fat32_lfn_init(buf);
|
||||
return 0;
|
||||
}
|
||||
/* 带 0x40 标志 = LFN 链开始(最高序号项),重置缓冲 */
|
||||
if (seq & 0x40) {
|
||||
int prev_cksum = buf->checksum;
|
||||
fat32_lfn_init(buf);
|
||||
buf->checksum = entry[13];
|
||||
(void)prev_cksum;
|
||||
}
|
||||
/* 提取 13 个 UTF-16 LE 字符,放到 (ord-1)*13 位置(正序) */
|
||||
int base = (ord - 1) * 13;
|
||||
if (base + 13 > FAT32_LFN_MAX) {
|
||||
fat32_lfn_init(buf);
|
||||
return 0;
|
||||
}
|
||||
/* name1: entry[1..10] = 5 个 UTF-16 LE 字符 */
|
||||
for (int i = 0; i < 5; i++) {
|
||||
u16 c = (u16)entry[1 + i * 2] | ((u16)entry[2 + i * 2] << 8);
|
||||
buf->chars[base + i] = c;
|
||||
}
|
||||
/* name2: entry[14..25] = 6 个 UTF-16 LE 字符 */
|
||||
for (int i = 0; i < 6; i++) {
|
||||
u16 c = (u16)entry[14 + i * 2] | ((u16)entry[15 + i * 2] << 8);
|
||||
buf->chars[base + 5 + i] = c;
|
||||
}
|
||||
/* name3: entry[28..31] = 2 个 UTF-16 LE 字符 */
|
||||
for (int i = 0; i < 2; i++) {
|
||||
u16 c = (u16)entry[28 + i * 2] | ((u16)entry[29 + i * 2] << 8);
|
||||
buf->chars[base + 11 + i] = c;
|
||||
}
|
||||
return 0; /* LFN 项,还不是短名项 */
|
||||
}
|
||||
|
||||
/* 短名项:校验 checksum */
|
||||
u8 cksum = fat32_lfn_checksum(entry);
|
||||
if (buf->checksum != 0 && buf->checksum == cksum) {
|
||||
/* LFN 链有效——计算实际字符数(截断于 0x0000 终止符或 0xFFFF 填充符) */
|
||||
int n = 0;
|
||||
for (int i = 0; i < FAT32_LFN_MAX; i++) {
|
||||
if (buf->chars[i] == 0x0000 || buf->chars[i] == 0xFFFF) break;
|
||||
n++;
|
||||
}
|
||||
buf->count = n;
|
||||
buf->valid = 1;
|
||||
} else {
|
||||
/* checksum 不匹配或无 LFN 链:清空,回退 8.3 */
|
||||
fat32_lfn_init(buf);
|
||||
}
|
||||
return 1; /* 短名项处理完毕 */
|
||||
}
|
||||
|
||||
/* 从 LFN 缓冲提取 ASCII 名(UTF-16->ASCII 缩窄,仅 BMP U+0000..U+007F)。
|
||||
* out_cap 为 out 缓冲容量(含 NUL)。
|
||||
* 返回字符串长度(不含 NUL),-1=无有效 LFN 或含非 ASCII 字符。 */
|
||||
static inline int fat32_lfn_to_ascii(const fat32_lfn_buf *buf, char *out, int out_cap) {
|
||||
if (!buf->valid || buf->count == 0 || out_cap <= 0) return -1;
|
||||
int n = buf->count;
|
||||
if (n >= out_cap) n = out_cap - 1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
u16 c = buf->chars[i];
|
||||
if (c > 0x7F) return -1; /* 含非 ASCII 字符 */
|
||||
out[i] = (char)(u8)c;
|
||||
}
|
||||
out[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ASCII 大小写不敏感字符串比较 */
|
||||
static inline int fat32_lfn_streq_ci(const char *a, const char *b) {
|
||||
if (!a || !b) return 0;
|
||||
while (*a && *b) {
|
||||
char ca = *a, cb = *b;
|
||||
if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 32);
|
||||
if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 32);
|
||||
if (ca != cb) return 0;
|
||||
a++; b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
/* 判断 ASCII 字符串是否以指定后缀结尾(大小写不敏感)。
|
||||
* 例如 fat32_lfn_ends_with_ci("libtest.so", ".so") -> 1 */
|
||||
static inline int fat32_lfn_ends_with_ci(const char *s, const char *suffix) {
|
||||
if (!s || !suffix) return 0;
|
||||
int sl = 0, xl = 0;
|
||||
while (s[sl]) sl++;
|
||||
while (suffix[xl]) xl++;
|
||||
if (xl > sl || xl == 0) return 0;
|
||||
for (int i = 0; i < xl; i++) {
|
||||
char a = s[sl - xl + i];
|
||||
char b = suffix[i];
|
||||
if (a >= 'a' && a <= 'z') a = (char)(a - 32);
|
||||
if (b >= 'a' && b <= 'z') b = (char)(b - 32);
|
||||
if (a != b) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 匹配目录项:优先匹配 LFN 长名(大小写不敏感),回退 8.3 短名。
|
||||
* buf : LFN 收集缓冲(短名项处理后)
|
||||
* name11 : 短名项的 11 字节 8.3 名
|
||||
* target_long: 目标长名(ASCII,可为 NULL=不匹配长名)
|
||||
* target83 : 目标 8.3 名(11 字节,可为 NULL=不匹配 8.3)
|
||||
* 返回 1=匹配,0=不匹配。
|
||||
* FAT32 规范:文件名查找大小写不敏感。 */
|
||||
static inline int fat32_lfn_match(const fat32_lfn_buf *buf, const u8 name11[11],
|
||||
const char *target_long, const char *target83) {
|
||||
/* 1. 优先匹配 LFN 长名 */
|
||||
if (buf->valid && buf->count > 0 && target_long) {
|
||||
char ascii[FAT32_LFN_MAX];
|
||||
int n = fat32_lfn_to_ascii(buf, ascii, sizeof(ascii));
|
||||
if (n >= 0 && fat32_lfn_streq_ci(ascii, target_long)) return 1;
|
||||
}
|
||||
/* 2. 回退 8.3 短名匹配(精确比较 11 字节) */
|
||||
if (target83 && name11) {
|
||||
for (int i = 0; i < 11; i++) {
|
||||
if (name11[i] != (u8)target83[i]) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 从 8.3 短名(11 字节)生成可显示 ASCII 字符串(如 "README.TXT")。
|
||||
* 与 fat32_io.h 的 f32_name_from_83 功能相同,独立提供以避免循环依赖。 */
|
||||
static inline void fat32_lfn_short_to_str(const u8 name11[11], char out[13]) {
|
||||
int p = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (name11[j] == ' ') break;
|
||||
out[p++] = (char)name11[j];
|
||||
}
|
||||
if (name11[8] != ' ') {
|
||||
out[p++] = '.';
|
||||
for (int j = 8; j < 11; j++) {
|
||||
if (name11[j] == ' ') break;
|
||||
out[p++] = (char)name11[j];
|
||||
}
|
||||
}
|
||||
out[p] = 0;
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* 第二部分:FAT32 读写库(原 fat32_io.h)
|
||||
* =====================================================================
|
||||
*
|
||||
* 通过 f32_init() 注入 block_read/block_write 函数指针(来自 dkm_kernel_api + 0xA8)。
|
||||
*
|
||||
* 能力:FAT32 根目录文件的读/写/删/列举,支持 FAT 链跟随、多簇文件。
|
||||
* 限制:仅根目录(不支持子目录,Phase 2.2 fileman 将扩展);单文件最大 256KB。
|
||||
*
|
||||
* 用法:
|
||||
* f32_init(block_read, block_write) - 初始化(block_write 可为 NULL,仅读场景)
|
||||
* f32_name_to_83("file.txt", name11) - "file.txt" -> 11 字符 8.3 名
|
||||
* f32_read_root_file(name11, &data, &size) - 读根目录文件到 f32_data 缓冲
|
||||
* f32_write_root_file(name11, data, size) - 写/替换根目录文件
|
||||
* f32_delete_root_file(name11) - 删除根目录文件
|
||||
* f32_list_root(emit_cb, user_data) - 列举根目录
|
||||
*
|
||||
* 缓冲区:每个包含本头的 .elf 独立持有 256KB g_disk + 4KB g_cluster + 256KB g_fdata(共 ~516KB BSS)。
|
||||
* 与 net_stack.h 同模式,接受 static 缓冲的代价以换取无库依赖。
|
||||
*/
|
||||
|
||||
/* ---- block 设备函数类型(与 shell/DSK 一致:index 参数固定 0) ---- */
|
||||
typedef int (*f32_block_read_fn)(u32 index, u64 lba, u32 count, void *buf);
|
||||
typedef int (*f32_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf);
|
||||
|
||||
static f32_block_read_fn f32_blk_read = 0;
|
||||
static f32_block_write_fn f32_blk_write = 0;
|
||||
|
||||
/* ---- FAT32 BPB / 目录项结构(packed,与 DSK/shell 一致) ---- */
|
||||
typedef struct __attribute__((packed)) {
|
||||
u8 jmp[3]; char oem[8]; u16 bps; u8 spc; u16 rsvd; u8 fc; u16 root_ent;
|
||||
u16 ts16; u8 media; u16 spf16; u16 spt; u16 heads; u32 hidden; u32 ts32;
|
||||
u32 spf; u16 flags; u16 ver; u32 root_clus; u16 fsi; u16 bkboot;
|
||||
u8 res[12]; u8 drv; u8 ntfl; u8 sig; u32 ser; char lbl[11]; char typ[8];
|
||||
u8 code[420]; u16 boot_sig;
|
||||
} f32_bpb;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char name[11]; u8 attr; u8 ntr; u8 ctenth;
|
||||
u16 ctime; u16 cdate; u16 adate; u16 chigh;
|
||||
u16 wtime; u16 wdate; u16 clow; u32 fsize;
|
||||
} f32_dirent;
|
||||
|
||||
/* ---- 内部小工具 ---- */
|
||||
static inline u32 f32_r32(const u8 *p) { return (u32)p[0]|((u32)p[1]<<8)|((u32)p[2]<<16)|((u32)p[3]<<24); }
|
||||
static inline u16 f32_r16(const u8 *p) { return (u16)p[0]|((u16)p[1]<<8); }
|
||||
static inline int f32_neq11(const char *a, const char *b) {
|
||||
for (int i = 0; i < 11; i++) if (a[i] != b[i]) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- 批量读写缓冲区(与 DSK/shell 对齐:256 扇区覆盖 BPB+FAT+根目录+小文件) ---- */
|
||||
/* 文件数据缓冲大小可由包含者在 include 前 #define F32_DATA_BYTES 覆盖
|
||||
* (cmd.elf 需要装载 2MB+ 的 PE64 程序,定义为 4MB;其余默认 256KB)。 */
|
||||
#ifndef F32_DATA_BYTES
|
||||
#define F32_DATA_BYTES 262144
|
||||
#endif
|
||||
static u8 f32_disk[131072]; /* 256 扇区 BPB+FAT+根目录缓存 */
|
||||
static u8 f32_cluster[4096]; /* 单簇缓冲 */
|
||||
static u8 f32_data[F32_DATA_BYTES]; /* 文件数据缓冲 */
|
||||
static int f32_disk_loaded = 0; /* f32_disk 是否已加载 BPB+FAT */
|
||||
|
||||
/* 初始化:注入 block_read/block_write 函数指针 */
|
||||
static inline void f32_init(f32_block_read_fn rd, f32_block_write_fn wr) {
|
||||
f32_blk_read = rd;
|
||||
f32_blk_write = wr;
|
||||
f32_disk_loaded = 0;
|
||||
}
|
||||
|
||||
static inline int f32_read_sectors(u32 lba, u32 count, u8 *out) {
|
||||
return f32_blk_read ? f32_blk_read(0, lba, count, out) : -1;
|
||||
}
|
||||
static inline int f32_write_sectors(u32 lba, u32 count, const u8 *buf) {
|
||||
return f32_blk_write ? f32_blk_write(0, lba, count, buf) : -1;
|
||||
}
|
||||
|
||||
/* 确保 f32_disk 已加载 BPB+FAT+根目录(256 扇区) */
|
||||
static inline int f32_disk_load(void) {
|
||||
if (f32_disk_loaded) return 0;
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
if (bpb->boot_sig != 0xAA55 || bpb->bps != 512) return -2;
|
||||
if (bpb->spf == 0 || bpb->root_clus < 2) return -3;
|
||||
f32_disk_loaded = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 8.3 名字转换 ---- */
|
||||
|
||||
/* "file.txt" / "NAME" -> 11 字符 8.3 名(大写,空格填充)
|
||||
* 返回 0 成功,-1 失败(过长或非法字符) */
|
||||
static inline int f32_name_to_83(const char *in, char out[11]) {
|
||||
for (int i = 0; i < 11; i++) out[i] = ' ';
|
||||
int inlen = 0;
|
||||
while (in[inlen] && inlen < 13) inlen++;
|
||||
if (inlen == 0 || inlen > 12) return -1;
|
||||
int dot = -1;
|
||||
for (int i = 0; i < inlen; i++) if (in[i] == '.') dot = i;
|
||||
int base_end = (dot >= 0) ? dot : inlen;
|
||||
if (base_end == 0 || base_end > 8) return -1;
|
||||
for (int j = 0; j < base_end; j++) {
|
||||
char c = in[j];
|
||||
if (c >= 'a' && c <= 'z') c -= 32;
|
||||
if (c == '.' || c == ' ' || c == '/' || c == '\\') return -1;
|
||||
out[j] = c;
|
||||
}
|
||||
if (dot >= 0) {
|
||||
int extlen = inlen - dot - 1;
|
||||
if (extlen > 3) return -1;
|
||||
for (int j = 0; j < extlen; j++) {
|
||||
char c = in[dot + 1 + j];
|
||||
if (c >= 'a' && c <= 'z') c -= 32;
|
||||
if (c == '.' || c == ' ' || c == '/' || c == '\\') return -1;
|
||||
out[8 + j] = c;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 11 字符 8.3 名 -> 可显示字符串(如 "README.TXT") */
|
||||
static inline void f32_name_from_83(const char in[11], char out[13]) {
|
||||
int p = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (in[j] == ' ') break;
|
||||
out[p++] = in[j];
|
||||
}
|
||||
if (in[8] != ' ') {
|
||||
out[p++] = '.';
|
||||
for (int j = 8; j < 11; j++) {
|
||||
if (in[j] == ' ') break;
|
||||
out[p++] = in[j];
|
||||
}
|
||||
}
|
||||
out[p] = 0;
|
||||
}
|
||||
|
||||
/* ---- 根目录文件查找 ---- */
|
||||
|
||||
/* 在根目录簇链中查找 11 字符名,输出首簇号与文件大小 */
|
||||
static inline int f32_find_in_root(const u8 *clus, u32 clus_sectors, const char *target,
|
||||
u32 *out_clus, u32 *out_size, u32 *out_idx) {
|
||||
const f32_dirent *dir = (const f32_dirent *)clus;
|
||||
for (u32 e = 0; e * 32 < clus_sectors * 512; e++) {
|
||||
if (dir[e].name[0] == 0) break;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, target)) {
|
||||
*out_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
*out_size = dir[e].fsize;
|
||||
if (out_idx) *out_idx = e;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 读取根目录下指定 8.3 名文件到 f32_data。
|
||||
* 返回 0 成功,*out_data 指向 f32_data,*out_size 为字节数;非 0 失败。 */
|
||||
static inline int f32_read_root_file(const char *name11, u8 **out_data, u32 *out_size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
|
||||
u32 clus = bpb->root_clus;
|
||||
u32 found_clus = 0, found_size = 0;
|
||||
int found = 0;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8 && !found) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
if (f32_find_in_root(cb, spc, name11, &found_clus, &found_size, 0) == 0) { found = 1; break; }
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
if (!found) return -5;
|
||||
if (found_size > sizeof(f32_data)) return -6;
|
||||
|
||||
u8 *dst = f32_data; u32 remaining = found_size; u32 fc = found_clus;
|
||||
while (fc >= 2 && fc < 0x0FFFFFF8 && remaining > 0) {
|
||||
u32 fc_lba = data_lba + (fc - 2) * spc;
|
||||
u32 fc_bytes = spc * 512;
|
||||
if (fc_bytes > remaining) fc_bytes = remaining;
|
||||
const u8 *fb;
|
||||
if ((u64)fc_lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
fb = f32_disk + (u64)fc_lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(fc_lba, 8, f32_cluster) != 0) return -7;
|
||||
fb = f32_cluster;
|
||||
}
|
||||
for (u32 b = 0; b < fc_bytes; b++) dst[b] = fb[b];
|
||||
dst += fc_bytes; remaining -= fc_bytes;
|
||||
u32 fo = fat_byte_off + fc * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
*out_data = f32_data; *out_size = found_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 写入根目录下指定 8.3 名文件(存在则替换,不存在则新建)。
|
||||
* 依赖 f32_disk 已加载 BPB+FAT。返回 0 成功。 */
|
||||
static inline int f32_write_root_file(const char *name11, const u8 *data, u32 size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
if (!f32_blk_write) return -10;
|
||||
/* 写入前重新加载 BPB+FAT,保证与盘上一致 */
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -2;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 spc = bpb->spc;
|
||||
u32 fat_lba = bpb->rsvd;
|
||||
u32 fat_sectors = bpb->spf;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 root_clus = bpb->root_clus;
|
||||
u32 cluster_bytes = spc * 512;
|
||||
|
||||
u32 root_lba = data_lba + (root_clus - 2) * spc;
|
||||
u8 *root_buf = f32_disk + (u64)root_lba * 512;
|
||||
u32 max_entries = cluster_bytes / 32;
|
||||
f32_dirent *dir = (f32_dirent *)root_buf;
|
||||
int free_entry = -1;
|
||||
u32 existing_clus = 0;
|
||||
int found = 0;
|
||||
u32 existing_idx = 0;
|
||||
for (u32 e = 0; e < max_entries; e++) {
|
||||
if (dir[e].name[0] == 0) { if (free_entry < 0) free_entry = (int)e; break; }
|
||||
if ((u8)dir[e].name[0] == 0xE5) { if (free_entry < 0) free_entry = (int)e; continue; }
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, name11)) {
|
||||
existing_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
found = 1; existing_idx = e;
|
||||
if (free_entry < 0) free_entry = (int)e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 bytes_needed = size > 0 ? size : 1;
|
||||
u32 clusters_needed = (bytes_needed + cluster_bytes - 1) / cluster_bytes;
|
||||
u32 first_clus = 0;
|
||||
u32 prev_clus = 0;
|
||||
|
||||
if (found && existing_clus >= 2) {
|
||||
first_clus = existing_clus;
|
||||
u32 cur = existing_clus;
|
||||
u32 count = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && count < clusters_needed) {
|
||||
prev_clus = cur;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
count++;
|
||||
}
|
||||
while (count < clusters_needed) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
if (pfo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
count++;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
/* 释放多余旧簇 */
|
||||
u32 next = 0;
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
while (next >= 2 && next < 0x0FFFFFF8) {
|
||||
u32 nfo = fat_byte_off + next * 4;
|
||||
u32 nn = 0;
|
||||
if (nfo + 4 <= sizeof(f32_disk)) nn = f32_r32(f32_disk + nfo) & 0x0FFFFFFF;
|
||||
f32_disk[nfo] = 0; f32_disk[nfo+1] = 0; f32_disk[nfo+2] = 0; f32_disk[nfo+3] = 0;
|
||||
next = nn;
|
||||
}
|
||||
} else {
|
||||
if (free_entry < 0) return -4;
|
||||
for (u32 i = 0; i < clusters_needed; i++) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
if (i == 0) first_clus = newc;
|
||||
if (prev_clus >= 2) {
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
/* 写数据到簇 */
|
||||
u32 remaining = size;
|
||||
const u8 *src = data;
|
||||
u32 cur = first_clus;
|
||||
u32 ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
u32 chunk = remaining < cluster_bytes ? remaining : cluster_bytes;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
u8 *dst = f32_disk + (u64)clba * 512;
|
||||
for (u32 b = 0; b < chunk; b++) dst[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) dst[b] = 0;
|
||||
} else {
|
||||
if (f32_read_sectors(clba, spc, f32_cluster) != 0) return -5;
|
||||
for (u32 b = 0; b < chunk; b++) f32_cluster[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) f32_cluster[b] = 0;
|
||||
if (f32_write_sectors(clba, spc, f32_cluster) != 0) return -6;
|
||||
}
|
||||
src += chunk; remaining -= chunk; ci++;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
/* 更新目录项 */
|
||||
u32 entry_idx = found ? existing_idx : (u32)free_entry;
|
||||
f32_dirent *e = &dir[entry_idx];
|
||||
if (!found) {
|
||||
for (int i = 0; i < 11; i++) e->name[i] = name11[i];
|
||||
e->attr = 0x20; e->ntr = 0; e->ctenth = 0; e->ctime = 0; e->cdate = 0; e->adate = 0;
|
||||
}
|
||||
e->chigh = (u16)((first_clus >> 16) & 0xFFFF);
|
||||
e->clow = (u16)(first_clus & 0xFFFF);
|
||||
e->fsize = size;
|
||||
|
||||
/* 回写 FAT(两份)+ 根目录 + 缓冲区内的数据簇 */
|
||||
for (u32 f = 0; f < bpb->fc; f++) {
|
||||
u32 flba = fat_lba + f * fat_sectors;
|
||||
if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -7;
|
||||
}
|
||||
u32 root_dir_lba = data_lba + (root_clus - 2) * spc;
|
||||
if ((u64)root_dir_lba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
if (f32_write_sectors(root_dir_lba, spc, f32_disk + (u64)root_dir_lba * 512) != 0) return -8;
|
||||
}
|
||||
cur = first_clus; ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
if (f32_write_sectors(clba, spc, f32_disk + (u64)clba * 512) != 0) return -9;
|
||||
}
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
ci++;
|
||||
}
|
||||
f32_disk_loaded = 0; /* 下次操作重新加载,避免使用脏缓存 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 在指定目录簇中写入文件(存在则替换,不存在则新建)。
|
||||
* 泛化自 f32_write_root_file:目录可跨簇链,目录项回写到其所在簇。
|
||||
* dir_clus : 目标目录首簇(0 = 根目录)
|
||||
* name11 : 11 字符 8.3 名
|
||||
* 返回 0 成功;负值失败(-4 = 目录链无空闲槽,不扩展目录簇链)。
|
||||
* 注意:仅 8.3 短名匹配/创建,不生成 LFN 项。 */
|
||||
static inline int f32_write_file_in_dir(u32 dir_clus, const char *name11, const u8 *data, u32 size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
if (!f32_blk_write) return -10;
|
||||
/* 写入前重新加载 BPB+FAT,保证与盘上一致 */
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -2;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 spc = bpb->spc;
|
||||
u32 fat_lba = bpb->rsvd;
|
||||
u32 fat_sectors = bpb->spf;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 cluster_bytes = spc * 512;
|
||||
if (dir_clus == 0) dir_clus = bpb->root_clus;
|
||||
|
||||
/* 第一遍:沿目录簇链查找现有项与空闲槽,记录其所在簇与索引 */
|
||||
u32 found_clus = 0, existing_clus = 0;
|
||||
int found = 0;
|
||||
u32 found_idx = 0, free_idx = 0;
|
||||
u32 free_slot_clus = 0;
|
||||
int have_free = 0;
|
||||
u32 dclus = dir_clus;
|
||||
while (dclus >= 2 && dclus < 0x0FFFFFF8 && !found) {
|
||||
u32 dlba = data_lba + (dclus - 2) * spc;
|
||||
const u8 *db;
|
||||
if ((u64)dlba * 512 + cluster_bytes <= 256ULL * 512)
|
||||
db = f32_disk + (u64)dlba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(dlba, spc, f32_cluster) != 0) return -11;
|
||||
db = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)db;
|
||||
u32 max_entries = cluster_bytes / 32;
|
||||
for (u32 e = 0; e < max_entries; e++) {
|
||||
if (dir[e].name[0] == 0) {
|
||||
if (!have_free) { have_free = 1; free_slot_clus = dclus; free_idx = e; }
|
||||
goto scan_done; /* 0x00 = 目录结尾 */
|
||||
}
|
||||
if ((u8)dir[e].name[0] == 0xE5) {
|
||||
if (!have_free) { have_free = 1; free_slot_clus = dclus; free_idx = e; }
|
||||
continue;
|
||||
}
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, name11)) {
|
||||
existing_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
found = 1; found_idx = e; found_clus = dclus;
|
||||
if (!have_free) { have_free = 1; free_slot_clus = dclus; free_idx = e; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) break;
|
||||
u32 fo = fat_byte_off + dclus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
dclus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
scan_done:
|
||||
; /* C89: 标签后必须是语句 */
|
||||
|
||||
u32 bytes_needed = size > 0 ? size : 1;
|
||||
u32 clusters_needed = (bytes_needed + cluster_bytes - 1) / cluster_bytes;
|
||||
u32 first_clus = 0;
|
||||
u32 prev_clus = 0;
|
||||
|
||||
if (found && existing_clus >= 2) {
|
||||
first_clus = existing_clus;
|
||||
u32 cur = existing_clus;
|
||||
u32 count = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && count < clusters_needed) {
|
||||
prev_clus = cur;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
count++;
|
||||
}
|
||||
while (count < clusters_needed) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
if (pfo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
count++;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
/* 释放多余旧簇 */
|
||||
u32 next = 0;
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
while (next >= 2 && next < 0x0FFFFFF8) {
|
||||
u32 nfo = fat_byte_off + next * 4;
|
||||
u32 nn = 0;
|
||||
if (nfo + 4 <= sizeof(f32_disk)) nn = f32_r32(f32_disk + nfo) & 0x0FFFFFFF;
|
||||
f32_disk[nfo] = 0; f32_disk[nfo+1] = 0; f32_disk[nfo+2] = 0; f32_disk[nfo+3] = 0;
|
||||
next = nn;
|
||||
}
|
||||
} else {
|
||||
if (!have_free) return -4; /* 目录链满,不扩展 */
|
||||
for (u32 i = 0; i < clusters_needed; i++) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
if (i == 0) first_clus = newc;
|
||||
if (prev_clus >= 2) {
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
/* 写数据到簇(窗口内直接改 f32_disk,窗口外 RMW) */
|
||||
u32 remaining = size;
|
||||
const u8 *src = data;
|
||||
u32 cur = first_clus;
|
||||
u32 ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
u32 chunk = remaining < cluster_bytes ? remaining : cluster_bytes;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
u8 *dst = f32_disk + (u64)clba * 512;
|
||||
for (u32 b = 0; b < chunk; b++) dst[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) dst[b] = 0;
|
||||
} else {
|
||||
if (f32_read_sectors(clba, spc, f32_cluster) != 0) return -5;
|
||||
for (u32 b = 0; b < chunk; b++) f32_cluster[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) f32_cluster[b] = 0;
|
||||
if (f32_write_sectors(clba, spc, f32_cluster) != 0) return -6;
|
||||
}
|
||||
src += chunk; remaining -= chunk; ci++;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
/* 更新目录项(所在簇可能不在窗口内,需 RMW) */
|
||||
u32 entry_clus = found ? found_clus : free_slot_clus;
|
||||
u32 entry_idx = found ? found_idx : free_idx;
|
||||
u32 elba = data_lba + (entry_clus - 2) * spc;
|
||||
u8 *ebuf;
|
||||
int ebuf_windowed = ((u64)elba * 512 + cluster_bytes <= 256ULL * 512);
|
||||
if (ebuf_windowed) ebuf = f32_disk + (u64)elba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(elba, spc, f32_cluster) != 0) return -12;
|
||||
ebuf = f32_cluster;
|
||||
}
|
||||
f32_dirent *e = &((f32_dirent *)ebuf)[entry_idx];
|
||||
if (!found) {
|
||||
for (int i = 0; i < 11; i++) e->name[i] = name11[i];
|
||||
e->attr = 0x20; e->ntr = 0; e->ctenth = 0; e->ctime = 0; e->cdate = 0; e->adate = 0;
|
||||
e->wtime = 0; e->wdate = 0;
|
||||
}
|
||||
e->chigh = (u16)((first_clus >> 16) & 0xFFFF);
|
||||
e->clow = (u16)(first_clus & 0xFFFF);
|
||||
e->fsize = size;
|
||||
|
||||
/* 回写 FAT(两份)+ 目录项簇 + 窗口内数据簇 */
|
||||
for (u32 f = 0; f < bpb->fc; f++) {
|
||||
u32 flba = fat_lba + f * fat_sectors;
|
||||
if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -7;
|
||||
}
|
||||
if (ebuf_windowed) {
|
||||
if (f32_write_sectors(elba, spc, f32_disk + (u64)elba * 512) != 0) return -8;
|
||||
} else {
|
||||
if (f32_write_sectors(elba, spc, f32_cluster) != 0) return -8;
|
||||
}
|
||||
cur = first_clus; ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
if (f32_write_sectors(clba, spc, f32_disk + (u64)clba * 512) != 0) return -9;
|
||||
}
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
ci++;
|
||||
}
|
||||
f32_disk_loaded = 0; /* 下次操作重新加载,避免使用脏缓存 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 删除根目录下指定 8.3 名文件:清空簇链 + 标记目录项为 0xE5。
|
||||
* 返回 0 成功,-1 未找到,其他负值失败。 */
|
||||
static inline int f32_delete_root_file(const char *name11) {
|
||||
if (f32_disk_load() != 0) return -2;
|
||||
if (!f32_blk_write) return -10;
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -3;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 spc = bpb->spc;
|
||||
u32 fat_lba = bpb->rsvd;
|
||||
u32 fat_sectors = bpb->spf;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 root_clus = bpb->root_clus;
|
||||
|
||||
u32 root_lba = data_lba + (root_clus - 2) * spc;
|
||||
u8 *root_buf = f32_disk + (u64)root_lba * 512;
|
||||
u32 max_entries = (spc * 512) / 32;
|
||||
f32_dirent *dir = (f32_dirent *)root_buf;
|
||||
u32 found_idx = 0xFFFFFFFFu;
|
||||
for (u32 e = 0; e < max_entries; e++) {
|
||||
if (dir[e].name[0] == 0) break;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, name11)) { found_idx = e; break; }
|
||||
}
|
||||
if (found_idx == 0xFFFFFFFFu) return -1;
|
||||
|
||||
/* 释放簇链 */
|
||||
u32 cur = (u32)f32_r16((const u8*)&dir[found_idx].clow) | ((u32)f32_r16((const u8*)&dir[found_idx].chigh) << 16);
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8) {
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
u32 next = 0;
|
||||
if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
f32_disk[fo] = 0; f32_disk[fo+1] = 0; f32_disk[fo+2] = 0; f32_disk[fo+3] = 0;
|
||||
cur = next;
|
||||
}
|
||||
/* 标记目录项为已删除 */
|
||||
dir[found_idx].name[0] = (char)0xE5;
|
||||
|
||||
/* 回写 FAT + 根目录 */
|
||||
for (u32 f = 0; f < bpb->fc; f++) {
|
||||
u32 flba = fat_lba + f * fat_sectors;
|
||||
if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -4;
|
||||
}
|
||||
if (f32_write_sectors(root_lba, spc, f32_disk + (u64)root_lba * 512) != 0) return -5;
|
||||
f32_disk_loaded = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 列举根目录条目回调签名:返回 0 继续,非 0 停止 */
|
||||
typedef int (*f32_list_cb)(const char *name, u32 size, u8 attr, void *user_data);
|
||||
|
||||
/* 列出根目录所有文件/目录,调用回调输出。
|
||||
* 回调返回非 0 立即停止。返回 0 成功(含回调提前停止),负值失败。 */
|
||||
static inline int f32_list_root(f32_list_cb emit, void *user_data) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = bpb->root_clus;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)cb;
|
||||
for (u32 e = 0; e * 32 < spc * 512; e++) {
|
||||
if (dir[e].name[0] == 0) goto done;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
char disp[13];
|
||||
f32_name_from_83(dir[e].name, disp);
|
||||
if (emit(disp, dir[e].fsize, dir[e].attr, user_data) != 0) goto done;
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 子目录遍历支持(Phase 2.2 fileman 扩展) ---- */
|
||||
|
||||
/* 目录条目(供 fileman 等工具使用) */
|
||||
typedef struct {
|
||||
char name[13]; /* 8.3 显示名 */
|
||||
u32 clus; /* 首簇号 */
|
||||
u32 size; /* 文件大小(目录为 0) */
|
||||
u8 attr;
|
||||
u8 is_dir;
|
||||
u8 has_lfn; /* 1=long_name 字段有效 */
|
||||
char long_name[260]; /* LFN 长文件名(ASCII) */
|
||||
} f32_entry;
|
||||
|
||||
typedef int (*f32_list_entry_cb)(const f32_entry *e, void *user_data);
|
||||
|
||||
/* 列举指定目录簇的内容(含 LFN 长文件名)。
|
||||
* dir_clus = 0 表示根目录(自动从 BPB 取 root_clus)。
|
||||
* 回调返回非 0 立即停止。返回 0 成功,负值失败。 */
|
||||
static inline int f32_list_dir(u32 dir_clus, f32_list_entry_cb emit, void *user_data) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = (dir_clus == 0) ? bpb->root_clus : dir_clus;
|
||||
/* LFN 缓冲必须在簇循环外声明:跨簇 LFN 链需要持续存活 */
|
||||
fat32_lfn_buf lfn;
|
||||
fat32_lfn_init(&lfn);
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const u8 *entries = (const u8 *)cb;
|
||||
u32 entry_count = (spc * 512) / 32;
|
||||
for (u32 e = 0; e < entry_count; e++) {
|
||||
const u8 *entry = entries + e * 32;
|
||||
if (entry[0] == 0) goto done;
|
||||
if ((u8)entry[0] == 0xE5) {
|
||||
/* 已删除项:清空 LFN 缓冲防止残留污染 */
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
const f32_dirent *de = (const f32_dirent *)entry;
|
||||
int is_short = fat32_lfn_process(&lfn, entry);
|
||||
if (!is_short) continue; /* LFN 片段,等待短名项 */
|
||||
if (de->attr & 0x08) continue; /* 卷标 */
|
||||
/* 跳过 "." 和 ".." 条目 */
|
||||
if (de->name[0] == '.' && (de->name[1] == ' ' || de->name[1] == '.'))
|
||||
continue;
|
||||
f32_entry fe;
|
||||
f32_name_from_83(de->name, fe.name);
|
||||
fe.clus = (u32)f32_r16((const u8*)&de->clow) | ((u32)f32_r16((const u8*)&de->chigh) << 16);
|
||||
fe.size = de->fsize;
|
||||
fe.attr = de->attr;
|
||||
fe.is_dir = (de->attr & 0x10) ? 1 : 0;
|
||||
fe.has_lfn = 0;
|
||||
fe.long_name[0] = 0;
|
||||
if (lfn.valid) {
|
||||
int ln = fat32_lfn_to_ascii(&lfn, fe.long_name, sizeof(fe.long_name));
|
||||
if (ln >= 0) fe.has_lfn = 1;
|
||||
}
|
||||
if (emit(&fe, user_data) != 0) goto done;
|
||||
/* 短名项处理完毕,重置 LFN 缓冲等待下一条链 */
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按首簇号 + 大小读取文件内容到 out_buf。
|
||||
* out_buf 至少 size 字节容量。返回 0 成功,负值失败。 */
|
||||
static inline int f32_read_file_by_clus(u32 clus, u32 size, u8 *out_buf, u32 buf_cap) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
if (size > buf_cap) size = buf_cap;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 remaining = size;
|
||||
u8 *dst = out_buf;
|
||||
u32 fc = clus;
|
||||
while (fc >= 2 && fc < 0x0FFFFFF8 && remaining > 0) {
|
||||
u32 fc_lba = data_lba + (fc - 2) * spc;
|
||||
u32 fc_bytes = spc * 512;
|
||||
if (fc_bytes > remaining) fc_bytes = remaining;
|
||||
const u8 *fb;
|
||||
if ((u64)fc_lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
fb = f32_disk + (u64)fc_lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(fc_lba, 8, f32_cluster) != 0) return -7;
|
||||
fb = f32_cluster;
|
||||
}
|
||||
for (u32 b = 0; b < fc_bytes; b++) dst[b] = fb[b];
|
||||
dst += fc_bytes; remaining -= fc_bytes;
|
||||
u32 fo = fat_byte_off + fc * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- LFN 长文件名支持(Phase 1 SYSTEM/lib 兼容层) ---- */
|
||||
|
||||
/* 在指定目录簇中按长文件名查找文件/子目录。
|
||||
* dir_clus : 起始目录簇(0=根目录)
|
||||
* long_name : 目标长文件名(ASCII,大小写不敏感);为 NULL 时仅按 8.3 短名匹配
|
||||
* is_dir : 1 只匹配子目录,0 只匹配文件
|
||||
* out_clus : 输出首簇号
|
||||
* out_size : 输出文件大小
|
||||
* 返回 0 成功,-1 未找到 / 错误。
|
||||
* LFN 缓冲跨簇遍历持续存活,支持跨簇边界的 LFN 链。 */
|
||||
static inline int f32_find_in_dir_lfn(u32 dir_clus, const char *long_name,
|
||||
int is_dir, u32 *out_clus, u32 *out_size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = (dir_clus == 0) ? bpb->root_clus : dir_clus;
|
||||
|
||||
/* LFN 缓冲必须在簇循环外声明:跨簇 LFN 链需要持续存活 */
|
||||
fat32_lfn_buf lfn;
|
||||
fat32_lfn_init(&lfn);
|
||||
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const u8 *entries = (const u8 *)cb;
|
||||
u32 entry_count = (spc * 512) / 32;
|
||||
for (u32 e = 0; e < entry_count; e++) {
|
||||
const u8 *entry = entries + e * 32;
|
||||
if (entry[0] == 0) goto not_found;
|
||||
if ((u8)entry[0] == 0xE5) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
const f32_dirent *de = (const f32_dirent *)entry;
|
||||
int is_short = fat32_lfn_process(&lfn, entry);
|
||||
if (!is_short) continue; /* LFN 片段 */
|
||||
if (de->attr & 0x08) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue; /* 卷标 */
|
||||
}
|
||||
int entry_is_dir = (de->attr & 0x10) ? 1 : 0;
|
||||
if (entry_is_dir != is_dir) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
/* 先尝试 LFN 长名匹配,再回退 8.3 匹配 */
|
||||
int matched = 0;
|
||||
if (lfn.valid && long_name) {
|
||||
char ascii[FAT32_LFN_MAX];
|
||||
int n = fat32_lfn_to_ascii(&lfn, ascii, sizeof(ascii));
|
||||
if (n >= 0 && fat32_lfn_streq_ci(ascii, long_name)) matched = 1;
|
||||
}
|
||||
if (!matched && long_name) {
|
||||
/* 用短名构造 8.3 显示串与目标比较(大小写不敏感) */
|
||||
char short_disp[13];
|
||||
fat32_lfn_short_to_str((const u8 *)de->name, short_disp);
|
||||
if (fat32_lfn_streq_ci(short_disp, long_name)) matched = 1;
|
||||
}
|
||||
if (matched) {
|
||||
*out_clus = (u32)f32_r16((const u8 *)&de->clow) |
|
||||
((u32)f32_r16((const u8 *)&de->chigh) << 16);
|
||||
*out_size = de->fsize;
|
||||
return 0;
|
||||
}
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
not_found:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 按 '/' 分隔的路径定位最终目录簇。
|
||||
* path : 路径(如 "lib" 或 "system/deshab64"),每段为长文件名
|
||||
* out_clus : 输出目录簇号
|
||||
* 返回 0 成功,-1 失败。空路径返回根目录簇。 */
|
||||
static inline int f32_find_path_dir_lfn(const char *path, u32 *out_clus) {
|
||||
if (!path || !out_clus) return -1;
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 cur = bpb->root_clus;
|
||||
|
||||
u32 pos = 0;
|
||||
u32 path_len = 0;
|
||||
while (path[path_len]) path_len++;
|
||||
|
||||
/* 空路径或仅 "/" -> 根目录 */
|
||||
if (path_len == 0 || (path_len == 1 && path[0] == '/')) {
|
||||
*out_clus = cur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (pos < path_len) {
|
||||
/* 跳过前导 '/' */
|
||||
while (pos < path_len && path[pos] == '/') pos++;
|
||||
if (pos >= path_len) break;
|
||||
/* 提取路径组件(到下一个 '/' 或结尾) */
|
||||
char comp[260];
|
||||
u32 ci = 0;
|
||||
while (pos < path_len && path[pos] != '/' && ci + 1 < sizeof(comp)) {
|
||||
comp[ci++] = path[pos++];
|
||||
}
|
||||
comp[ci] = 0;
|
||||
if (ci == 0) continue;
|
||||
|
||||
u32 sub_clus = 0, sub_size = 0;
|
||||
if (f32_find_in_dir_lfn(cur, comp, 1, &sub_clus, &sub_size) != 0) return -1;
|
||||
cur = sub_clus;
|
||||
}
|
||||
*out_clus = cur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按长路径读取文件到 f32_data 缓冲。
|
||||
* path : '/' 分隔的长文件名路径(如 "lib/libtest.so")
|
||||
* out_data : 返回 f32_data 内的指针
|
||||
* out_size : 返回文件大小
|
||||
* 返回 0 成功,负值失败。 */
|
||||
static inline int f32_read_path_lfn(const char *path, u8 **out_data, u32 *out_size) {
|
||||
if (!path || !out_data || !out_size) return -1;
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
|
||||
/* 分离目录部分与文件名 */
|
||||
u32 path_len = 0;
|
||||
while (path[path_len]) path_len++;
|
||||
u32 last_slash = 0xFFFFFFFFu;
|
||||
for (u32 i = 0; i < path_len; i++) {
|
||||
if (path[i] == '/') last_slash = i;
|
||||
}
|
||||
const char *fname = (last_slash == 0xFFFFFFFFu) ? path : path + last_slash + 1;
|
||||
|
||||
/* 定位父目录簇 */
|
||||
u32 dir_clus = 0;
|
||||
if (last_slash == 0xFFFFFFFFu) {
|
||||
/* 无 '/' -> 根目录 */
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
dir_clus = bpb->root_clus;
|
||||
} else {
|
||||
char dirpath[260];
|
||||
u32 dn = last_slash;
|
||||
if (dn >= sizeof(dirpath)) dn = sizeof(dirpath) - 1;
|
||||
for (u32 i = 0; i < dn; i++) dirpath[i] = path[i];
|
||||
dirpath[dn] = 0;
|
||||
if (f32_find_path_dir_lfn(dirpath, &dir_clus) != 0) return -2;
|
||||
}
|
||||
|
||||
/* 在父目录中查找文件 */
|
||||
u32 file_clus = 0, file_size = 0;
|
||||
if (f32_find_in_dir_lfn(dir_clus, fname, 0, &file_clus, &file_size) != 0) return -3;
|
||||
if (file_size > sizeof(f32_data)) return -4;
|
||||
|
||||
/* 读取文件数据 */
|
||||
if (f32_read_file_by_clus(file_clus, file_size, f32_data, sizeof(f32_data)) != 0) return -5;
|
||||
*out_data = f32_data;
|
||||
*out_size = file_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按长路径写入文件(存在则替换,不存在则在父目录中新建 8.3 项)。
|
||||
* path : '/' 分隔路径(如 "system/deshab64/FUCK"),文件名段必须可转 8.3
|
||||
* 返回 0 成功,负值失败(-6 = 文件名不是合法 8.3)。 */
|
||||
static inline int f32_write_path_lfn(const char *path, const u8 *data, u32 size) {
|
||||
if (!path || !data) return -1;
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
|
||||
u32 path_len = 0;
|
||||
while (path[path_len]) path_len++;
|
||||
u32 last_slash = 0xFFFFFFFFu;
|
||||
for (u32 i = 0; i < path_len; i++) {
|
||||
if (path[i] == '/') last_slash = i;
|
||||
}
|
||||
const char *fname = (last_slash == 0xFFFFFFFFu) ? path : path + last_slash + 1;
|
||||
|
||||
u32 dir_clus = 0;
|
||||
if (last_slash == 0xFFFFFFFFu) {
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
dir_clus = bpb->root_clus;
|
||||
} else {
|
||||
char dirpath[260];
|
||||
u32 dn = last_slash;
|
||||
if (dn >= sizeof(dirpath)) dn = sizeof(dirpath) - 1;
|
||||
for (u32 i = 0; i < dn; i++) dirpath[i] = path[i];
|
||||
dirpath[dn] = 0;
|
||||
if (f32_find_path_dir_lfn(dirpath, &dir_clus) != 0) return -2;
|
||||
}
|
||||
|
||||
char name11[11];
|
||||
if (f32_name_to_83(fname, name11) != 0) return -6;
|
||||
return f32_write_file_in_dir(dir_clus, name11, data, size);
|
||||
}
|
||||
|
||||
#endif /* DESHAB_FAT32_H */
|
||||
@@ -0,0 +1,91 @@
|
||||
/* deshab/fb.h - 帧缓冲绘制原语
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_pixel/pixel_read/blend/fill_rect/fill_bg/
|
||||
* fill_rounded_rect/rect_outline,统一前缀为 dsb_。
|
||||
* 假设 32bpp BGRA 帧缓冲(Limine framebuffer 标准格式)。
|
||||
*/
|
||||
#ifndef DESHAB_FB_H
|
||||
#define DESHAB_FB_H
|
||||
|
||||
#include "types.h"
|
||||
#include "app.h"
|
||||
|
||||
static inline void dsb_pixel(dsb_app_context *ac, i64 x, i64 y, u32 color) {
|
||||
if (x < 0 || (u64)x >= ac->fb_w || y < 0 || (u64)y >= ac->fb_h) return;
|
||||
u32 *line = (u32 *)((u8 *)ac->fb + (u64)y * ac->fb_pitch);
|
||||
line[x] = color;
|
||||
}
|
||||
|
||||
static inline u32 dsb_pixel_read(dsb_app_context *ac, i64 x, i64 y) {
|
||||
if (x < 0 || (u64)x >= ac->fb_w || y < 0 || (u64)y >= ac->fb_h) return 0;
|
||||
u32 *line = (u32 *)((u8 *)ac->fb + (u64)y * ac->fb_pitch);
|
||||
return line[x];
|
||||
}
|
||||
|
||||
static inline u32 dsb_blend(u32 bg, u32 fg, u32 alpha) {
|
||||
u32 na = 256 - alpha;
|
||||
u32 r = ((bg & 0xFF) * na + (fg & 0xFF) * alpha) >> 8;
|
||||
u32 g = (((bg >> 8) & 0xFF) * na + ((fg >> 8) & 0xFF) * alpha) >> 8;
|
||||
u32 b = (((bg >> 16) & 0xFF) * na + ((fg >> 16) & 0xFF) * alpha) >> 8;
|
||||
return 0xFF000000u | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
|
||||
static inline void dsb_fill_rect(dsb_app_context *ac, i64 x, i64 y, i64 w, i64 h, u32 color) {
|
||||
for (i64 r = 0; r < h; r++) {
|
||||
i64 yy = y + r;
|
||||
if (yy < 0 || (u64)yy >= ac->fb_h) continue;
|
||||
u32 *line = (u32 *)((u8 *)ac->fb + (u64)yy * ac->fb_pitch);
|
||||
for (i64 c = 0; c < w; c++) {
|
||||
i64 xx = x + c;
|
||||
if (xx < 0 || (u64)xx >= ac->fb_w) continue;
|
||||
line[xx] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dsb_fill_bg(dsb_app_context *ac, u32 color) {
|
||||
dsb_fill_rect(ac, 0, 0, (i64)ac->fb_w, (i64)ac->fb_h, color);
|
||||
}
|
||||
|
||||
static inline void dsb_fill_rounded_rect(dsb_app_context *ac, i64 x, i64 y, i64 w, i64 h, u32 color, i64 radius) {
|
||||
i64 r = radius;
|
||||
if (r > w / 2) r = w / 2;
|
||||
if (r > h / 2) r = h / 2;
|
||||
dsb_fill_rect(ac, x + r, y, w - 2 * r, h, color);
|
||||
dsb_fill_rect(ac, x, y + r, r, h - 2 * r, color);
|
||||
dsb_fill_rect(ac, x + w - r, y + r, r, h - 2 * r, color);
|
||||
for (i64 dy = 0; dy < r; dy++)
|
||||
for (i64 dx = 0; dx < r; dx++)
|
||||
if (dx * dx + dy * dy <= r * r) {
|
||||
dsb_pixel(ac, x + r - 1 - dx, y + r - 1 - dy, color);
|
||||
dsb_pixel(ac, x + w - r + dx, y + r - 1 - dy, color);
|
||||
dsb_pixel(ac, x + r - 1 - dx, y + h - r + dy, color);
|
||||
dsb_pixel(ac, x + w - r + dx, y + h - r + dy, color);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dsb_rect_outline(dsb_app_context *ac, i64 x, i64 y, i64 w, i64 h, u32 color, i64 radius) {
|
||||
i64 r = radius;
|
||||
if (r > w / 2) r = w / 2;
|
||||
if (r > h / 2) r = h / 2;
|
||||
for (i64 c = r; c < w - r; c++) {
|
||||
dsb_pixel(ac, x + c, y, color);
|
||||
dsb_pixel(ac, x + c, y + h - 1, color);
|
||||
}
|
||||
for (i64 rr = r; rr < h - r; rr++) {
|
||||
dsb_pixel(ac, x, y + rr, color);
|
||||
dsb_pixel(ac, x + w - 1, y + rr, color);
|
||||
}
|
||||
for (i64 dy = 0; dy <= r; dy++)
|
||||
for (i64 dx = 0; dx <= r; dx++) {
|
||||
i64 d2 = dx * dx + dy * dy;
|
||||
if (d2 <= r * r && d2 > (r - 1) * (r - 1)) {
|
||||
dsb_pixel(ac, x + r - dx, y + r - dy, color);
|
||||
dsb_pixel(ac, x + w - 1 - r + dx, y + r - dy, color);
|
||||
dsb_pixel(ac, x + r - dx, y + h - 1 - r + dy, color);
|
||||
dsb_pixel(ac, x + w - 1 - r + dx, y + h - 1 - r + dy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DESHAB_FB_H */
|
||||
@@ -0,0 +1,51 @@
|
||||
/* deshab/font.h - ASCII 位图字体渲染
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_draw_char/draw_string,统一前缀为 dsb_。
|
||||
*
|
||||
* 前置条件:包含者需先提供位图数据源。两种方式:
|
||||
* 1. #include "ascii_bitmaps.c"(定义 static g_ascii/g_ascii_w/g_ascii_h)
|
||||
* 2. 自行定义 const u8 *g_ascii[95]; i64 g_ascii_w, g_ascii_h;
|
||||
* 再 #include "font.h"。
|
||||
*
|
||||
* 与 deshab_ui.h 使用相同的包含顺序约定。
|
||||
*/
|
||||
#ifndef DESHAB_FONT_H
|
||||
#define DESHAB_FONT_H
|
||||
|
||||
#include "types.h"
|
||||
#include "app.h"
|
||||
#include "fb.h"
|
||||
|
||||
/* 由包含者在 include 本头前提供:
|
||||
* static const u8 *g_ascii[95]; // 95 字符 ' '..'~'
|
||||
* static i64 g_ascii_w, g_ascii_h;
|
||||
*/
|
||||
static inline void dsb_draw_char(dsb_app_context *ac, char ch, i64 x, i64 y, u32 fg, u32 bg) {
|
||||
u32 idx = (u32)(ch - ' ');
|
||||
if (idx > 94) idx = 0;
|
||||
const u8 *glyph = g_ascii[idx];
|
||||
i64 gw = g_ascii_w, gh = g_ascii_h;
|
||||
for (i64 r = 0; r < gh; r++) {
|
||||
i64 yy = y + r;
|
||||
if (yy < 0 || (u64)yy >= ac->fb_h) continue;
|
||||
u32 *line = (u32 *)((u8 *)ac->fb + (u64)yy * ac->fb_pitch);
|
||||
for (i64 c = 0; c < gw; c++) {
|
||||
u8 a = glyph[r * gw + c];
|
||||
if (a == 0) continue;
|
||||
i64 xx = x + c;
|
||||
if (xx < 0 || (u64)xx >= ac->fb_w) continue;
|
||||
line[xx] = (a == 255) ? fg : dsb_blend(bg, fg, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dsb_draw_string(dsb_app_context *ac, const char *s, i64 x, i64 y, u32 fg, u32 bg, i64 step) {
|
||||
i64 cx = x;
|
||||
while (*s) {
|
||||
dsb_draw_char(ac, *s, cx, y, fg, bg);
|
||||
cx += step;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DESHAB_FONT_H */
|
||||
@@ -0,0 +1,115 @@
|
||||
/* deshab/input.h - PS/2 键盘与鼠标输入
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_scan_to_ascii / da_mouse / da_mouse_init /
|
||||
* da_mouse_poll / da_ps2_drain,统一前缀为 dsb_。
|
||||
*
|
||||
* PS/2 鼠标初始化序列与 desktop/main.c:ps2_mouse_init 保持一致,
|
||||
* 必须先冲刷输出缓冲再发命令,避免残留字节污染配置字
|
||||
* (BUG-20260801-008)。
|
||||
*/
|
||||
#ifndef DESHAB_INPUT_H
|
||||
#define DESHAB_INPUT_H
|
||||
|
||||
#include "types.h"
|
||||
#include "portio.h"
|
||||
#include "cursor.h"
|
||||
|
||||
/* ---- 键盘 scan code set 1 -> ASCII ---- */
|
||||
static inline char dsb_scan_to_ascii(u8 sc, int shift) {
|
||||
static const char normal[58] = {
|
||||
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t',
|
||||
'q','w','e','r','t','y','u','i','o','p','[',']','\n',0,
|
||||
'a','s','d','f','g','h','j','k','l',';','\'', '`',0,'\\',
|
||||
'z','x','c','v','b','n','m',',','.','/',0,'*',0,' '
|
||||
};
|
||||
static const char shifted[58] = {
|
||||
0, 27, '!','@','#','$','%','^','&','*','(',')','_','+', 8, '\t',
|
||||
'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0,
|
||||
'A','S','D','F','G','H','J','K','L',':','"','~',0,'|',
|
||||
'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' '
|
||||
};
|
||||
if (sc >= 58) return 0;
|
||||
return shift ? shifted[sc] : normal[sc];
|
||||
}
|
||||
|
||||
/* ---- PS/2 鼠标 ---- */
|
||||
typedef struct dsb_mouse {
|
||||
u8 buf[3];
|
||||
int idx;
|
||||
int has_pkt;
|
||||
} dsb_mouse;
|
||||
|
||||
/* 冲刷 PS/2 控制器输出缓冲(最多 16 字节)。 */
|
||||
static inline void dsb_ps2_drain(void) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!(dsb_inb(0x64) & 1)) break;
|
||||
dsb_inb(0x60);
|
||||
}
|
||||
}
|
||||
|
||||
/* 初始化 PS/2 鼠标:启用 AUX 端口 + 流模式 + 数据报告。
|
||||
* 序列与 desktop/main.c:ps2_mouse_init 一致。 */
|
||||
static inline void dsb_mouse_init(void) {
|
||||
dsb_ps2_drain();
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x64, 0xA8);
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x64, 0x20);
|
||||
for (int t = 0; t < 100000; t++) { if (dsb_inb(0x64) & 1) break; }
|
||||
u8 cfg = dsb_inb(0x60);
|
||||
cfg &= ~0x20; cfg |= 0x02; cfg |= 0x40; /* AUX 时钟启用 + AUX IRQ + 键盘翻译 */
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x64, 0x60);
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x60, cfg);
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x64, 0xD4);
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x60, 0xFF);
|
||||
for (int i = 0; i < 8; i++) { /* 读完复位全部响应(FA AA 00) */
|
||||
int got = 0;
|
||||
for (int t = 0; t < 100000; t++) { if (dsb_inb(0x64) & 1) { got = 1; break; } }
|
||||
if (!got) break;
|
||||
dsb_inb(0x60);
|
||||
}
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x64, 0xD4);
|
||||
for (int t = 0; t < 100000; t++) { if (!(dsb_inb(0x64) & 2)) break; }
|
||||
dsb_outb(0x60, 0xF4);
|
||||
for (int i = 0; i < 4; i++) { /* 读完使能 ACK */
|
||||
int got = 0;
|
||||
for (int t = 0; t < 100000; t++) { if (dsb_inb(0x64) & 1) { got = 1; break; } }
|
||||
if (!got) break;
|
||||
dsb_inb(0x60);
|
||||
}
|
||||
dsb_ps2_drain();
|
||||
}
|
||||
|
||||
/* 轮询鼠标:3 字节包解码,更新光标坐标与按键状态。
|
||||
* 返回 1=收到完整包,0=无数据/未完成。
|
||||
* 必须先查 AUX 位(bit5)再读数据端口,避免吞掉键盘扫描码
|
||||
* (BUG-20260801-003)。 */
|
||||
static inline int dsb_mouse_poll(dsb_mouse *m, dsb_cursor *c, i64 fb_w, i64 fb_h) {
|
||||
u8 st = dsb_inb(0x64);
|
||||
if (!(st & 1)) return 0;
|
||||
if (!(st & 0x20)) return 0; /* 非 AUX 数据,留给键盘路径 */
|
||||
u8 data = dsb_inb(0x60);
|
||||
m->buf[m->idx++] = data;
|
||||
if (m->idx < 3) return 0;
|
||||
m->idx = 0;
|
||||
if (!(m->buf[0] & 0x08)) return 0; /* 同步位验证 */
|
||||
int dx = (int)(signed char)m->buf[1];
|
||||
int dy = (int)(signed char)m->buf[2];
|
||||
dy = -dy; /* PS/2 Y 向上为正,屏幕 Y 向下为正 */
|
||||
c->mx += dx;
|
||||
c->my += dy;
|
||||
if (c->mx < 0) c->mx = 0;
|
||||
if (c->my < 0) c->my = 0;
|
||||
if (c->mx >= (int)fb_w - DSB_CURSOR_SIZE) c->mx = (int)fb_w - DSB_CURSOR_SIZE;
|
||||
if (c->my >= (int)fb_h - DSB_CURSOR_SIZE) c->my = (int)fb_h - DSB_CURSOR_SIZE;
|
||||
c->btn = (m->buf[0] & 0x01) ? 1 : 0;
|
||||
m->has_pkt = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* DESHAB_INPUT_H */
|
||||
@@ -0,0 +1,176 @@
|
||||
/* deshab/kernel_api.h - dkm_kernel_api ABI 单一定义来源
|
||||
*
|
||||
* 整合 CODE/UTSM/include/utsm/dkm.h 与 CODE/DKM/dkm_shared.h 的 ABI 结构体,
|
||||
* 消除驱动本地重复定义 dkm_kernel_api 的隐患(CONTRIBUTING.md 硬约束)。
|
||||
*
|
||||
* 约定:
|
||||
* - 字段顺序/类型不可调整,UTSM 端按固定偏移填充。
|
||||
* - 新增字段只允许追加在结构末尾(尾部追加保持 ABI 兼容)。
|
||||
* - 结构体同时提供 typedef 名与 struct 标签,兼容两种引用方式。
|
||||
*
|
||||
* 偏移常量(供不便解引用 kernel_api 指针的场景使用,消除硬编码):
|
||||
* DSB_KAPI_OFF_NET 0x48 net API 指针
|
||||
* DSB_KAPI_OFF_BLOCK 0xA8 block API 指针
|
||||
*/
|
||||
#ifndef DESHAB_KERNEL_API_H
|
||||
#define DESHAB_KERNEL_API_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define DKM_KERNEL_API_VERSION 1u
|
||||
|
||||
/* ---- 网卡链路/能力标志 ---- */
|
||||
#define DKM_NET_F_LINK_UP (1u << 0)
|
||||
#define DKM_NET_F_TX_READY (1u << 1)
|
||||
#define DKM_NET_F_RX_READY (1u << 2)
|
||||
#define DKM_NET_F_WIRELESS (1u << 3)
|
||||
|
||||
/* ---- WiFi 扫描加密类型 ---- */
|
||||
#define DKM_NET_SEC_OPEN 0u
|
||||
#define DKM_NET_SEC_WEP 1u
|
||||
#define DKM_NET_SEC_WPA 2u
|
||||
#define DKM_NET_SEC_WPA2 3u
|
||||
#define DKM_NET_SEC_WPA3 4u
|
||||
|
||||
/* ---- log API ---- */
|
||||
typedef struct dkm_log_api {
|
||||
void (*info)(const char *msg);
|
||||
void (*warn)(const char *msg);
|
||||
void (*error)(const char *msg);
|
||||
void (*panic)(const char *msg);
|
||||
} dkm_log_api;
|
||||
|
||||
/* ---- DMA API ---- */
|
||||
typedef struct dkm_dma_buffer {
|
||||
void *virt;
|
||||
u64 phys;
|
||||
u64 size;
|
||||
} dkm_dma_buffer;
|
||||
|
||||
typedef struct dkm_dma_api {
|
||||
int (*alloc_pages)(u64 page_count, u64 alignment, u64 max_phys, dkm_dma_buffer *out);
|
||||
} dkm_dma_api;
|
||||
|
||||
/* ---- net API ---- */
|
||||
typedef struct dkm_net_scan_result {
|
||||
char ssid[33];
|
||||
u8 bssid[6];
|
||||
u8 channel;
|
||||
i8 rssi;
|
||||
u8 security;
|
||||
} dkm_net_scan_result;
|
||||
|
||||
typedef struct dkm_net_device_desc {
|
||||
const char *name;
|
||||
u8 mac[6];
|
||||
u32 flags;
|
||||
void *ctx;
|
||||
int (*tx)(void *ctx, const void *packet, u32 length);
|
||||
int (*rx_poll)(void *ctx, void *buffer, u32 capacity, u32 *out_length);
|
||||
/* 无线扩展回调(有线驱动置 NULL,向后兼容)。 */
|
||||
int (*scan_start)(void *ctx);
|
||||
int (*scan_count)(void *ctx);
|
||||
int (*scan_result)(void *ctx, u32 n, dkm_net_scan_result *out);
|
||||
int (*is_wireless)(void *ctx);
|
||||
} dkm_net_device_desc;
|
||||
|
||||
typedef struct dkm_net_stats {
|
||||
u64 tx_ok;
|
||||
u64 tx_err;
|
||||
u64 rx_ok;
|
||||
u64 rx_err;
|
||||
u64 rx_overflow;
|
||||
u64 link_changes;
|
||||
} dkm_net_stats;
|
||||
|
||||
typedef struct dkm_net_api {
|
||||
int (*register_device)(const dkm_net_device_desc *desc);
|
||||
u32 (*device_count)(void);
|
||||
int (*device_info)(u32 index, void *out);
|
||||
int (*tx)(u32 index, const void *packet, u32 length);
|
||||
int (*rx_poll)(u32 index, void *buffer, u32 capacity, u32 *out_length);
|
||||
int (*scan_start)(u32 index);
|
||||
int (*scan_count)(u32 index);
|
||||
int (*scan_result)(u32 index, u32 n, dkm_net_scan_result *out);
|
||||
int (*is_wireless)(u32 index);
|
||||
int (*device_stats)(u32 index, dkm_net_stats *out);
|
||||
} dkm_net_api;
|
||||
|
||||
/* ---- block API ---- */
|
||||
typedef int (*dkm_block_read_fn)(void *ctx, u64 lba, u32 count, void *buffer);
|
||||
typedef int (*dkm_block_write_fn)(void *ctx, u64 lba, u32 count, const void *buffer);
|
||||
|
||||
typedef struct dkm_block_device_desc {
|
||||
const char *name;
|
||||
u64 sector_size;
|
||||
u64 sector_count;
|
||||
void *ctx;
|
||||
int (*read)(void *ctx, u64 lba, u32 count, void *buffer);
|
||||
int (*write)(void *ctx, u64 lba, u32 count, const void *buffer);
|
||||
} dkm_block_device_desc;
|
||||
|
||||
typedef struct dkm_block_api {
|
||||
int (*register_device)(const dkm_block_device_desc *desc);
|
||||
u32 (*device_count)(void);
|
||||
int (*read)(u32 index, u64 lba, u32 count, void *buffer);
|
||||
int (*write)(u32 index, u64 lba, u32 count, const void *buffer);
|
||||
u64 (*sector_size)(u32 index);
|
||||
const char *(*device_name)(u32 index);
|
||||
int (*set_write_fn)(u32 index, dkm_block_write_fn fn);
|
||||
} dkm_block_api;
|
||||
|
||||
/* ---- MMIO / 页表 API ----
|
||||
* 与 UTSM/include/utsm/paging.h 布局一致。 */
|
||||
typedef struct dkm_mmio_api {
|
||||
int (*is_mapped)(u64 vaddr); /* 返回 0=未映射;1=4K PTE;2=2M PDE;3=1G PDPTE */
|
||||
int (*map_mmio)(u64 phys, u64 size); /* 映射到 hhdm_offset + phys,返回 0 成功 */
|
||||
u64 (*cr3)(void); /* 当前 CR3 物理地址(诊断用) */
|
||||
} dkm_mmio_api;
|
||||
|
||||
/* ---- kernel_api 主结构 ----
|
||||
* 字段顺序/类型不可调整 - UTSM 端按固定偏移填充。
|
||||
* DSK 以 api+0xA8 硬偏移读取 block,net_stack 以 api+0x48 读取 net。 */
|
||||
typedef struct dkm_kernel_api {
|
||||
u32 version;
|
||||
u32 size;
|
||||
u64 feature_bits;
|
||||
|
||||
const dkm_log_api *log;
|
||||
const void *mem;
|
||||
const void *utsm;
|
||||
const void *irq;
|
||||
const void *pci;
|
||||
const dkm_dma_api *dma;
|
||||
const void *vfs;
|
||||
const dkm_net_api *net;
|
||||
const void *timer;
|
||||
const void *drr;
|
||||
|
||||
const void *rsdp_address; /* ACPI RSDP physical address */
|
||||
const void *fb_address; /* framebuffer base */
|
||||
u64 fb_width;
|
||||
u64 fb_height;
|
||||
u64 fb_pitch;
|
||||
u16 fb_bpp;
|
||||
const void *boot_modules_response; /* limine_module_response* */
|
||||
int (*irq_register)(u8 irq, void *handler); /* kernel irq_register */
|
||||
u64 hhdm_offset; /* physical -> virtual */
|
||||
const dkm_block_api *block; /* block provider registry */
|
||||
const dkm_mmio_api *mmio; /* 页表/高位 MMIO 映射服务 */
|
||||
/* -- 新增字段只允许追加在末尾 -- */
|
||||
void *(*mm_map_mmio)(u64 phys, u64 size); /* MMIO 独立窗口映射(PCD|PWT),失败返回 0 */
|
||||
void (*mm_unmap_mmio)(void *virt, u64 size); /* 解除 MMIO 独立窗口映射 */
|
||||
/* B7 阶段2: LAPIC EOI 钩子注册(apic.drv LAPIC 接管时调用)。 */
|
||||
void (*register_apic_eoi)(void (*eoi_fn)(void));
|
||||
/* B7 阶段3: 动态 IDT 向量分配器(MSI/MSI-X 用),池 0x40-0xDF。 */
|
||||
int (*irq_vector_alloc)(void);
|
||||
void (*irq_vector_free)(int vector);
|
||||
} dkm_kernel_api;
|
||||
|
||||
/* ---- kernel_api 字段偏移常量(消除散落的硬编码偏移) ----
|
||||
* 供不便通过结构体解引用的场景(如早期 boot context 解析)使用。
|
||||
* 常规代码应直接通过 api->net / api->block 访问。 */
|
||||
#define DSB_KAPI_OFF_NET 0x48u /* const dkm_net_api *net */
|
||||
#define DSB_KAPI_OFF_BLOCK 0xA8u /* const dkm_block_api *block */
|
||||
|
||||
#endif /* DESHAB_KERNEL_API_H */
|
||||
@@ -0,0 +1,713 @@
|
||||
/* deshab/net.h - Deshab SDK 用户态共享网络协议栈(static inline,无库)
|
||||
*
|
||||
* Deshab SDK 的一部分。基础类型与端口 I/O / TSC / 内存操作由 SDK 头提供:
|
||||
* types.h -> u8/u16/u32/u64/i64/NULL
|
||||
* portio.h -> dsb_outb/dsb_inb
|
||||
* tsc.h -> dsb_rdtsc/dsb_tsc_per_ms/dsb_tsc_calibrate/dsb_deadline_reached
|
||||
* string.h -> dsb_memcpy/dsb_memset/dsb_strlen
|
||||
* 通过 dkm_kernel_api + 0x48 的 net API 收发原始以太帧(轮询模型,无 IRQ)。
|
||||
*
|
||||
* 能力:eth II / ARP(含应答)/ IPv4 / ICMP echo / UDP DNS / TCP 最小 client。
|
||||
* 默认拓扑:QEMU slirp - guest 10.0.2.15 / gw 10.0.2.2 / dns 10.0.2.3。
|
||||
*
|
||||
* 用法:
|
||||
* ns_init(ctx->dkm_kernel_api) - 初始化,选第一个有线 tx/rx ready 设备
|
||||
* ns_ping(ip, seq, timeout, &rtt, &ttl) - 单次 ICMP echo
|
||||
* ns_dns_resolve(host, &ip) - A 记录查询(数字 IP 直接返回)
|
||||
* ns_tcp_connect(ip, port, timeout)
|
||||
* ns_tcp_send(data, len)
|
||||
* ns_tcp_recv(buf, cap, idle_timeout) - 收至 FIN/超时,返回累计长度
|
||||
* ns_tcp_close()
|
||||
*
|
||||
* IP 以 u32 网络序数值表示(NS_IP4(10,0,2,15))。
|
||||
*/
|
||||
|
||||
#ifndef DESHAB_NET_H
|
||||
#define DESHAB_NET_H
|
||||
|
||||
#include "types.h"
|
||||
#include "portio.h"
|
||||
#include "tsc.h"
|
||||
#include "string.h"
|
||||
|
||||
/* ---- 默认网络配置(QEMU user-mode slirp) ---- */
|
||||
#define NS_IP4(a,b,c,d) ((u32)((((u32)(a))<<24)|(((u32)(b))<<16)|(((u32)(c))<<8)|((u32)(d))))
|
||||
#define NS_GUEST_IP NS_IP4(10,0,2,15)
|
||||
#define NS_GATEWAY_IP NS_IP4(10,0,2,2)
|
||||
#define NS_DNS_IP NS_IP4(10,0,2,3)
|
||||
#define NS_NETMASK NS_IP4(255,255,255,0)
|
||||
|
||||
/* 当前生效 IP 配置:默认 slirp 硬编码;netman 租约经 ns_apply_lease 覆盖。
|
||||
* 租约约定:dsk_boot_context.reserved[7] 存放指向 net_lease_info(dsk.h)的
|
||||
* 指针,结构由 DSK 常驻持有,netman 在 DHCP/静态配置成功后写入。 */
|
||||
static u32 ns_guest_ip = NS_GUEST_IP;
|
||||
static u32 ns_gateway_ip = NS_GATEWAY_IP;
|
||||
static u32 ns_dns_ip = NS_DNS_IP;
|
||||
|
||||
static inline void ns_apply_lease(u32 ip, u32 gw, u32 dns) {
|
||||
if (ip) ns_guest_ip = ip;
|
||||
if (gw) ns_gateway_ip = gw;
|
||||
if (dns) ns_dns_ip = dns;
|
||||
}
|
||||
|
||||
/* 从 boot context 读取 netman 租约(存在且 valid 则应用)。
|
||||
* ctx_u64 为 dsk_boot_context 指针值。reserved[8] 位于偏移 160
|
||||
* (第 20 个 u64 起),reserved[7] = 第 27 个 u64,存 net_lease_info 指针:
|
||||
* +0 magic("NETL" 0x4E45544C) +4 flags(bit0 valid) +8 ip +12 gw +16 dns */
|
||||
static inline void ns_apply_lease_from_ctx(u64 ctx_u64) {
|
||||
if (!ctx_u64) return;
|
||||
const u64 *ctx = (const u64 *)ctx_u64;
|
||||
u64 lease_ptr = ctx[27]; /* reserved[7] */
|
||||
if (!lease_ptr) return;
|
||||
const u32 *li = (const u32 *)lease_ptr;
|
||||
if (li[0] != 0x4E45544Cu) return; /* NET_LEASE_MAGIC */
|
||||
if (!(li[1] & 1u)) return; /* NET_LEASE_F_VALID */
|
||||
ns_apply_lease(li[2], li[3], li[4]);
|
||||
}
|
||||
|
||||
#define NS_ETH_ARP 0x0806
|
||||
#define NS_ETH_IPV4 0x0800
|
||||
#define NS_IP_ICMP 1
|
||||
#define NS_IP_TCP 6
|
||||
#define NS_IP_UDP 17
|
||||
|
||||
#define NS_ARP_CACHE_SIZE 8
|
||||
#define NS_TCP_RX_CAP 65536
|
||||
|
||||
/* dkm_net_device_desc.flags 位(与 utsm/net.h 一致) */
|
||||
#define NS_NET_F_TX_READY 0x2
|
||||
#define NS_NET_F_RX_READY 0x4
|
||||
#define NS_NET_F_WIRELESS 0x8
|
||||
|
||||
/* ---- 字节序/校验和 ---- */
|
||||
static inline void ns_w16(u8 *p, u16 v) { p[0] = (u8)(v >> 8); p[1] = (u8)v; }
|
||||
static inline void ns_w32(u8 *p, u32 v) { p[0] = (u8)(v >> 24); p[1] = (u8)(v >> 16); p[2] = (u8)(v >> 8); p[3] = (u8)v; }
|
||||
static inline u16 ns_r16(const u8 *p) { return (u16)(((u16)p[0] << 8) | p[1]); }
|
||||
static inline u32 ns_r32(const u8 *p) { return ((u32)p[0] << 24) | ((u32)p[1] << 16) | ((u32)p[2] << 8) | p[3]; }
|
||||
|
||||
static inline u16 ns_checksum(const void *data, u32 len) {
|
||||
const u8 *p = (const u8 *)data;
|
||||
u32 sum = 0;
|
||||
while (len > 1) { sum += ((u16)p[0] << 8) | p[1]; p += 2; len -= 2; }
|
||||
if (len) sum += ((u16)p[0] << 8);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
return (u16)~sum;
|
||||
}
|
||||
|
||||
/* ---- 十进制/IP 字符串 ---- */
|
||||
static inline void ns_u32_dec(char *buf, u32 v) {
|
||||
char tmp[12]; int n = 0;
|
||||
if (!v) { buf[0] = '0'; buf[1] = 0; return; }
|
||||
while (v && n < 11) { tmp[n++] = (char)('0' + v % 10); v /= 10; }
|
||||
for (int i = 0; i < n; i++) buf[i] = tmp[n - 1 - i];
|
||||
buf[n] = 0;
|
||||
}
|
||||
|
||||
static inline void ns_fmt_ip(u32 ip, char *out) {
|
||||
int p = 0;
|
||||
for (int i = 3; i >= 0; i--) {
|
||||
char d[4]; ns_u32_dec(d, (ip >> (i * 8)) & 0xFF);
|
||||
for (int j = 0; d[j]; j++) out[p++] = d[j];
|
||||
if (i) out[p++] = '.';
|
||||
}
|
||||
out[p] = 0;
|
||||
}
|
||||
|
||||
/* 解析 "a.b.c.d" -> 网络序 u32。返回 0 成功。 */
|
||||
static inline int ns_parse_ip(const char *s, u32 *out) {
|
||||
u32 v = 0;
|
||||
for (int part = 0; part < 4; part++) {
|
||||
u32 n = 0; int digits = 0;
|
||||
while (*s >= '0' && *s <= '9') {
|
||||
n = n * 10 + (u32)(*s - '0');
|
||||
if (n > 255) return -1;
|
||||
s++; digits++;
|
||||
}
|
||||
if (!digits) return -1;
|
||||
v = (v << 8) | n;
|
||||
if (part < 3) {
|
||||
if (*s != '.') return -1;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
if (*s && *s != ' ') return -1;
|
||||
*out = v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- net API 绑定 ---- */
|
||||
typedef int (*ns_net_tx_fn)(u32 index, const void *packet, u32 length);
|
||||
typedef int (*ns_net_rx_poll_fn)(u32 index, void *buffer, u32 capacity, u32 *out_length);
|
||||
typedef u32 (*ns_net_count_fn)(void);
|
||||
typedef int (*ns_net_info_fn)(u32 index, void *out);
|
||||
|
||||
static int ns_ready = 0;
|
||||
static u32 ns_dev_index = 0;
|
||||
static u8 ns_mac[6] = {0};
|
||||
static ns_net_tx_fn ns_net_tx = 0;
|
||||
static ns_net_rx_poll_fn ns_net_rx_poll_fn_ptr = 0;
|
||||
static u16 ns_ip_id = 1;
|
||||
|
||||
/* dkm_net_device_info 布局:name ptr @0, mac[6] @8, flags @16(对齐填充后 size 24) */
|
||||
typedef struct __attribute__((packed)) {
|
||||
u64 name;
|
||||
u8 mac[6];
|
||||
u16 pad;
|
||||
u32 flags;
|
||||
} ns_dev_info;
|
||||
|
||||
static inline int ns_init(u64 dkm_kernel_api) {
|
||||
if (ns_ready) return 0;
|
||||
if (!dkm_kernel_api) return -1;
|
||||
u64 net = *(u64 *)(dkm_kernel_api + 0x48);
|
||||
if (!net) return -2;
|
||||
ns_net_count_fn device_count = (ns_net_count_fn)*(u64 *)(net + 0x08);
|
||||
ns_net_info_fn device_info = (ns_net_info_fn)*(u64 *)(net + 0x10);
|
||||
ns_net_tx = (ns_net_tx_fn)*(u64 *)(net + 0x18);
|
||||
ns_net_rx_poll_fn_ptr = (ns_net_rx_poll_fn)*(u64 *)(net + 0x20);
|
||||
if (!device_count || !device_info || !ns_net_tx || !ns_net_rx_poll_fn_ptr) return -3;
|
||||
u32 n = device_count();
|
||||
if (!n) return -4;
|
||||
|
||||
int picked = -1;
|
||||
ns_dev_info info;
|
||||
for (u32 i = 0; i < n && i < 8; i++) {
|
||||
dsb_memset(&info, 0, sizeof(info));
|
||||
if (device_info(i, &info) != 0) continue;
|
||||
if (info.flags & NS_NET_F_WIRELESS) continue;
|
||||
if ((info.flags & NS_NET_F_TX_READY) && (info.flags & NS_NET_F_RX_READY)) {
|
||||
picked = (int)i;
|
||||
for (int k = 0; k < 6; k++) ns_mac[k] = info.mac[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (picked < 0) {
|
||||
dsb_memset(&info, 0, sizeof(info));
|
||||
if (device_info(0, &info) != 0) return -5;
|
||||
picked = 0;
|
||||
for (int k = 0; k < 6; k++) ns_mac[k] = info.mac[k];
|
||||
}
|
||||
ns_dev_index = (u32)picked;
|
||||
dsb_tsc_calibrate();
|
||||
ns_ready = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 帧缓冲 ---- */
|
||||
static u8 ns_txbuf[2048];
|
||||
static u8 ns_rxframe[2048];
|
||||
|
||||
/* 发送以太帧 */
|
||||
static inline int ns_send_eth(const u8 dst_mac[6], u16 ethertype, const u8 *payload, u32 len) {
|
||||
if (len + 14 > sizeof(ns_txbuf)) return -1;
|
||||
for (int i = 0; i < 6; i++) ns_txbuf[i] = dst_mac[i];
|
||||
for (int i = 0; i < 6; i++) ns_txbuf[6 + i] = ns_mac[i];
|
||||
ns_w16(ns_txbuf + 12, ethertype);
|
||||
dsb_memcpy(ns_txbuf + 14, payload, len);
|
||||
u32 total = len + 14;
|
||||
if (total < 60) { /* 以太网最小帧 */
|
||||
dsb_memset(ns_txbuf + total, 0, 60 - total);
|
||||
total = 60;
|
||||
}
|
||||
return ns_net_tx(ns_dev_index, ns_txbuf, total);
|
||||
}
|
||||
|
||||
/* ---- ARP ---- */
|
||||
typedef struct {
|
||||
u32 ip;
|
||||
u8 mac[6];
|
||||
u8 valid;
|
||||
} ns_arp_entry;
|
||||
|
||||
static ns_arp_entry ns_arp_cache[NS_ARP_CACHE_SIZE];
|
||||
static u8 ns_arp_reply_mac[6];
|
||||
static volatile int ns_arp_reply_valid = 0;
|
||||
|
||||
static inline void ns_arp_cache_put(u32 ip, const u8 mac[6]) {
|
||||
int slot = -1;
|
||||
for (int i = 0; i < NS_ARP_CACHE_SIZE; i++) {
|
||||
if (ns_arp_cache[i].valid && ns_arp_cache[i].ip == ip) { slot = i; break; }
|
||||
}
|
||||
if (slot < 0) {
|
||||
for (int i = 0; i < NS_ARP_CACHE_SIZE; i++) {
|
||||
if (!ns_arp_cache[i].valid) { slot = i; break; }
|
||||
}
|
||||
}
|
||||
if (slot < 0) slot = 0;
|
||||
ns_arp_cache[slot].ip = ip;
|
||||
for (int k = 0; k < 6; k++) ns_arp_cache[slot].mac[k] = mac[k];
|
||||
ns_arp_cache[slot].valid = 1;
|
||||
}
|
||||
|
||||
static inline int ns_arp_cache_get(u32 ip, u8 mac[6]) {
|
||||
for (int i = 0; i < NS_ARP_CACHE_SIZE; i++) {
|
||||
if (ns_arp_cache[i].valid && ns_arp_cache[i].ip == ip) {
|
||||
for (int k = 0; k < 6; k++) mac[k] = ns_arp_cache[i].mac[k];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 构造 ARP 包到 out(42 字节),oper 1=request 2=reply */
|
||||
static inline void ns_arp_build(u8 *out, u16 oper,
|
||||
const u8 sha[6], u32 spa,
|
||||
const u8 tha[6], u32 tpa) {
|
||||
ns_w16(out + 0, 1); /* htype eth */
|
||||
ns_w16(out + 2, NS_ETH_IPV4); /* ptype */
|
||||
out[4] = 6; out[5] = 4;
|
||||
ns_w16(out + 6, oper);
|
||||
for (int i = 0; i < 6; i++) out[8 + i] = sha[i];
|
||||
ns_w32(out + 14, spa);
|
||||
for (int i = 0; i < 6; i++) out[18 + i] = tha[i];
|
||||
ns_w32(out + 24, tpa);
|
||||
}
|
||||
|
||||
/* 处理收到的 ARP 包:应答对我们的请求,记录 reply。 */
|
||||
static inline void ns_arp_handle(const u8 *pkt, u32 len) {
|
||||
if (len < 28) return;
|
||||
u16 oper = ns_r16(pkt + 6);
|
||||
u32 spa = ns_r32(pkt + 14);
|
||||
u32 tpa = ns_r32(pkt + 24);
|
||||
if (oper == 1 && tpa == ns_guest_ip) {
|
||||
/* 请求我们的 IP -> 回复 */
|
||||
u8 reply[28];
|
||||
ns_arp_build(reply, 2, ns_mac, ns_guest_ip, pkt + 8, spa);
|
||||
ns_send_eth(pkt + 8, NS_ETH_ARP, reply, 28);
|
||||
} else if (oper == 2) {
|
||||
ns_arp_cache_put(spa, pkt + 8);
|
||||
for (int k = 0; k < 6; k++) ns_arp_reply_mac[k] = pkt[8 + k];
|
||||
ns_arp_reply_valid = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 轮询一帧;ARP 内部处理(不返回给调用者)。
|
||||
* 返回 0=收到非 ARP 帧(*out_et 为 ethertype),1=超时。 */
|
||||
static inline int ns_wait_frame(u8 *buf, u32 cap, u32 *out_len, u16 *out_et, u32 timeout_ms) {
|
||||
u64 deadline = dsb_rdtsc() + dsb_tsc_per_ms * timeout_ms;
|
||||
for (;;) {
|
||||
u32 len = 0;
|
||||
int rc = ns_net_rx_poll_fn_ptr(ns_dev_index, buf, cap, &len);
|
||||
if (rc == 0 && len >= 14) {
|
||||
u16 et = ns_r16(buf + 12);
|
||||
if (et == NS_ETH_ARP) {
|
||||
ns_arp_handle(buf + 14, len - 14);
|
||||
/* 继续等待 */
|
||||
} else {
|
||||
*out_len = len;
|
||||
*out_et = et;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (dsb_deadline_reached(deadline)) return 1;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
}
|
||||
|
||||
/* ARP 解析 ip -> mac,3 次重传。返回 0 成功。 */
|
||||
static inline int ns_arp_resolve(u32 ip, u8 out_mac[6]) {
|
||||
if (ns_arp_cache_get(ip, out_mac) == 0) return 0;
|
||||
static const u8 bcast[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
||||
static const u8 zero_mac[6] = {0,0,0,0,0,0};
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
u8 req[28];
|
||||
ns_arp_build(req, 1, ns_mac, ns_guest_ip, zero_mac, ip);
|
||||
ns_arp_reply_valid = 0;
|
||||
if (ns_send_eth(bcast, NS_ETH_ARP, req, 28) != 0) continue;
|
||||
u64 deadline = dsb_rdtsc() + dsb_tsc_per_ms * 1000;
|
||||
while (!dsb_deadline_reached(deadline)) {
|
||||
u32 len = 0;
|
||||
int rc = ns_net_rx_poll_fn_ptr(ns_dev_index, ns_rxframe, sizeof(ns_rxframe), &len);
|
||||
if (rc == 0 && len >= 14 && ns_r16(ns_rxframe + 12) == NS_ETH_ARP) {
|
||||
ns_arp_handle(ns_rxframe + 14, len - 14);
|
||||
if (ns_arp_reply_valid) {
|
||||
u32 spa = ns_r32(ns_rxframe + 14 + 14);
|
||||
if (spa == ip) {
|
||||
for (int k = 0; k < 6; k++) out_mac[k] = ns_arp_reply_mac[k];
|
||||
return 0;
|
||||
}
|
||||
ns_arp_reply_valid = 0;
|
||||
}
|
||||
}
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 目标 IP 的下一跳 MAC(同子网直连,否则走网关)。返回 0 成功。 */
|
||||
static inline int ns_route_mac(u32 dst_ip, u8 out_mac[6]) {
|
||||
u32 next = dst_ip;
|
||||
if ((dst_ip & NS_NETMASK) != (ns_guest_ip & NS_NETMASK)) next = ns_gateway_ip;
|
||||
return ns_arp_resolve(next, out_mac);
|
||||
}
|
||||
|
||||
/* ---- IPv4 发送 ---- */
|
||||
static inline void ns_ipv4_build(u8 *out, u8 proto, u32 src, u32 dst, u32 payload_len) {
|
||||
out[0] = 0x45; /* ver=4 ihl=5 */
|
||||
out[1] = 0; /* tos */
|
||||
ns_w16(out + 2, (u16)(20 + payload_len));
|
||||
ns_w16(out + 4, ns_ip_id++);
|
||||
ns_w16(out + 6, 0x4000); /* DF */
|
||||
out[8] = 64; /* ttl */
|
||||
out[9] = proto;
|
||||
ns_w16(out + 10, 0);
|
||||
ns_w32(out + 12, src);
|
||||
ns_w32(out + 16, dst);
|
||||
ns_w16(out + 10, ns_checksum(out, 20));
|
||||
}
|
||||
|
||||
/* ---- ICMP echo(ping) ----
|
||||
* 返回 0 成功(*out_rtt_ms / *out_ttl 有效),-1 ARP 失败,-2 超时。 */
|
||||
static inline int ns_ping(u32 dst_ip, u16 seq, u32 timeout_ms, u32 *out_rtt_ms, u8 *out_ttl) {
|
||||
u8 dst_mac[6];
|
||||
if (ns_route_mac(dst_ip, dst_mac) != 0) return -1;
|
||||
|
||||
u8 pkt[20 + 8 + 32];
|
||||
ns_ipv4_build(pkt, NS_IP_ICMP, ns_guest_ip, dst_ip, 8 + 32);
|
||||
u8 *icmp = pkt + 20;
|
||||
icmp[0] = 8; /* echo request */
|
||||
icmp[1] = 0;
|
||||
ns_w16(icmp + 2, 0);
|
||||
ns_w16(icmp + 4, 0xD5AB); /* identifier */
|
||||
ns_w16(icmp + 6, seq);
|
||||
for (int i = 0; i < 32; i++) icmp[8 + i] = (u8)(0x61 + (i % 26));
|
||||
ns_w16(icmp + 2, ns_checksum(icmp, 8 + 32));
|
||||
|
||||
if (ns_send_eth(dst_mac, NS_ETH_IPV4, pkt, sizeof(pkt)) != 0) return -3;
|
||||
u64 t0 = dsb_rdtsc();
|
||||
u64 deadline = t0 + dsb_tsc_per_ms * timeout_ms;
|
||||
while (!dsb_deadline_reached(deadline)) {
|
||||
u32 len = 0;
|
||||
int rc = ns_net_rx_poll_fn_ptr(ns_dev_index, ns_rxframe, sizeof(ns_rxframe), &len);
|
||||
if (rc == 0 && len >= 14) {
|
||||
u16 et = ns_r16(ns_rxframe + 12);
|
||||
if (et == NS_ETH_ARP) {
|
||||
ns_arp_handle(ns_rxframe + 14, len - 14);
|
||||
} else if (et == NS_ETH_IPV4 && len >= 14 + 20 + 8) {
|
||||
const u8 *ip = ns_rxframe + 14;
|
||||
u8 ihl = (u8)((ip[0] & 0x0F) * 4);
|
||||
if (ihl < 20 || len < 14 + ihl + 8) continue;
|
||||
if (ip[9] != NS_IP_ICMP) continue;
|
||||
const u8 *ic = ip + ihl;
|
||||
if (ic[0] == 0 && ns_r16(ic + 4) == 0xD5AB && ns_r16(ic + 6) == seq) {
|
||||
u64 dt = dsb_rdtsc() - t0;
|
||||
if (out_rtt_ms) {
|
||||
u32 ms = (u32)(dt / dsb_tsc_per_ms);
|
||||
*out_rtt_ms = ms;
|
||||
}
|
||||
if (out_ttl) *out_ttl = ip[8];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* ---- UDP DNS A 查询 ----
|
||||
* host 为数字 IP 时直接解析返回。返回 0 成功。 */
|
||||
static inline int ns_dns_resolve(const char *host, u32 *out_ip) {
|
||||
if (ns_parse_ip(host, out_ip) == 0) return 0;
|
||||
|
||||
u8 dst_mac[6];
|
||||
if (ns_route_mac(ns_dns_ip, dst_mac) != 0) return -1;
|
||||
|
||||
/* 构造查询:header + qname + qtype/qclass */
|
||||
u8 q[512];
|
||||
u16 txid = (u16)(dsb_rdtsc() & 0xFFFF);
|
||||
if (!txid) txid = 0x1234;
|
||||
ns_w16(q + 0, txid);
|
||||
ns_w16(q + 2, 0x0100); /* RD */
|
||||
ns_w16(q + 4, 1); /* qdcount */
|
||||
ns_w16(q + 6, 0); ns_w16(q + 8, 0); ns_w16(q + 10, 0);
|
||||
u32 p = 12;
|
||||
u32 i = 0;
|
||||
while (host[i]) {
|
||||
u32 start = p++;
|
||||
u8 n = 0;
|
||||
while (host[i] && host[i] != '.') {
|
||||
if (n >= 63 || p >= 500) return -2;
|
||||
q[p++] = (u8)host[i++]; n++;
|
||||
}
|
||||
q[start] = n;
|
||||
if (host[i] == '.') i++;
|
||||
}
|
||||
q[p++] = 0;
|
||||
ns_w16(q + p, 1); p += 2; /* A */
|
||||
ns_w16(q + p, 1); p += 2; /* IN */
|
||||
u32 qlen = p;
|
||||
|
||||
u8 pkt[20 + 8 + 512];
|
||||
ns_ipv4_build(pkt, NS_IP_UDP, ns_guest_ip, ns_dns_ip, 8 + qlen);
|
||||
u8 *udp = pkt + 20;
|
||||
ns_w16(udp + 0, 49153);
|
||||
ns_w16(udp + 2, 53);
|
||||
ns_w16(udp + 4, (u16)(8 + qlen));
|
||||
ns_w16(udp + 6, 0); /* 不校验 UDP checksum */
|
||||
dsb_memcpy(udp + 8, q, qlen);
|
||||
|
||||
for (int attempt = 0; attempt < 2; attempt++) {
|
||||
if (ns_send_eth(dst_mac, NS_ETH_IPV4, pkt, 20 + 8 + qlen) != 0) continue;
|
||||
u64 deadline = dsb_rdtsc() + dsb_tsc_per_ms * 3000;
|
||||
while (!dsb_deadline_reached(deadline)) {
|
||||
u32 len = 0;
|
||||
int rc = ns_net_rx_poll_fn_ptr(ns_dev_index, ns_rxframe, sizeof(ns_rxframe), &len);
|
||||
if (rc == 0 && len >= 14) {
|
||||
u16 et = ns_r16(ns_rxframe + 12);
|
||||
if (et == NS_ETH_ARP) {
|
||||
ns_arp_handle(ns_rxframe + 14, len - 14);
|
||||
} else if (et == NS_ETH_IPV4 && len >= 14 + 20 + 8 + 12) {
|
||||
const u8 *ip = ns_rxframe + 14;
|
||||
u8 ihl = (u8)((ip[0] & 0x0F) * 4);
|
||||
if (ihl < 20 || ip[9] != NS_IP_UDP) continue;
|
||||
const u8 *ru = ip + ihl;
|
||||
if (ns_r16(ru + 2) != 49153) continue;
|
||||
const u8 *d = ru + 8;
|
||||
if (ns_r16(d + 0) != txid) continue;
|
||||
u16 qd = ns_r16(d + 4);
|
||||
u16 an = ns_r16(d + 6);
|
||||
u32 off = 12;
|
||||
/* 跳过问题段 */
|
||||
for (u16 qq = 0; qq < qd; qq++) {
|
||||
while (off < len) {
|
||||
u8 b = d[off];
|
||||
if (b == 0) { off++; break; }
|
||||
if ((b & 0xC0) == 0xC0) { off += 2; break; }
|
||||
off += 1 + b;
|
||||
}
|
||||
off += 4;
|
||||
}
|
||||
/* 遍历应答 RR */
|
||||
for (u16 rr = 0; rr < an; rr++) {
|
||||
while (off < len) { /* 跳过 name */
|
||||
u8 b = d[off];
|
||||
if (b == 0) { off++; break; }
|
||||
if ((b & 0xC0) == 0xC0) { off += 2; break; }
|
||||
off += 1 + b;
|
||||
}
|
||||
if (off + 10 > len) break;
|
||||
u16 rtype = ns_r16(d + off);
|
||||
u16 rdlen = ns_r16(d + off + 8);
|
||||
off += 10;
|
||||
if (rtype == 1 && rdlen == 4 && off + 4 <= len) {
|
||||
*out_ip = ns_r32(d + off);
|
||||
return 0;
|
||||
}
|
||||
off += rdlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
}
|
||||
return -3;
|
||||
}
|
||||
|
||||
/* ---- TCP 最小 client ---- */
|
||||
#define NS_TCP_CLOSED 0
|
||||
#define NS_TCP_SYN_SENT 1
|
||||
#define NS_TCP_ESTAB 2
|
||||
#define NS_TCP_FIN_WAIT 3
|
||||
|
||||
static struct {
|
||||
u32 state;
|
||||
u32 dst_ip;
|
||||
u16 dst_port;
|
||||
u16 src_port;
|
||||
u32 snd_nxt;
|
||||
u32 rcv_nxt;
|
||||
u8 dst_mac[6];
|
||||
} ns_tc = {0, 0, 0, 0, 0, 0, {0}};
|
||||
|
||||
static u8 ns_tcp_rx[NS_TCP_RX_CAP];
|
||||
static u32 ns_tcp_rx_len = 0;
|
||||
static volatile int ns_tcp_got_fin = 0;
|
||||
static volatile int ns_tcp_got_rst = 0;
|
||||
|
||||
#define NS_TCP_FIN 0x01
|
||||
#define NS_TCP_SYN 0x02
|
||||
#define NS_TCP_RST 0x04
|
||||
#define NS_TCP_PSH 0x08
|
||||
#define NS_TCP_ACK 0x10
|
||||
|
||||
/* 构造并发送 TCP 段(data 可为 0)。 */
|
||||
static inline int ns_tcp_emit(u8 flags, const u8 *data, u32 len, u32 seq) {
|
||||
u8 pkt[20 + 20 + 1460];
|
||||
if (len > 1460) return -1;
|
||||
u32 tcp_len = 20 + len;
|
||||
ns_ipv4_build(pkt, NS_IP_TCP, ns_guest_ip, ns_tc.dst_ip, tcp_len);
|
||||
u8 *t = pkt + 20;
|
||||
ns_w16(t + 0, ns_tc.src_port);
|
||||
ns_w16(t + 2, ns_tc.dst_port);
|
||||
ns_w32(t + 4, seq);
|
||||
ns_w32(t + 8, (flags & NS_TCP_ACK) ? ns_tc.rcv_nxt : 0);
|
||||
t[12] = (5 << 4); /* data offset */
|
||||
t[13] = flags;
|
||||
ns_w16(t + 14, 64240); /* window */
|
||||
ns_w16(t + 16, 0);
|
||||
ns_w16(t + 18, 0);
|
||||
if (len) dsb_memcpy(t + 20, data, len);
|
||||
/* 伪头部校验和 */
|
||||
u8 ph[12];
|
||||
ns_w32(ph + 0, ns_guest_ip);
|
||||
ns_w32(ph + 4, ns_tc.dst_ip);
|
||||
ph[8] = 0; ph[9] = NS_IP_TCP;
|
||||
ns_w16(ph + 10, (u16)tcp_len);
|
||||
u32 sum = 0;
|
||||
for (u32 k = 0; k < 12; k += 2) sum += ((u16)ph[k] << 8) | ph[k + 1];
|
||||
for (u32 k = 0; k + 1 < tcp_len; k += 2) sum += ((u16)t[k] << 8) | t[k + 1];
|
||||
if (tcp_len & 1) sum += ((u16)t[tcp_len - 1] << 8);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
ns_w16(t + 16, (u16)~sum);
|
||||
return ns_send_eth(ns_tc.dst_mac, NS_ETH_IPV4, pkt, 20 + tcp_len);
|
||||
}
|
||||
|
||||
/* 处理收到的 IPv4/TCP 段。返回 1 表示属于本连接并已处理。 */
|
||||
static inline int ns_tcp_process(const u8 *ip, u32 ip_len) {
|
||||
if (ip_len < 20) return 0;
|
||||
u8 ihl = (u8)((ip[0] & 0x0F) * 4);
|
||||
if (ihl < 20 || ip_len < ihl + 20) return 0;
|
||||
if (ip[9] != NS_IP_TCP) return 0;
|
||||
if (ns_r32(ip + 12) != ns_tc.dst_ip) return 0;
|
||||
const u8 *t = ip + ihl;
|
||||
if (ns_r16(t + 0) != ns_tc.dst_port) return 0;
|
||||
if (ns_r16(t + 2) != ns_tc.src_port) return 0;
|
||||
|
||||
u32 seq = ns_r32(t + 4);
|
||||
u32 ack = ns_r32(t + 8);
|
||||
u8 doff = (u8)((t[12] >> 4) * 4);
|
||||
u8 flags = t[13];
|
||||
u16 ip_total = ns_r16(ip + 2);
|
||||
u32 data_len = (u32)ip_total - ihl - doff;
|
||||
const u8 *data = t + doff;
|
||||
|
||||
if (flags & NS_TCP_RST) { ns_tcp_got_rst = 1; ns_tc.state = NS_TCP_CLOSED; return 1; }
|
||||
|
||||
if (ns_tc.state == NS_TCP_SYN_SENT) {
|
||||
if ((flags & (NS_TCP_SYN | NS_TCP_ACK)) == (NS_TCP_SYN | NS_TCP_ACK)) {
|
||||
ns_tc.rcv_nxt = seq + 1;
|
||||
ns_tc.snd_nxt = ack;
|
||||
ns_tc.state = NS_TCP_ESTAB;
|
||||
ns_tcp_emit(NS_TCP_ACK, 0, 0, ns_tc.snd_nxt);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ns_tc.state == NS_TCP_ESTAB || ns_tc.state == NS_TCP_FIN_WAIT) {
|
||||
if (data_len > 0) {
|
||||
if (seq == ns_tc.rcv_nxt) {
|
||||
u32 room = NS_TCP_RX_CAP - ns_tcp_rx_len;
|
||||
u32 take = data_len < room ? data_len : room;
|
||||
dsb_memcpy(ns_tcp_rx + ns_tcp_rx_len, data, take);
|
||||
ns_tcp_rx_len += take;
|
||||
ns_tc.rcv_nxt += data_len;
|
||||
ns_tcp_emit(NS_TCP_ACK, 0, 0, ns_tc.snd_nxt);
|
||||
} else {
|
||||
/* 乱序超前或重复段:重 ACK 当前期望序号 */
|
||||
ns_tcp_emit(NS_TCP_ACK, 0, 0, ns_tc.snd_nxt);
|
||||
}
|
||||
}
|
||||
if (flags & NS_TCP_FIN) {
|
||||
if (seq + data_len == ns_tc.rcv_nxt || data_len == 0) {
|
||||
ns_tc.rcv_nxt = seq + data_len + 1;
|
||||
ns_tcp_emit(NS_TCP_ACK, 0,0, ns_tc.snd_nxt);
|
||||
ns_tcp_got_fin = 1;
|
||||
ns_tc.state = NS_TCP_CLOSED;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 在超时窗口内收帧并喂给 TCP;返回 0=收到本连接段,1=超时。 */
|
||||
static inline int ns_tcp_wait(u32 timeout_ms) {
|
||||
u64 deadline = dsb_rdtsc() + dsb_tsc_per_ms * timeout_ms;
|
||||
while (!dsb_deadline_reached(deadline)) {
|
||||
u32 len = 0;
|
||||
int rc = ns_net_rx_poll_fn_ptr(ns_dev_index, ns_rxframe, sizeof(ns_rxframe), &len);
|
||||
if (rc == 0 && len >= 14) {
|
||||
u16 et = ns_r16(ns_rxframe + 12);
|
||||
if (et == NS_ETH_ARP) {
|
||||
ns_arp_handle(ns_rxframe + 14, len - 14);
|
||||
} else if (et == NS_ETH_IPV4) {
|
||||
if (ns_tcp_process(ns_rxframe + 14, len - 14)) return 0;
|
||||
}
|
||||
}
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 返回 0 成功(state=ESTAB),-1 ARP 失败,-2 超时/RST。 */
|
||||
static inline int ns_tcp_connect(u32 dst_ip, u16 dst_port, u32 timeout_ms) {
|
||||
if (ns_route_mac(dst_ip, ns_tc.dst_mac) != 0) return -1;
|
||||
ns_tc.dst_ip = dst_ip;
|
||||
ns_tc.dst_port = dst_port;
|
||||
ns_tc.src_port = (u16)(49152 + (dsb_rdtsc() & 0x0FFF));
|
||||
u32 iss = (u32)dsb_rdtsc();
|
||||
ns_tcp_rx_len = 0;
|
||||
ns_tcp_got_fin = 0;
|
||||
ns_tcp_got_rst = 0;
|
||||
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
ns_tc.state = NS_TCP_SYN_SENT;
|
||||
if (ns_tcp_emit(NS_TCP_SYN, 0, 0, iss) != 0) continue;
|
||||
u64 deadline = dsb_rdtsc() + dsb_tsc_per_ms * timeout_ms;
|
||||
while (!dsb_deadline_reached(deadline)) {
|
||||
if (ns_tcp_got_rst) { ns_tc.state = NS_TCP_CLOSED; return -2; }
|
||||
if (ns_tc.state == NS_TCP_ESTAB) return 0;
|
||||
ns_tcp_wait(50);
|
||||
}
|
||||
}
|
||||
ns_tc.state = NS_TCP_CLOSED;
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 发送数据并等待 ACK(数据段可能在等待期间到达,已入缓冲)。 */
|
||||
static inline int ns_tcp_send(const u8 *data, u32 len) {
|
||||
if (ns_tc.state != NS_TCP_ESTAB) return -1;
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
u32 expect = ns_tc.snd_nxt + len;
|
||||
if (ns_tcp_emit((u8)(NS_TCP_PSH | NS_TCP_ACK), data, len, ns_tc.snd_nxt) != 0) return -2;
|
||||
u64 deadline = dsb_rdtsc() + dsb_tsc_per_ms * 2000;
|
||||
while (!dsb_deadline_reached(deadline)) {
|
||||
if (ns_tcp_got_rst) { ns_tc.state = NS_TCP_CLOSED; return -3; }
|
||||
/* 检查 snd_una 是否被 ACK 推进:ns_tcp_process 不记录 una,
|
||||
* 简单起见此处认为发出后收到任何本连接段即推进。
|
||||
* 对 HTTP/1.0 请求-响应模式足够(对端 ACK+数据一起回来)。 */
|
||||
if (ns_tcp_wait(100) == 0) {
|
||||
ns_tc.snd_nxt = expect;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -4;
|
||||
}
|
||||
|
||||
/* 收至 FIN 或空闲超时;把累计数据拷到 buf(最多 cap)。返回长度或负值。 */
|
||||
static inline int ns_tcp_recv(u8 *buf, u32 cap, u32 idle_timeout_ms) {
|
||||
u64 idle_deadline = dsb_rdtsc() + dsb_tsc_per_ms * idle_timeout_ms;
|
||||
for (;;) {
|
||||
if (ns_tcp_got_rst) return -1;
|
||||
if (ns_tcp_got_fin) break;
|
||||
if (dsb_deadline_reached(idle_deadline)) break;
|
||||
if (ns_tcp_wait(100) == 0) {
|
||||
idle_deadline = dsb_rdtsc() + dsb_tsc_per_ms * idle_timeout_ms;
|
||||
}
|
||||
}
|
||||
u32 n = ns_tcp_rx_len < cap ? ns_tcp_rx_len : cap;
|
||||
if (buf && n) dsb_memcpy(buf, ns_tcp_rx, n);
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
static inline void ns_tcp_close(void) {
|
||||
if (ns_tc.state == NS_TCP_ESTAB) {
|
||||
ns_tcp_emit((u8)(NS_TCP_FIN | NS_TCP_ACK), 0, 0, ns_tc.snd_nxt);
|
||||
}
|
||||
ns_tc.state = NS_TCP_CLOSED;
|
||||
}
|
||||
|
||||
#endif /* DESHAB_NET_H */
|
||||
@@ -0,0 +1,40 @@
|
||||
/* deshab/portio.h - 端口 I/O 内联函数
|
||||
*
|
||||
* 统一 dkm_shared.h 的 dkm_outb/inb/outl/inl 与 desktop_app.h 的 outb/inb 两套实现。
|
||||
* 宿主已自带端口 I/O 声明时,包含前 #define DSB_NO_PORTIO 跳过本段。
|
||||
*/
|
||||
#ifndef DESHAB_PORTIO_H
|
||||
#define DESHAB_PORTIO_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#ifndef DSB_NO_PORTIO
|
||||
|
||||
static inline void dsb_outb(u16 port, u8 val) {
|
||||
__asm__ volatile("outb %0, %1" :: "a"(val), "Nd"(port));
|
||||
}
|
||||
static inline u8 dsb_inb(u16 port) {
|
||||
u8 v;
|
||||
__asm__ volatile("inb %1, %0" : "=a"(v) : "Nd"(port));
|
||||
return v;
|
||||
}
|
||||
static inline void dsb_outw(u16 port, u16 val) {
|
||||
__asm__ volatile("outw %0, %1" :: "a"(val), "Nd"(port));
|
||||
}
|
||||
static inline u16 dsb_inw(u16 port) {
|
||||
u16 v;
|
||||
__asm__ volatile("inw %1, %0" : "=a"(v) : "Nd"(port));
|
||||
return v;
|
||||
}
|
||||
static inline void dsb_outl(u16 port, u32 val) {
|
||||
__asm__ volatile("outl %0, %1" :: "a"(val), "Nd"(port));
|
||||
}
|
||||
static inline u32 dsb_inl(u16 port) {
|
||||
u32 v;
|
||||
__asm__ volatile("inl %1, %0" : "=a"(v) : "Nd"(port));
|
||||
return v;
|
||||
}
|
||||
|
||||
#endif /* DSB_NO_PORTIO */
|
||||
|
||||
#endif /* DESHAB_PORTIO_H */
|
||||
@@ -0,0 +1,20 @@
|
||||
/* deshab/rtc.h - CMOS RTC 时钟读取
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_rtc_time。读取 CMOS RTC 时/分(BCD -> 二进制)。
|
||||
*/
|
||||
#ifndef DESHAB_RTC_H
|
||||
#define DESHAB_RTC_H
|
||||
|
||||
#include "types.h"
|
||||
#include "portio.h"
|
||||
|
||||
static inline void dsb_rtc_time(u8 *h, u8 *m) {
|
||||
dsb_outb(0x70, 4);
|
||||
*h = dsb_inb(0x71);
|
||||
dsb_outb(0x70, 2);
|
||||
*m = dsb_inb(0x71);
|
||||
*h = (u8)((*h >> 4) * 10 + (*h & 0xF));
|
||||
*m = (u8)((*m >> 4) * 10 + (*m & 0xF));
|
||||
}
|
||||
|
||||
#endif /* DESHAB_RTC_H */
|
||||
@@ -0,0 +1,36 @@
|
||||
/* deshab/serial.h - COM1 串口日志
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_sputc/swrite/slog,统一前缀为 dsb_。
|
||||
* 用于无内核 log API 可用的早期阶段(应用启动、驱动 init 前)。
|
||||
*/
|
||||
#ifndef DESHAB_SERIAL_H
|
||||
#define DESHAB_SERIAL_H
|
||||
|
||||
#include "types.h"
|
||||
#include "portio.h"
|
||||
|
||||
#define DSB_COM1 0x3F8
|
||||
|
||||
static inline void dsb_sputc(char c) {
|
||||
for (unsigned i = 0; i < 100000; i++) {
|
||||
if (dsb_inb(DSB_COM1 + 5) & 0x20) break;
|
||||
}
|
||||
dsb_outb(DSB_COM1, (u8)c);
|
||||
}
|
||||
|
||||
static inline void dsb_swrite(const char *s) {
|
||||
while (*s) {
|
||||
if (*s == '\n') dsb_sputc('\r');
|
||||
dsb_sputc(*s++);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dsb_slog(const char *tag, const char *msg) {
|
||||
dsb_swrite("[");
|
||||
dsb_swrite(tag);
|
||||
dsb_swrite("] ");
|
||||
dsb_swrite(msg);
|
||||
dsb_swrite("\n");
|
||||
}
|
||||
|
||||
#endif /* DESHAB_SERIAL_H */
|
||||
@@ -0,0 +1,30 @@
|
||||
/* deshab/string.h - 基础内存操作(无 libc 依赖)
|
||||
*
|
||||
* 整合 desktop_app.h 的 da_memset/memcpy,统一前缀为 dsb_。
|
||||
* SAS-R0 单地址空间模型下应用/驱动无 libc,需自备基础内存操作。
|
||||
*/
|
||||
#ifndef DESHAB_STRING_H
|
||||
#define DESHAB_STRING_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
static inline void *dsb_memset(void *d, int c, u64 n) {
|
||||
u8 *p = (u8 *)d;
|
||||
while (n--) *p++ = (u8)c;
|
||||
return d;
|
||||
}
|
||||
|
||||
static inline void *dsb_memcpy(void *d, const void *s, u64 n) {
|
||||
u8 *dd = (u8 *)d;
|
||||
const u8 *ss = (const u8 *)s;
|
||||
while (n--) *dd++ = *ss++;
|
||||
return d;
|
||||
}
|
||||
|
||||
static inline u64 dsb_strlen(const char *s) {
|
||||
u64 n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
#endif /* DESHAB_STRING_H */
|
||||
@@ -0,0 +1,89 @@
|
||||
/* deshab/tsc.h - TSC 时钟校准与延时
|
||||
*
|
||||
* 整合 dkm_shared.h 的 dkm_tsc_calibrate/delay_ms/delay_us/serial_wait_tx
|
||||
* 与 net_stack.h 的 ns_tsc_calibrate/rdtsc,统一前缀为 dsb_。
|
||||
*
|
||||
* 用法:
|
||||
* dsb_tsc_calibrate() - 在模块入口开头调用,PIT ch0 ~10ms 校准 TSC
|
||||
* dsb_rdtsc() - 读取 TSC 时间戳
|
||||
* dsb_delay_ms/us() - 基于 TSC 的忙等延时
|
||||
* dsb_deadline_reached()- 截止时间判定
|
||||
* dsb_serial_wait_tx() - 串口发送就绪等待(100us 超时)
|
||||
*
|
||||
* 每个 .elf/.drv 包含本头后获得独立 file-local dsb_tsc_per_ms 副本。
|
||||
*/
|
||||
#ifndef DESHAB_TSC_H
|
||||
#define DESHAB_TSC_H
|
||||
|
||||
#include "types.h"
|
||||
#include "portio.h"
|
||||
|
||||
static u64 dsb_tsc_per_ms = 0;
|
||||
|
||||
static inline u64 dsb_rdtsc(void) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
/* PIT ch0 + TSC 校准 ~10ms 区间,推算每毫秒 TSC 周期数。 */
|
||||
static inline void dsb_tsc_calibrate(void) {
|
||||
if (dsb_tsc_per_ms) return;
|
||||
dsb_outb(0x43, 0x30); /* ch0, lo+hi, mode 0, binary */
|
||||
dsb_outb(0x40, 0x7C); /* 11932 low = ~10ms */
|
||||
dsb_outb(0x40, 0x2E); /* 11932 high */
|
||||
u64 tsc_start = dsb_rdtsc();
|
||||
u16 prev = 0;
|
||||
u64 loops = 0;
|
||||
for (;;) {
|
||||
dsb_outb(0x43, 0x00);
|
||||
u16 cur = (u16)dsb_inb(0x40) | ((u16)dsb_inb(0x40) << 8);
|
||||
if (cur > prev && loops > 10) break;
|
||||
prev = cur;
|
||||
loops++;
|
||||
}
|
||||
dsb_tsc_per_ms = (dsb_rdtsc() - tsc_start) / 10;
|
||||
if (!dsb_tsc_per_ms) dsb_tsc_per_ms = 3000; /* 兜底 ~3GHz */
|
||||
}
|
||||
|
||||
static inline int dsb_deadline_reached(u64 deadline) {
|
||||
return (i64)(dsb_rdtsc() - deadline) >= 0;
|
||||
}
|
||||
|
||||
static inline void dsb_delay_ms(u32 ms) {
|
||||
if (!dsb_tsc_per_ms) {
|
||||
for (volatile u32 i = 0; i < 100000 * ms; i++) __asm__ volatile("pause");
|
||||
return;
|
||||
}
|
||||
u64 target = dsb_tsc_per_ms * ms;
|
||||
u64 start = dsb_rdtsc();
|
||||
while (dsb_rdtsc() - start < target) __asm__ volatile("pause");
|
||||
}
|
||||
|
||||
static inline void dsb_delay_us(u32 us) {
|
||||
if (!dsb_tsc_per_ms) {
|
||||
for (volatile u32 i = 0; i < 100 * us; i++) __asm__ volatile("pause");
|
||||
return;
|
||||
}
|
||||
u64 target = dsb_tsc_per_ms * us / 1000;
|
||||
u64 start = dsb_rdtsc();
|
||||
while (dsb_rdtsc() - start < target) __asm__ volatile("pause");
|
||||
}
|
||||
|
||||
/* 串口发送就绪等待(100us 超时)。com_port = 0x3F8 (COM1)。 */
|
||||
static inline void dsb_serial_wait_tx(u16 com_port) {
|
||||
if (!dsb_tsc_per_ms) {
|
||||
for (volatile u32 i = 0; i < 100000; i++) {
|
||||
if (dsb_inb((u16)(com_port + 5)) & 0x20) break;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return;
|
||||
}
|
||||
u64 deadline = dsb_rdtsc() + dsb_tsc_per_ms / 10;
|
||||
while (dsb_rdtsc() < deadline) {
|
||||
if (dsb_inb((u16)(com_port + 5)) & 0x20) break;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DESHAB_TSC_H */
|
||||
@@ -0,0 +1,23 @@
|
||||
/* deshab/types.h - Deshab SDK 基础类型定义
|
||||
*
|
||||
* 单一来源:所有 SDK 头文件与应用/驱动均通过本头获取基础类型,
|
||||
* 消除 dkm_shared.h / desktop_app.h / net_stack.h 各自重复定义的碎片化。
|
||||
* 布局与 CODE/UTSM/include/utsm/types.h 保持一致。
|
||||
*/
|
||||
#ifndef DESHAB_TYPES_H
|
||||
#define DESHAB_TYPES_H
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
typedef signed char i8;
|
||||
typedef short i16;
|
||||
typedef int i32;
|
||||
typedef long long i64;
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL ((void *)0)
|
||||
#endif
|
||||
|
||||
#endif /* DESHAB_TYPES_H */
|
||||
@@ -0,0 +1,124 @@
|
||||
/* deshab/ui.h - 窗口装饰与色板
|
||||
*
|
||||
* 整合 desktop_app.h 的色板常量与 da_draw_titlebar/draw_statusbar,
|
||||
* 以及 LINUXAPP.CNF 解析契约,统一前缀为 dsb_。
|
||||
*
|
||||
* 独立工具应用全屏运行时的窗口装饰:标题栏 + 关闭按钮 + 状态栏。
|
||||
*/
|
||||
#ifndef DESHAB_UI_H
|
||||
#define DESHAB_UI_H
|
||||
|
||||
#include "types.h"
|
||||
#include "app.h"
|
||||
#include "fb.h"
|
||||
#include "font.h"
|
||||
|
||||
/* ---- 色板(与 deshab_ui.h DP_* 一致) ---- */
|
||||
#define DSB_BG_PRIMARY 0xFF0A1428u /* DP_ABYSS_900 */
|
||||
#define DSB_BG_SECONDARY 0xFF0F1E38u /* DP_ABYSS_800 */
|
||||
#define DSB_BG_TERTIARY 0xFF162848u /* DP_ABYSS_700 */
|
||||
#define DSB_TEXT_PRIMARY 0xFFE8E8F0u /* DP_NEUTRAL_700 */
|
||||
#define DSB_TEXT_DIM 0xFF6A6A88u /* DP_NEUTRAL_300 */
|
||||
#define DSB_ACCENT 0xFF00A8CCu /* DP_SEAL_500 */
|
||||
#define DSB_ACCENT_LIGHT 0xFF44CCF0u /* DP_SEAL_300 */
|
||||
#define DSB_BORDER 0xFF284888u /* DP_ABYSS_500 */
|
||||
#define DSB_BORDER_FOCUS 0xFF44CCF0u /* DP_SEAL_300 */
|
||||
#define DSB_CURSOR_COLOR 0xFF00A8CCu /* DP_SEAL_500 */
|
||||
#define DSB_PROMPT_COLOR 0xFF44CCF0u /* DP_SEAL_300 */
|
||||
#define DSB_SUCCESS 0xFF40C880u /* DP_SUCCESS */
|
||||
#define DSB_ERROR 0xFFFF4466u /* DP_ERROR */
|
||||
#define DSB_WARNING 0xFFF0A030u /* DP_WARNING */
|
||||
|
||||
/* ---- 窗口装饰尺寸 ---- */
|
||||
#define DSB_TITLEBAR_H 28
|
||||
#define DSB_BORDER_W 2
|
||||
#define DSB_STATUSBAR_H 20
|
||||
|
||||
static inline void dsb_draw_titlebar(dsb_app_context *ac, const char *title, i64 w) {
|
||||
dsb_fill_rect(ac, 0, 0, w, DSB_TITLEBAR_H, DSB_BG_SECONDARY);
|
||||
dsb_draw_string(ac, title, 8, (DSB_TITLEBAR_H - 18) / 2, DSB_TEXT_PRIMARY, DSB_BG_SECONDARY, 12);
|
||||
/* 关闭按钮 */
|
||||
dsb_fill_rounded_rect(ac, w - 28, 4, 24, 20, DSB_ERROR, 4);
|
||||
dsb_draw_string(ac, "X", w - 20, 5, 0xFF0A1428u, DSB_ERROR, 12);
|
||||
/* 底部分隔线 */
|
||||
dsb_fill_rect(ac, 0, DSB_TITLEBAR_H, w, 1, DSB_ACCENT);
|
||||
}
|
||||
|
||||
static inline void dsb_draw_statusbar(dsb_app_context *ac, const char *text, i64 w, i64 h) {
|
||||
i64 y = h - DSB_STATUSBAR_H;
|
||||
dsb_fill_rect(ac, 0, y, w, DSB_STATUSBAR_H, DSB_BG_SECONDARY);
|
||||
dsb_draw_string(ac, text, 4, y + 1, DSB_TEXT_DIM, DSB_BG_SECONDARY, 12);
|
||||
/* 顶部分隔线 */
|
||||
dsb_fill_rect(ac, 0, y, w, 1, DSB_BORDER);
|
||||
}
|
||||
|
||||
/* ---- Linux 桌面应用配置契约(LINUXAPP.CNF) ----
|
||||
*
|
||||
* desktop.elf 启动时从 FAT32 根目录读取 LINUXAPP.CNF(8.3 名 LINUXAPPCNF),
|
||||
* 每行注册一个 Linux 应用图标:
|
||||
* 显示名|linux命令
|
||||
* 示例:
|
||||
* NEOFETCH|/usr/bin/neofetch
|
||||
* HTOP|/usr/bin/htop
|
||||
* 规则:空行与 # 开头的行被忽略;显示名 <= 11 字符,命令 <= 63 字符。 */
|
||||
|
||||
#define DSB_LINUXAPP_CNF_83 "LINUXAPPCNF" /* FAT32 8.3 名 */
|
||||
#define DSB_LINUXAPP_MAX 8
|
||||
#define DSB_LINUXAPP_NAME_CAP 12
|
||||
#define DSB_LINUXAPP_CMD_CAP 64
|
||||
|
||||
typedef struct {
|
||||
char name[DSB_LINUXAPP_NAME_CAP];
|
||||
char cmd[DSB_LINUXAPP_CMD_CAP];
|
||||
} dsb_linux_app;
|
||||
|
||||
/* 解析 LINUXAPP.CNF 文本(允许非 NUL 结尾,按 size 截断)。
|
||||
* 返回有效条目数(<= max)。 */
|
||||
static inline int dsb_linuxapp_parse(const char *text, u32 size, dsb_linux_app *out, int max) {
|
||||
int count = 0;
|
||||
u32 pos = 0;
|
||||
while (pos < size && count < max) {
|
||||
char line[96];
|
||||
int ll = 0;
|
||||
while (pos < size && text[pos] != '\n') {
|
||||
char c = text[pos++];
|
||||
if (c == '\r') continue;
|
||||
if (ll < 95) line[ll++] = c;
|
||||
}
|
||||
if (pos < size && text[pos] == '\n') pos++;
|
||||
line[ll] = 0;
|
||||
|
||||
while (ll > 0 && (line[ll - 1] == ' ' || line[ll - 1] == '\t')) line[--ll] = 0;
|
||||
|
||||
int s = 0;
|
||||
while (line[s] == ' ' || line[s] == '\t') s++;
|
||||
if (line[s] == 0 || line[s] == '#') continue;
|
||||
|
||||
int bar = s;
|
||||
while (line[bar] && line[bar] != '|') bar++;
|
||||
if (line[bar] != '|') continue;
|
||||
|
||||
int ne = bar;
|
||||
while (ne > s && (line[ne - 1] == ' ' || line[ne - 1] == '\t')) ne--;
|
||||
if (ne <= s) continue;
|
||||
|
||||
int cs = bar + 1;
|
||||
while (line[cs] == ' ' || line[cs] == '\t') cs++;
|
||||
if (line[cs] == 0) continue;
|
||||
|
||||
int ni = 0;
|
||||
for (int k = s; k < ne && ni < DSB_LINUXAPP_NAME_CAP - 1; k++)
|
||||
out[count].name[ni++] = line[k];
|
||||
out[count].name[ni] = 0;
|
||||
|
||||
int ci = 0;
|
||||
for (int k = cs; line[k] && ci < DSB_LINUXAPP_CMD_CAP - 1; k++)
|
||||
out[count].cmd[ci++] = line[k];
|
||||
out[count].cmd[ci] = 0;
|
||||
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif /* DESHAB_UI_H */
|
||||
@@ -0,0 +1,35 @@
|
||||
# Deshab SDK 应用 MAKEFILE 模板
|
||||
#
|
||||
# 由 scaffold/new-module.ps1 生成时自动替换:
|
||||
# {{MODULE_NAME}} -> 模块名 (如 myapp)
|
||||
# {{SDK_INC}} -> SDK include 目录相对路径 (如 ../../sdk/include)
|
||||
# {{OUT_DIR}} -> 输出目录相对路径 (如 $(ROOT)/SYSTEM/system/deshab64/tools)
|
||||
# {{BITMAPS}} -> ascii_bitmaps.c 相对路径 (如 ../../firstInit/ascii_bitmaps.c)
|
||||
#
|
||||
# 手动使用时替换上述占位符。
|
||||
|
||||
CC := clang
|
||||
LD := ld.lld
|
||||
|
||||
ROOT := ../../..
|
||||
OUT := {{OUT_DIR}}/{{MODULE_NAME}}.elf
|
||||
|
||||
CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I{{SDK_INC}} -O2
|
||||
LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld
|
||||
|
||||
C_SOURCES := main.c
|
||||
OBJECTS := $(C_SOURCES:.c=.o)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT)
|
||||
|
||||
$(OUT): $(OBJECTS) linker.ld
|
||||
@mkdir -p "$(dir $(OUT))"
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }"
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Deshab SDK 应用 linker.ld 模板
|
||||
*
|
||||
* PIE ELF 布局,ENTRY(dsk_entry)。
|
||||
* BSS 分离为独立 NOBITS 段,避免占文件空间。
|
||||
* 与现有应用 (ping/editor/...) linker.ld 一致。
|
||||
*/
|
||||
ENTRY(dsk_entry)
|
||||
|
||||
PHDRS
|
||||
{
|
||||
text PT_LOAD FLAGS(5);
|
||||
rodata PT_LOAD FLAGS(4);
|
||||
data PT_LOAD FLAGS(6);
|
||||
dynamic PT_DYNAMIC FLAGS(4);
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = 0x1000;
|
||||
.text : ALIGN(4K) { *(.text*) } :text
|
||||
.rodata : ALIGN(4K) { *(.rodata*) } :rodata
|
||||
.data : ALIGN(4K) {
|
||||
*(.data*)
|
||||
*(.data.rel.ro*)
|
||||
*(COMMON)
|
||||
} :data
|
||||
.rela.dyn : ALIGN(8) { *(.rela.dyn*) } :data
|
||||
.dynamic : ALIGN(8) { *(.dynamic*) } :data :dynamic
|
||||
.bss : ALIGN(4K) {
|
||||
*(.bss*)
|
||||
} :data
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Deshab SDK 应用入口模板
|
||||
*
|
||||
* 由 scaffold/new-module.ps1 生成时自动替换:
|
||||
* {{MODULE_NAME}} -> 模块名
|
||||
* {{BITMAPS}} -> ascii_bitmaps.c 相对路径
|
||||
*
|
||||
* 展示最小应用框架: 初始化上下文 -> 清屏 -> 标题栏 -> Esc 退出。
|
||||
*/
|
||||
#include "{{BITMAPS}}"
|
||||
#include "deshab/deshab.h"
|
||||
#include "deshab/font.h"
|
||||
|
||||
static dsb_app_context g_ac;
|
||||
|
||||
void dsk_entry(const dsk_boot_context *ctx) {
|
||||
if (ctx->magic != DSK_BOOT_MAGIC) return;
|
||||
dsb_app_init(&g_ac, ctx);
|
||||
|
||||
/* 清屏 */
|
||||
dsb_fill_bg(&g_ac, DSB_BG_PRIMARY);
|
||||
|
||||
/* 标题栏 + 状态栏 */
|
||||
dsb_draw_titlebar(&g_ac, "{{MODULE_NAME}}", (i64)g_ac.fb_w);
|
||||
dsb_draw_statusbar(&g_ac, "Esc to exit", (i64)g_ac.fb_w, (i64)g_ac.fb_h);
|
||||
|
||||
/* 居中欢迎文字 */
|
||||
const char *msg = "Hello from {{MODULE_NAME}}!";
|
||||
i64 msg_w = (i64)dsb_strlen(msg) * 12;
|
||||
i64 x = ((i64)g_ac.fb_w - msg_w) / 2;
|
||||
i64 y = ((i64)g_ac.fb_h) / 2;
|
||||
dsb_draw_string(&g_ac, msg, x, y, DSB_TEXT_PRIMARY, DSB_BG_PRIMARY, 12);
|
||||
|
||||
/* 等待 Esc 键退出 */
|
||||
for (;;) {
|
||||
u8 st = dsb_inb(0x64);
|
||||
if (st & 1) {
|
||||
u8 sc = dsb_inb(0x60);
|
||||
if (sc == 1) break; /* Esc make code */
|
||||
}
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* Deshab SDK 驱动入口模板
|
||||
*
|
||||
* 由 scaffold/new-module.ps1 生成时自动替换:
|
||||
* {{MODULE_NAME}} -> 驱动名 (如 mydriver)
|
||||
* {{CLASS}} -> DKM_CLASS_* 类别 (如 DKM_CLASS_MISC)
|
||||
* {{STAGE}} -> 加载阶段 0-3
|
||||
*
|
||||
* 展示最小 DKM 驱动框架: driver_desc + driver_init + driver_exit。
|
||||
* 构建方式: clang -c main.c -> ld.lld -r -o {{MODULE_NAME}}.drv main.o
|
||||
* (见 scaffold/driver/build.ps1 片段或 build.ps1 的 Build-DkmDriver)
|
||||
*/
|
||||
#include "deshab/driver.h"
|
||||
#include "deshab/serial.h"
|
||||
|
||||
static const dkm_log_api *g_log = 0;
|
||||
|
||||
static const char *const g_depends[] = { 0 };
|
||||
static const char *const g_provides[] = { "{{MODULE_NAME}}" };
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
const dkm_driver_desc driver_desc = {
|
||||
.magic = DKM_DRIVER_MAGIC,
|
||||
.abi_version = DKM_ABI_VERSION,
|
||||
.desc_size = sizeof(dkm_driver_desc),
|
||||
.name = "{{MODULE_NAME}}",
|
||||
.version = "1.0",
|
||||
.vendor = "Deshab",
|
||||
.driver_class = {{CLASS}},
|
||||
.stage = {{STAGE}},
|
||||
.flags = 0,
|
||||
.priority = 100,
|
||||
.depends = g_depends,
|
||||
.depends_count = 0,
|
||||
.provides = g_provides,
|
||||
.provides_count = 1,
|
||||
.min_kernel_abi = DKM_KERNEL_API_VERSION,
|
||||
.feature_bits = 0,
|
||||
};
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int driver_init(const dkm_kernel_api *api, dkm_driver_handle *handle) {
|
||||
(void)handle;
|
||||
if (!api || !api->log) return -1;
|
||||
g_log = api->log;
|
||||
g_log->info("[{{MODULE_NAME}}] init begin");
|
||||
/* TODO: 驱动初始化逻辑 */
|
||||
g_log->info("[{{MODULE_NAME}}] driver ready");
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int driver_exit(dkm_driver_handle *handle) {
|
||||
(void)handle;
|
||||
if (g_log) g_log->info("[{{MODULE_NAME}}] exit");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Deshab SDK 一键创建新应用/驱动模块脚手架。
|
||||
|
||||
.DESCRIPTION
|
||||
从 CODE/sdk/scaffold/ 模板生成新模块,自动替换占位符并计算相对路径。
|
||||
|
||||
.PARAMETER Name
|
||||
模块名(如 myapp、mydriver)。
|
||||
|
||||
.PARAMETER Type
|
||||
模块类型:app(应用 .elf)或 driver(驱动 .drv)。默认 app。
|
||||
|
||||
.PARAMETER OutDir
|
||||
输出目录。默认 app -> CODE/tools/$Name,driver -> CODE/DKM/$Name。
|
||||
|
||||
.PARAMETER Class
|
||||
驱动类别(DKM_CLASS_*)。仅 Type=driver 时使用。默认 DKM_CLASS_MISC。
|
||||
|
||||
.PARAMETER Stage
|
||||
驱动加载阶段 0-3。仅 Type=driver 时使用。默认 3。
|
||||
|
||||
.EXAMPLE
|
||||
.\new-module.ps1 -Name myapp
|
||||
.\new-module.ps1 -Name mydriver -Type driver -Class DKM_CLASS_NET -Stage 3
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Name,
|
||||
[ValidateSet('app','driver')][string]$Type = 'app',
|
||||
[string]$OutDir,
|
||||
[string]$Class = 'DKM_CLASS_MISC',
|
||||
[int]$Stage = 3
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$SdkDir = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
$CodeDir = [System.IO.Path]::GetFullPath((Join-Path $SdkDir '..'))
|
||||
$RootDir = [System.IO.Path]::GetFullPath((Join-Path $CodeDir '..'))
|
||||
|
||||
if (-not $OutDir) {
|
||||
if ($Type -eq 'app') {
|
||||
$OutDir = Join-Path $CodeDir "tools\$Name"
|
||||
} else {
|
||||
$OutDir = Join-Path $CodeDir "DKM\$Name"
|
||||
}
|
||||
}
|
||||
$OutDir = [System.IO.Path]::GetFullPath($OutDir)
|
||||
if (Test-Path $OutDir) { throw "目录已存在: $OutDir" }
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
# 计算从 $from 到 $to 的相对路径(反斜杠)
|
||||
function Get-RelPath($from, $to) {
|
||||
$fromUri = [System.Uri]::new($from.TrimEnd('\') + '\')
|
||||
$toUri = [System.Uri]::new($to.TrimEnd('\') + '\')
|
||||
if (Test-Path $to -PathType Leaf) {
|
||||
$toUri = [System.Uri]::new($to)
|
||||
}
|
||||
return $fromUri.MakeRelativeUri($toUri).ToString().Replace('/', '\')
|
||||
}
|
||||
|
||||
if ($Type -eq 'app') {
|
||||
$SdkInc = Get-RelPath $OutDir (Join-Path $SdkDir 'include')
|
||||
$Bitmaps = Get-RelPath $OutDir (Join-Path $CodeDir 'firstInit\ascii_bitmaps.c')
|
||||
$OutPath = Join-Path $RootDir "SYSTEM\system\deshab64\tools\$Name.elf"
|
||||
$OutDirR = Get-RelPath $OutDir (Split-Path $OutPath -Parent)
|
||||
|
||||
# MAKEFILE
|
||||
$mf = Get-Content (Join-Path $SdkDir 'scaffold\app\MAKEFILE.tmpl') -Raw
|
||||
$mf = $mf.Replace('{{MODULE_NAME}}', $Name).Replace('{{SDK_INC}}', $SdkInc).Replace('{{OUT_DIR}}', $OutDirR)
|
||||
Set-Content (Join-Path $OutDir 'MAKEFILE') $mf -NoNewline
|
||||
|
||||
# linker.ld
|
||||
Copy-Item (Join-Path $SdkDir 'scaffold\app\linker.ld.tmpl') (Join-Path $OutDir 'linker.ld')
|
||||
|
||||
# main.c
|
||||
$mc = Get-Content (Join-Path $SdkDir 'scaffold\app\main.c.tmpl') -Raw
|
||||
$mc = $mc.Replace('{{MODULE_NAME}}', $Name).Replace('{{BITMAPS}}', $Bitmaps)
|
||||
Set-Content (Join-Path $OutDir 'main.c') $mc -NoNewline
|
||||
|
||||
Write-Host "[sdk] 应用 '$Name' 已创建: $OutDir" -ForegroundColor Green
|
||||
Write-Host "[sdk] 构建: cd $OutDir ; make" -ForegroundColor Cyan
|
||||
Write-Host "[sdk] 输出: $OutPath" -ForegroundColor Cyan
|
||||
} else {
|
||||
# main.c
|
||||
$mc = Get-Content (Join-Path $SdkDir 'scaffold\driver\main.c.tmpl') -Raw
|
||||
$mc = $mc.Replace('{{MODULE_NAME}}', $Name).Replace('{{CLASS}}', $Class).Replace('{{STAGE}}', [string]$Stage)
|
||||
Set-Content (Join-Path $OutDir 'main.c') $mc -NoNewline
|
||||
|
||||
Write-Host "[sdk] 驱动 '$Name' 已创建: $OutDir" -ForegroundColor Green
|
||||
Write-Host "[sdk] 构建: clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fno-pic -fno-pie -mcmodel=kernel -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I$(Join-Path $SdkDir 'include') -O2 -c main.c -o $Name.o" -ForegroundColor Cyan
|
||||
Write-Host " ld.lld -r -o $Name.drv $Name.o" -ForegroundColor Cyan
|
||||
Write-Host "[sdk] 注册: 在 SYSTEM/driver/manifest.json 添加条目" -ForegroundColor Yellow
|
||||
Write-Host "[sdk] 预加载: 在 SYSTEM/boot/limine.conf 添加 module_path" -ForegroundColor Yellow
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Deshab SDK 编译验证脚本。
|
||||
.DESCRIPTION
|
||||
验证所有 SDK 头文件可编译(自洽性),示例和工具可构建。
|
||||
用法: .\sdk-test.ps1 [-LlvmBin <path>]
|
||||
#>
|
||||
param(
|
||||
[string]$LlvmBin = 'C:\Program Files\LLVM\bin'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$SdkDir = $PSScriptRoot
|
||||
$RootDir = [System.IO.Path]::GetFullPath((Join-Path $SdkDir '..\..'))
|
||||
|
||||
$Clang = Join-Path $LlvmBin 'clang.exe'
|
||||
if (-not (Test-Path $Clang)) { throw "clang not found: $Clang" }
|
||||
$env:PATH = "$LlvmBin;$env:PATH"
|
||||
|
||||
$global:pass = 0
|
||||
$global:fail = 0
|
||||
|
||||
function Test-Step($name, [scriptblock]$sb) {
|
||||
Write-Host -NoNewline "[sdk-test] $name ... "
|
||||
try {
|
||||
& $sb
|
||||
if ($LASTEXITCODE -ne 0) { throw "exit code $LASTEXITCODE" }
|
||||
Write-Host "PASS" -ForegroundColor Green
|
||||
$global:pass++
|
||||
} catch {
|
||||
Write-Host "FAIL: $_" -ForegroundColor Red
|
||||
$global:fail++
|
||||
}
|
||||
}
|
||||
|
||||
# ---- 1. 头文件自洽性测试 ----
|
||||
$tmpDir = Join-Path $env:TEMP 'deshab-sdk-test'
|
||||
New-Item -ItemType Directory -Force -Path $tmpDir | Out-Null
|
||||
$testC = @'
|
||||
#include "deshab/types.h"
|
||||
/* 提供字体位图 dummy 定义,使 font.h/ui.h 可编译 */
|
||||
static const u8 *g_ascii[95] = {0};
|
||||
static i64 g_ascii_w = 8;
|
||||
static i64 g_ascii_h = 16;
|
||||
#include "deshab/portio.h"
|
||||
#include "deshab/serial.h"
|
||||
#include "deshab/tsc.h"
|
||||
#include "deshab/string.h"
|
||||
#include "deshab/kernel_api.h"
|
||||
#include "deshab/boot_context.h"
|
||||
#include "deshab/driver.h"
|
||||
#include "deshab/block.h"
|
||||
#include "deshab/app.h"
|
||||
#include "deshab/fb.h"
|
||||
#include "deshab/cursor.h"
|
||||
#include "deshab/input.h"
|
||||
#include "deshab/ui.h"
|
||||
#include "deshab/rtc.h"
|
||||
#include "deshab/fat32.h"
|
||||
#include "deshab/net.h"
|
||||
|
||||
/* 触发 static inline 实例化,确保头文件无编译错误 */
|
||||
void sdk_selftest(void) {
|
||||
dsb_app_context ac; dsb_cursor cur; dsb_mouse ms; dsb_block blk;
|
||||
(void)ac; (void)cur; (void)ms; (void)blk;
|
||||
}
|
||||
'@
|
||||
$testFile = Join-Path $tmpDir 'sdk_selftest.c'
|
||||
Set-Content $testFile $testC
|
||||
|
||||
Test-Step "headers self-consistency" {
|
||||
& $Clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin `
|
||||
-fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx `
|
||||
-msoft-float -m64 -Wall -Wextra "-I$(Join-Path $SdkDir 'include')" -O2 `
|
||||
-c $testFile -o (Join-Path $tmpDir 'sdk_selftest.o') 2>&1
|
||||
}
|
||||
|
||||
# ---- 2. hello-app 编译+链接 ----
|
||||
Test-Step "hello-app build" {
|
||||
Push-Location (Join-Path $SdkDir 'examples\hello-app')
|
||||
& $Clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin `
|
||||
-fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx `
|
||||
-msoft-float -m64 -Wall -Wextra -I../../include -O2 -c main.c -o main.o 2>&1
|
||||
& (Join-Path $LlvmBin 'ld.lld.exe') -nostdlib -static -pie -z max-page-size=0x1000 `
|
||||
-T linker.ld -o hello.elf main.o 2>&1
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# ---- 3. hello-driver 编译+链接 ----
|
||||
Test-Step "hello-driver build" {
|
||||
Push-Location (Join-Path $SdkDir 'examples\hello-driver')
|
||||
& $Clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin `
|
||||
-fno-stack-protector -fno-pic -fno-pie -mcmodel=kernel -mno-red-zone `
|
||||
-mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I../../include -O2 `
|
||||
-c main.c -o hello.o 2>&1
|
||||
& (Join-Path $LlvmBin 'ld.lld.exe') -r -o hello.drv hello.o 2>&1
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# ---- 4. sysinfo 编译+链接 ----
|
||||
Test-Step "sysinfo build" {
|
||||
Push-Location (Join-Path $RootDir 'CODE\tools\sysinfo')
|
||||
& $Clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin `
|
||||
-fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx `
|
||||
-msoft-float -m64 -Wall -Wextra -I../../sdk/include -O2 -c main.c -o main.o 2>&1
|
||||
& (Join-Path $LlvmBin 'ld.lld.exe') -nostdlib -static -pie -z max-page-size=0x1000 `
|
||||
-T linker.ld -o sysinfo.elf main.o 2>&1
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# ---- 汇总 ----
|
||||
Write-Host ""
|
||||
Write-Host "[sdk-test] Result: $global:pass passed, $global:fail failed" `
|
||||
-ForegroundColor $(if ($global:fail -eq 0) {'Green'} else {'Red'})
|
||||
exit $global:fail
|
||||
+4
-2
@@ -2170,8 +2170,10 @@ static void cmd_dsl(const char *args) {
|
||||
argv_ptrs[0] = prog; /* argv[0] 用程序名(不带 /bin/ 前缀) */
|
||||
for (int i = 1; i < argc; i++) argv_ptrs[i] = argv_buf[i];
|
||||
|
||||
/* 执行:stdout 缓冲区 */
|
||||
static char stdout_buf[8192];
|
||||
/* 执行:stdout 缓冲区。P3 起 daemon 输出经 payload_pool(~1MB)传输,
|
||||
* 缓冲区从 8KB 提到 128KB,覆盖绝大多数命令输出;超出部分由 UTSM 侧
|
||||
* lxc_pool_copyout 钳制并记串口日志。 */
|
||||
static char stdout_buf[128 * 1024];
|
||||
u64 stdout_len = 0;
|
||||
u64 exit_code = 0;
|
||||
|
||||
|
||||
Binary file not shown.
@@ -114,7 +114,9 @@ static void do_fetch(void) {
|
||||
|
||||
br_set_status("Receiving...");
|
||||
redraw_all_imm();
|
||||
int n = ns_tcp_recv(0, 0, 5000);
|
||||
/* buf=0 不拷贝(直接读全局 ns_tcp_rx),cap 必须给缓冲容量,
|
||||
* 否则 n=min(rx_len,0) 恒为 0,误判为超时。 */
|
||||
int n = ns_tcp_recv(0, NS_TCP_RX_CAP, 5000);
|
||||
ns_tcp_close();
|
||||
|
||||
if (n < 0) { br_set_status("Connection reset"); return; }
|
||||
@@ -277,6 +279,7 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
}
|
||||
|
||||
da_init(&g_ac, ctx);
|
||||
ns_apply_lease_from_ctx((u64)(const void *)ctx); /* netman 租约覆盖默认 slirp IP */
|
||||
da_cursor_init(&g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h);
|
||||
da_mouse_init();
|
||||
|
||||
|
||||
@@ -213,7 +213,9 @@ static void do_http_get(const char *url) {
|
||||
/* 收至 FIN */
|
||||
term_puts_color(" -> 等待响应...\n", DA_TEXT_DIM);
|
||||
redraw_all();
|
||||
int n = ns_tcp_recv(0, 0, 5000);
|
||||
/* buf=0 不拷贝(直接读全局 ns_tcp_rx),cap 必须给缓冲容量,
|
||||
* 否则 n=min(rx_len,0) 恒为 0,误判为超时。 */
|
||||
int n = ns_tcp_recv(0, NS_TCP_RX_CAP, 5000);
|
||||
ns_tcp_close();
|
||||
if (n < 0) {
|
||||
term_puts_color(" [错误] 连接被重置\n", DA_ERROR);
|
||||
@@ -319,6 +321,7 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
}
|
||||
|
||||
da_init(&g_ac, ctx);
|
||||
ns_apply_lease_from_ctx((u64)(const void *)ctx); /* netman 租约覆盖默认 slirp IP */
|
||||
term_init_layout();
|
||||
term_clear();
|
||||
|
||||
|
||||
+451
-16
@@ -21,6 +21,8 @@
|
||||
#ifndef DESHAB_FAT32_IO_H
|
||||
#define DESHAB_FAT32_IO_H
|
||||
|
||||
#include "fat32_lfn.h"
|
||||
|
||||
/* ---- block 设备函数类型(与 shell/DSK 一致:index 参数固定 0) ---- */
|
||||
typedef int (*f32_block_read_fn)(u32 index, u64 lba, u32 count, void *buf);
|
||||
typedef int (*f32_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf);
|
||||
@@ -381,6 +383,217 @@ static inline int f32_write_root_file(const char *name11, const u8 *data, u32 si
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 在指定目录簇中写入文件(存在则替换,不存在则新建)。
|
||||
* 泛化自 f32_write_root_file:目录可跨簇链,目录项回写到其所在簇。
|
||||
* dir_clus : 目标目录首簇(0 = 根目录)
|
||||
* name11 : 11 字符 8.3 名
|
||||
* 返回 0 成功;负值失败(-4 = 目录链无空闲槽,不扩展目录簇链)。
|
||||
* 注意:仅 8.3 短名匹配/创建,不生成 LFN 项。 */
|
||||
static inline int f32_write_file_in_dir(u32 dir_clus, const char *name11, const u8 *data, u32 size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
if (!f32_blk_write) return -10;
|
||||
/* 写入前重新加载 BPB+FAT,保证与盘上一致 */
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -2;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 spc = bpb->spc;
|
||||
u32 fat_lba = bpb->rsvd;
|
||||
u32 fat_sectors = bpb->spf;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 cluster_bytes = spc * 512;
|
||||
if (dir_clus == 0) dir_clus = bpb->root_clus;
|
||||
|
||||
/* 第一遍:沿目录簇链查找现有项与空闲槽,记录其所在簇与索引 */
|
||||
u32 found_clus = 0, existing_clus = 0;
|
||||
int found = 0;
|
||||
u32 found_idx = 0, free_idx = 0;
|
||||
u32 free_slot_clus = 0;
|
||||
int have_free = 0;
|
||||
u32 dclus = dir_clus;
|
||||
while (dclus >= 2 && dclus < 0x0FFFFFF8 && !found) {
|
||||
u32 dlba = data_lba + (dclus - 2) * spc;
|
||||
const u8 *db;
|
||||
if ((u64)dlba * 512 + cluster_bytes <= 256ULL * 512)
|
||||
db = f32_disk + (u64)dlba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(dlba, spc, f32_cluster) != 0) return -11;
|
||||
db = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)db;
|
||||
u32 max_entries = cluster_bytes / 32;
|
||||
for (u32 e = 0; e < max_entries; e++) {
|
||||
if (dir[e].name[0] == 0) {
|
||||
if (!have_free) { have_free = 1; free_slot_clus = dclus; free_idx = e; }
|
||||
goto scan_done; /* 0x00 = 目录结尾 */
|
||||
}
|
||||
if ((u8)dir[e].name[0] == 0xE5) {
|
||||
if (!have_free) { have_free = 1; free_slot_clus = dclus; free_idx = e; }
|
||||
continue;
|
||||
}
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, name11)) {
|
||||
existing_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
found = 1; found_idx = e; found_clus = dclus;
|
||||
if (!have_free) { have_free = 1; free_slot_clus = dclus; free_idx = e; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) break;
|
||||
u32 fo = fat_byte_off + dclus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
dclus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
scan_done:
|
||||
; /* C89: 标签后必须是语句 */
|
||||
|
||||
u32 bytes_needed = size > 0 ? size : 1;
|
||||
u32 clusters_needed = (bytes_needed + cluster_bytes - 1) / cluster_bytes;
|
||||
u32 first_clus = 0;
|
||||
u32 prev_clus = 0;
|
||||
|
||||
if (found && existing_clus >= 2) {
|
||||
first_clus = existing_clus;
|
||||
u32 cur = existing_clus;
|
||||
u32 count = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && count < clusters_needed) {
|
||||
prev_clus = cur;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
count++;
|
||||
}
|
||||
while (count < clusters_needed) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
if (pfo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
count++;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
/* 释放多余旧簇 */
|
||||
u32 next = 0;
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
while (next >= 2 && next < 0x0FFFFFF8) {
|
||||
u32 nfo = fat_byte_off + next * 4;
|
||||
u32 nn = 0;
|
||||
if (nfo + 4 <= sizeof(f32_disk)) nn = f32_r32(f32_disk + nfo) & 0x0FFFFFFF;
|
||||
f32_disk[nfo] = 0; f32_disk[nfo+1] = 0; f32_disk[nfo+2] = 0; f32_disk[nfo+3] = 0;
|
||||
next = nn;
|
||||
}
|
||||
} else {
|
||||
if (!have_free) return -4; /* 目录链满,不扩展 */
|
||||
for (u32 i = 0; i < clusters_needed; i++) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
if (i == 0) first_clus = newc;
|
||||
if (prev_clus >= 2) {
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
/* 写数据到簇(窗口内直接改 f32_disk,窗口外 RMW) */
|
||||
u32 remaining = size;
|
||||
const u8 *src = data;
|
||||
u32 cur = first_clus;
|
||||
u32 ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
u32 chunk = remaining < cluster_bytes ? remaining : cluster_bytes;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
u8 *dst = f32_disk + (u64)clba * 512;
|
||||
for (u32 b = 0; b < chunk; b++) dst[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) dst[b] = 0;
|
||||
} else {
|
||||
if (f32_read_sectors(clba, spc, f32_cluster) != 0) return -5;
|
||||
for (u32 b = 0; b < chunk; b++) f32_cluster[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) f32_cluster[b] = 0;
|
||||
if (f32_write_sectors(clba, spc, f32_cluster) != 0) return -6;
|
||||
}
|
||||
src += chunk; remaining -= chunk; ci++;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
/* 更新目录项(所在簇可能不在窗口内,需 RMW) */
|
||||
u32 entry_clus = found ? found_clus : free_slot_clus;
|
||||
u32 entry_idx = found ? found_idx : free_idx;
|
||||
u32 elba = data_lba + (entry_clus - 2) * spc;
|
||||
u8 *ebuf;
|
||||
int ebuf_windowed = ((u64)elba * 512 + cluster_bytes <= 256ULL * 512);
|
||||
if (ebuf_windowed) ebuf = f32_disk + (u64)elba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(elba, spc, f32_cluster) != 0) return -12;
|
||||
ebuf = f32_cluster;
|
||||
}
|
||||
f32_dirent *e = &((f32_dirent *)ebuf)[entry_idx];
|
||||
if (!found) {
|
||||
for (int i = 0; i < 11; i++) e->name[i] = name11[i];
|
||||
e->attr = 0x20; e->ntr = 0; e->ctenth = 0; e->ctime = 0; e->cdate = 0; e->adate = 0;
|
||||
e->wtime = 0; e->wdate = 0;
|
||||
}
|
||||
e->chigh = (u16)((first_clus >> 16) & 0xFFFF);
|
||||
e->clow = (u16)(first_clus & 0xFFFF);
|
||||
e->fsize = size;
|
||||
|
||||
/* 回写 FAT(两份)+ 目录项簇 + 窗口内数据簇 */
|
||||
for (u32 f = 0; f < bpb->fc; f++) {
|
||||
u32 flba = fat_lba + f * fat_sectors;
|
||||
if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -7;
|
||||
}
|
||||
if (ebuf_windowed) {
|
||||
if (f32_write_sectors(elba, spc, f32_disk + (u64)elba * 512) != 0) return -8;
|
||||
} else {
|
||||
if (f32_write_sectors(elba, spc, f32_cluster) != 0) return -8;
|
||||
}
|
||||
cur = first_clus; ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
if (f32_write_sectors(clba, spc, f32_disk + (u64)clba * 512) != 0) return -9;
|
||||
}
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
ci++;
|
||||
}
|
||||
f32_disk_loaded = 0; /* 下次操作重新加载,避免使用脏缓存 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 删除根目录下指定 8.3 名文件:清空簇链 + 标记目录项为 0xE5。
|
||||
* 返回 0 成功,-1 未找到,其他负值失败。 */
|
||||
static inline int f32_delete_root_file(const char *name11) {
|
||||
@@ -474,16 +687,18 @@ done:
|
||||
|
||||
/* 目录条目(供 fileman 等工具使用) */
|
||||
typedef struct {
|
||||
char name[13]; /* 8.3 显示名 */
|
||||
u32 clus; /* 首簇号 */
|
||||
u32 size; /* 文件大小(目录为 0) */
|
||||
char name[13]; /* 8.3 显示名 */
|
||||
u32 clus; /* 首簇号 */
|
||||
u32 size; /* 文件大小(目录为 0) */
|
||||
u8 attr;
|
||||
u8 is_dir;
|
||||
u8 has_lfn; /* 1=long_name 字段有效 */
|
||||
char long_name[260]; /* LFN 长文件名(ASCII) */
|
||||
} f32_entry;
|
||||
|
||||
typedef int (*f32_list_entry_cb)(const f32_entry *e, void *user_data);
|
||||
|
||||
/* 列举指定目录簇的内容。
|
||||
/* 列举指定目录簇的内容(含 LFN 长文件名)。
|
||||
* dir_clus = 0 表示根目录(自动从 BPB 取 root_clus)。
|
||||
* 回调返回非 0 立即停止。返回 0 成功,负值失败。 */
|
||||
static inline int f32_list_dir(u32 dir_clus, f32_list_entry_cb emit, void *user_data) {
|
||||
@@ -493,6 +708,9 @@ static inline int f32_list_dir(u32 dir_clus, f32_list_entry_cb emit, void *user_
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = (dir_clus == 0) ? bpb->root_clus : dir_clus;
|
||||
/* LFN 缓冲必须在簇循环外声明:跨簇 LFN 链需要持续存活 */
|
||||
fat32_lfn_buf lfn;
|
||||
fat32_lfn_init(&lfn);
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
@@ -502,22 +720,38 @@ static inline int f32_list_dir(u32 dir_clus, f32_list_entry_cb emit, void *user_
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)cb;
|
||||
for (u32 e = 0; e * 32 < spc * 512; e++) {
|
||||
if (dir[e].name[0] == 0) goto done;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
const u8 *entries = (const u8 *)cb;
|
||||
u32 entry_count = (spc * 512) / 32;
|
||||
for (u32 e = 0; e < entry_count; e++) {
|
||||
const u8 *entry = entries + e * 32;
|
||||
if (entry[0] == 0) goto done;
|
||||
if ((u8)entry[0] == 0xE5) {
|
||||
/* 已删除项:清空 LFN 缓冲防止残留污染 */
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
const f32_dirent *de = (const f32_dirent *)entry;
|
||||
int is_short = fat32_lfn_process(&lfn, entry);
|
||||
if (!is_short) continue; /* LFN 片段,等待短名项 */
|
||||
if (de->attr & 0x08) continue; /* 卷标 */
|
||||
/* 跳过 "." 和 ".." 条目 */
|
||||
if (dir[e].name[0] == '.' && (dir[e].name[1] == ' ' || dir[e].name[1] == '.'))
|
||||
if (de->name[0] == '.' && (de->name[1] == ' ' || de->name[1] == '.'))
|
||||
continue;
|
||||
f32_entry fe;
|
||||
f32_name_from_83(dir[e].name, fe.name);
|
||||
fe.clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
fe.size = dir[e].fsize;
|
||||
fe.attr = dir[e].attr;
|
||||
fe.is_dir = (dir[e].attr & 0x10) ? 1 : 0;
|
||||
f32_name_from_83(de->name, fe.name);
|
||||
fe.clus = (u32)f32_r16((const u8*)&de->clow) | ((u32)f32_r16((const u8*)&de->chigh) << 16);
|
||||
fe.size = de->fsize;
|
||||
fe.attr = de->attr;
|
||||
fe.is_dir = (de->attr & 0x10) ? 1 : 0;
|
||||
fe.has_lfn = 0;
|
||||
fe.long_name[0] = 0;
|
||||
if (lfn.valid) {
|
||||
int ln = fat32_lfn_to_ascii(&lfn, fe.long_name, sizeof(fe.long_name));
|
||||
if (ln >= 0) fe.has_lfn = 1;
|
||||
}
|
||||
if (emit(&fe, user_data) != 0) goto done;
|
||||
/* 短名项处理完毕,重置 LFN 缓冲等待下一条链 */
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
@@ -559,4 +793,205 @@ static inline int f32_read_file_by_clus(u32 clus, u32 size, u8 *out_buf, u32 buf
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- LFN 长文件名支持(Phase 1 SYSTEM/lib 兼容层) ---- */
|
||||
|
||||
/* 在指定目录簇中按长文件名查找文件/子目录。
|
||||
* dir_clus : 起始目录簇(0=根目录)
|
||||
* long_name : 目标长文件名(ASCII,大小写不敏感);为 NULL 时仅按 8.3 短名匹配
|
||||
* is_dir : 1 只匹配子目录,0 只匹配文件
|
||||
* out_clus : 输出首簇号
|
||||
* out_size : 输出文件大小
|
||||
* 返回 0 成功,-1 未找到 / 错误。
|
||||
* LFN 缓冲跨簇遍历持续存活,支持跨簇边界的 LFN 链。 */
|
||||
static inline int f32_find_in_dir_lfn(u32 dir_clus, const char *long_name,
|
||||
int is_dir, u32 *out_clus, u32 *out_size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = (dir_clus == 0) ? bpb->root_clus : dir_clus;
|
||||
|
||||
/* LFN 缓冲必须在簇循环外声明:跨簇 LFN 链需要持续存活 */
|
||||
fat32_lfn_buf lfn;
|
||||
fat32_lfn_init(&lfn);
|
||||
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const u8 *entries = (const u8 *)cb;
|
||||
u32 entry_count = (spc * 512) / 32;
|
||||
for (u32 e = 0; e < entry_count; e++) {
|
||||
const u8 *entry = entries + e * 32;
|
||||
if (entry[0] == 0) goto not_found;
|
||||
if ((u8)entry[0] == 0xE5) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
const f32_dirent *de = (const f32_dirent *)entry;
|
||||
int is_short = fat32_lfn_process(&lfn, entry);
|
||||
if (!is_short) continue; /* LFN 片段 */
|
||||
if (de->attr & 0x08) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue; /* 卷标 */
|
||||
}
|
||||
int entry_is_dir = (de->attr & 0x10) ? 1 : 0;
|
||||
if (entry_is_dir != is_dir) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
/* 先尝试 LFN 长名匹配,再回退 8.3 匹配 */
|
||||
int matched = 0;
|
||||
if (lfn.valid && long_name) {
|
||||
char ascii[FAT32_LFN_MAX];
|
||||
int n = fat32_lfn_to_ascii(&lfn, ascii, sizeof(ascii));
|
||||
if (n >= 0 && fat32_lfn_streq_ci(ascii, long_name)) matched = 1;
|
||||
}
|
||||
if (!matched && long_name) {
|
||||
/* 用短名构造 8.3 显示串与目标比较(大小写不敏感) */
|
||||
char short_disp[13];
|
||||
fat32_lfn_short_to_str((const u8 *)de->name, short_disp);
|
||||
if (fat32_lfn_streq_ci(short_disp, long_name)) matched = 1;
|
||||
}
|
||||
if (matched) {
|
||||
*out_clus = (u32)f32_r16((const u8 *)&de->clow) |
|
||||
((u32)f32_r16((const u8 *)&de->chigh) << 16);
|
||||
*out_size = de->fsize;
|
||||
return 0;
|
||||
}
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
not_found:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 按 '/' 分隔的路径定位最终目录簇。
|
||||
* path : 路径(如 "lib" 或 "system/deshab64"),每段为长文件名
|
||||
* out_clus : 输出目录簇号
|
||||
* 返回 0 成功,-1 失败。空路径返回根目录簇。 */
|
||||
static inline int f32_find_path_dir_lfn(const char *path, u32 *out_clus) {
|
||||
if (!path || !out_clus) return -1;
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 cur = bpb->root_clus;
|
||||
|
||||
u32 pos = 0;
|
||||
u32 path_len = 0;
|
||||
while (path[path_len]) path_len++;
|
||||
|
||||
/* 空路径或仅 "/" → 根目录 */
|
||||
if (path_len == 0 || (path_len == 1 && path[0] == '/')) {
|
||||
*out_clus = cur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (pos < path_len) {
|
||||
/* 跳过前导 '/' */
|
||||
while (pos < path_len && path[pos] == '/') pos++;
|
||||
if (pos >= path_len) break;
|
||||
/* 提取路径组件(到下一个 '/' 或结尾) */
|
||||
char comp[260];
|
||||
u32 ci = 0;
|
||||
while (pos < path_len && path[pos] != '/' && ci + 1 < sizeof(comp)) {
|
||||
comp[ci++] = path[pos++];
|
||||
}
|
||||
comp[ci] = 0;
|
||||
if (ci == 0) continue;
|
||||
|
||||
u32 sub_clus = 0, sub_size = 0;
|
||||
if (f32_find_in_dir_lfn(cur, comp, 1, &sub_clus, &sub_size) != 0) return -1;
|
||||
cur = sub_clus;
|
||||
}
|
||||
*out_clus = cur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按长路径读取文件到 f32_data 缓冲。
|
||||
* path : '/' 分隔的长文件名路径(如 "lib/libtest.so")
|
||||
* out_data : 返回 f32_data 内的指针
|
||||
* out_size : 返回文件大小
|
||||
* 返回 0 成功,负值失败。 */
|
||||
static inline int f32_read_path_lfn(const char *path, u8 **out_data, u32 *out_size) {
|
||||
if (!path || !out_data || !out_size) return -1;
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
|
||||
/* 分离目录部分与文件名 */
|
||||
u32 path_len = 0;
|
||||
while (path[path_len]) path_len++;
|
||||
u32 last_slash = 0xFFFFFFFFu;
|
||||
for (u32 i = 0; i < path_len; i++) {
|
||||
if (path[i] == '/') last_slash = i;
|
||||
}
|
||||
const char *fname = (last_slash == 0xFFFFFFFFu) ? path : path + last_slash + 1;
|
||||
|
||||
/* 定位父目录簇 */
|
||||
u32 dir_clus = 0;
|
||||
if (last_slash == 0xFFFFFFFFu) {
|
||||
/* 无 '/' → 根目录 */
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
dir_clus = bpb->root_clus;
|
||||
} else {
|
||||
char dirpath[260];
|
||||
u32 dn = last_slash;
|
||||
if (dn >= sizeof(dirpath)) dn = sizeof(dirpath) - 1;
|
||||
for (u32 i = 0; i < dn; i++) dirpath[i] = path[i];
|
||||
dirpath[dn] = 0;
|
||||
if (f32_find_path_dir_lfn(dirpath, &dir_clus) != 0) return -2;
|
||||
}
|
||||
|
||||
/* 在父目录中查找文件 */
|
||||
u32 file_clus = 0, file_size = 0;
|
||||
if (f32_find_in_dir_lfn(dir_clus, fname, 0, &file_clus, &file_size) != 0) return -3;
|
||||
if (file_size > sizeof(f32_data)) return -4;
|
||||
|
||||
/* 读取文件数据 */
|
||||
if (f32_read_file_by_clus(file_clus, file_size, f32_data, sizeof(f32_data)) != 0) return -5;
|
||||
*out_data = f32_data;
|
||||
*out_size = file_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按长路径写入文件(存在则替换,不存在则在父目录中新建 8.3 项)。
|
||||
* path : '/' 分隔路径(如 "system/deshab64/FUCK"),文件名段必须可转 8.3
|
||||
* 返回 0 成功,负值失败(-6 = 文件名不是合法 8.3)。 */
|
||||
static inline int f32_write_path_lfn(const char *path, const u8 *data, u32 size) {
|
||||
if (!path || !data) return -1;
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
|
||||
u32 path_len = 0;
|
||||
while (path[path_len]) path_len++;
|
||||
u32 last_slash = 0xFFFFFFFFu;
|
||||
for (u32 i = 0; i < path_len; i++) {
|
||||
if (path[i] == '/') last_slash = i;
|
||||
}
|
||||
const char *fname = (last_slash == 0xFFFFFFFFu) ? path : path + last_slash + 1;
|
||||
|
||||
u32 dir_clus = 0;
|
||||
if (last_slash == 0xFFFFFFFFu) {
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
dir_clus = bpb->root_clus;
|
||||
} else {
|
||||
char dirpath[260];
|
||||
u32 dn = last_slash;
|
||||
if (dn >= sizeof(dirpath)) dn = sizeof(dirpath) - 1;
|
||||
for (u32 i = 0; i < dn; i++) dirpath[i] = path[i];
|
||||
dirpath[dn] = 0;
|
||||
if (f32_find_path_dir_lfn(dirpath, &dir_clus) != 0) return -2;
|
||||
}
|
||||
|
||||
char name11[11];
|
||||
if (f32_name_to_83(fname, name11) != 0) return -6;
|
||||
return f32_write_file_in_dir(dir_clus, name11, data, size);
|
||||
}
|
||||
|
||||
#endif /* DESHAB_FAT32_IO_H */
|
||||
|
||||
+37
-9
@@ -28,6 +28,34 @@
|
||||
#define NS_DNS_IP NS_IP4(10,0,2,3)
|
||||
#define NS_NETMASK NS_IP4(255,255,255,0)
|
||||
|
||||
/* 当前生效 IP 配置:默认 slirp 硬编码;netman 租约经 ns_apply_lease 覆盖。
|
||||
* 租约约定:dsk_boot_context.reserved[7] 存放指向 net_lease_info(dsk.h)的
|
||||
* 指针,结构由 DSK 常驻持有,netman 在 DHCP/静态配置成功后写入。 */
|
||||
static u32 ns_guest_ip = NS_GUEST_IP;
|
||||
static u32 ns_gateway_ip = NS_GATEWAY_IP;
|
||||
static u32 ns_dns_ip = NS_DNS_IP;
|
||||
|
||||
static inline void ns_apply_lease(u32 ip, u32 gw, u32 dns) {
|
||||
if (ip) ns_guest_ip = ip;
|
||||
if (gw) ns_gateway_ip = gw;
|
||||
if (dns) ns_dns_ip = dns;
|
||||
}
|
||||
|
||||
/* 从 boot context 读取 netman 租约(存在且 valid 则应用)。
|
||||
* ctx_u64 为 dsk_boot_context 指针值。reserved[8] 位于偏移 160
|
||||
* (第 20 个 u64 起),reserved[7] = 第 27 个 u64,存 net_lease_info 指针:
|
||||
* +0 magic("NETL" 0x4E45544C) +4 flags(bit0 valid) +8 ip +12 gw +16 dns */
|
||||
static inline void ns_apply_lease_from_ctx(u64 ctx_u64) {
|
||||
if (!ctx_u64) return;
|
||||
const u64 *ctx = (const u64 *)ctx_u64;
|
||||
u64 lease_ptr = ctx[27]; /* reserved[7] */
|
||||
if (!lease_ptr) return;
|
||||
const u32 *li = (const u32 *)lease_ptr;
|
||||
if (li[0] != 0x4E45544Cu) return; /* NET_LEASE_MAGIC */
|
||||
if (!(li[1] & 1u)) return; /* NET_LEASE_F_VALID */
|
||||
ns_apply_lease(li[2], li[3], li[4]);
|
||||
}
|
||||
|
||||
#define NS_ETH_ARP 0x0806
|
||||
#define NS_ETH_IPV4 0x0800
|
||||
#define NS_IP_ICMP 1
|
||||
@@ -272,10 +300,10 @@ static inline void ns_arp_handle(const u8 *pkt, u32 len) {
|
||||
u16 oper = ns_r16(pkt + 6);
|
||||
u32 spa = ns_r32(pkt + 14);
|
||||
u32 tpa = ns_r32(pkt + 24);
|
||||
if (oper == 1 && tpa == NS_GUEST_IP) {
|
||||
if (oper == 1 && tpa == ns_guest_ip) {
|
||||
/* 请求我们的 IP → 回复 */
|
||||
u8 reply[28];
|
||||
ns_arp_build(reply, 2, ns_mac, NS_GUEST_IP, pkt + 8, spa);
|
||||
ns_arp_build(reply, 2, ns_mac, ns_guest_ip, pkt + 8, spa);
|
||||
ns_send_eth(pkt + 8, NS_ETH_ARP, reply, 28);
|
||||
} else if (oper == 2) {
|
||||
ns_arp_cache_put(spa, pkt + 8);
|
||||
@@ -314,7 +342,7 @@ static inline int ns_arp_resolve(u32 ip, u8 out_mac[6]) {
|
||||
static const u8 zero_mac[6] = {0,0,0,0,0,0};
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
u8 req[28];
|
||||
ns_arp_build(req, 1, ns_mac, NS_GUEST_IP, zero_mac, ip);
|
||||
ns_arp_build(req, 1, ns_mac, ns_guest_ip, zero_mac, ip);
|
||||
ns_arp_reply_valid = 0;
|
||||
if (ns_send_eth(bcast, NS_ETH_ARP, req, 28) != 0) continue;
|
||||
u64 deadline = ns_rdtsc() + ns_tsc_per_ms * 1000;
|
||||
@@ -341,7 +369,7 @@ static inline int ns_arp_resolve(u32 ip, u8 out_mac[6]) {
|
||||
/* 目标 IP 的下一跳 MAC(同子网直连,否则走网关)。返回 0 成功。 */
|
||||
static inline int ns_route_mac(u32 dst_ip, u8 out_mac[6]) {
|
||||
u32 next = dst_ip;
|
||||
if ((dst_ip & NS_NETMASK) != (NS_GUEST_IP & NS_NETMASK)) next = NS_GATEWAY_IP;
|
||||
if ((dst_ip & NS_NETMASK) != (ns_guest_ip & NS_NETMASK)) next = ns_gateway_ip;
|
||||
return ns_arp_resolve(next, out_mac);
|
||||
}
|
||||
|
||||
@@ -367,7 +395,7 @@ static inline int ns_ping(u32 dst_ip, u16 seq, u32 timeout_ms, u32 *out_rtt_ms,
|
||||
if (ns_route_mac(dst_ip, dst_mac) != 0) return -1;
|
||||
|
||||
u8 pkt[20 + 8 + 32];
|
||||
ns_ipv4_build(pkt, NS_IP_ICMP, NS_GUEST_IP, dst_ip, 8 + 32);
|
||||
ns_ipv4_build(pkt, NS_IP_ICMP, ns_guest_ip, dst_ip, 8 + 32);
|
||||
u8 *icmp = pkt + 20;
|
||||
icmp[0] = 8; /* echo request */
|
||||
icmp[1] = 0;
|
||||
@@ -415,7 +443,7 @@ static inline int ns_dns_resolve(const char *host, u32 *out_ip) {
|
||||
if (ns_parse_ip(host, out_ip) == 0) return 0;
|
||||
|
||||
u8 dst_mac[6];
|
||||
if (ns_route_mac(NS_DNS_IP, dst_mac) != 0) return -1;
|
||||
if (ns_route_mac(ns_dns_ip, dst_mac) != 0) return -1;
|
||||
|
||||
/* 构造查询:header + qname + qtype/qclass */
|
||||
u8 q[512];
|
||||
@@ -443,7 +471,7 @@ static inline int ns_dns_resolve(const char *host, u32 *out_ip) {
|
||||
u32 qlen = p;
|
||||
|
||||
u8 pkt[20 + 8 + 512];
|
||||
ns_ipv4_build(pkt, NS_IP_UDP, NS_GUEST_IP, NS_DNS_IP, 8 + qlen);
|
||||
ns_ipv4_build(pkt, NS_IP_UDP, ns_guest_ip, ns_dns_ip, 8 + qlen);
|
||||
u8 *udp = pkt + 20;
|
||||
ns_w16(udp + 0, 49153);
|
||||
ns_w16(udp + 2, 53);
|
||||
@@ -540,7 +568,7 @@ static inline int ns_tcp_emit(u8 flags, const u8 *data, u32 len, u32 seq) {
|
||||
u8 pkt[20 + 20 + 1460];
|
||||
if (len > 1460) return -1;
|
||||
u32 tcp_len = 20 + len;
|
||||
ns_ipv4_build(pkt, NS_IP_TCP, NS_GUEST_IP, ns_tc.dst_ip, tcp_len);
|
||||
ns_ipv4_build(pkt, NS_IP_TCP, ns_guest_ip, ns_tc.dst_ip, tcp_len);
|
||||
u8 *t = pkt + 20;
|
||||
ns_w16(t + 0, ns_tc.src_port);
|
||||
ns_w16(t + 2, ns_tc.dst_port);
|
||||
@@ -554,7 +582,7 @@ static inline int ns_tcp_emit(u8 flags, const u8 *data, u32 len, u32 seq) {
|
||||
if (len) ns_memcpy(t + 20, data, len);
|
||||
/* 伪头部校验和 */
|
||||
u8 ph[12];
|
||||
ns_w32(ph + 0, NS_GUEST_IP);
|
||||
ns_w32(ph + 0, ns_guest_ip);
|
||||
ns_w32(ph + 4, ns_tc.dst_ip);
|
||||
ph[8] = 0; ph[9] = NS_IP_TCP;
|
||||
ns_w16(ph + 10, (u16)tcp_len);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/* realdll64.c — PE32+ 真实 DLL 链验证程序
|
||||
*
|
||||
* 用途: 验证 Deshab PE 兼容层 pe_dll_manager 对 SYSTEM/lib 真实 DLL 的
|
||||
* 加载与解析(含转发导出跟随与 DllMain ATTACH)。
|
||||
*
|
||||
* 验证路径:
|
||||
* A) 静态导入 msvcrt.dll 中非 shim 函数(strcat/strstr/atoi):
|
||||
* shim 表未命中 → pe_dll_resolve → 真实 msvcrt.dll
|
||||
* (Win10 msvcrt 把这些函数转发到 ucrtbase,验证转发导出跟随)
|
||||
* B) LoadLibraryA("ucrtbase.dll") + GetProcAddress:
|
||||
* 未知 DLL → pe_dll_load 真实加载,句柄=镜像基址,
|
||||
* GetProcAddress 按基址解析 strlen/memcpy 并调用验证。
|
||||
*
|
||||
* 输出: 全部经 shim printf(串口)。锚点格式 "[REALDLL] ..."。
|
||||
*
|
||||
* 编译 (MinGW-w64):
|
||||
* x86_64-w64-mingw32-gcc -fno-builtin -O1 -o realdll64.exe realdll64.c \
|
||||
* -nostartfiles -Wl,--entry,main -Wl,--image-base,0x140000000
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* 显式声明:这些函数不在 shim 表内,强制走真实 DLL 解析。
|
||||
* -fno-builtin 阻止 gcc 内联展开,保证产生 IAT 导入调用。 */
|
||||
__declspec(dllimport) char * __cdecl strcat(char *dst, const char *src);
|
||||
__declspec(dllimport) char * __cdecl strstr(const char *hay, const char *needle);
|
||||
__declspec(dllimport) int __cdecl atoi(const char *s);
|
||||
|
||||
typedef unsigned long long (__cdecl *strlen_fn)(const char *);
|
||||
typedef void *(__cdecl *memcpy_fn)(void *, const void *, unsigned long long);
|
||||
|
||||
/* MinGW ABI 对 main 强制插入 __main 调用(CRT 静态构造注册)。
|
||||
* freestanding(-nostdlib)无 CRT,提供空 stub。 */
|
||||
void __main(void) {}
|
||||
|
||||
static int g_fails = 0;
|
||||
|
||||
static void check(const char *name, int ok) {
|
||||
printf("[REALDLL] %s: %s\n", name, ok ? "OK" : "FAIL");
|
||||
if (!ok) g_fails++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("[REALDLL] begin\n");
|
||||
|
||||
/* ---- A1: strcat(真实 DLL,Win10 转发到 ucrtbase) ---- */
|
||||
char buf[64];
|
||||
buf[0] = '\0';
|
||||
strcat(buf, "abc");
|
||||
strcat(buf, "def");
|
||||
check("A1 strcat", buf[0] == 'a' && buf[1] == 'b' && buf[2] == 'c' &&
|
||||
buf[3] == 'd' && buf[4] == 'e' && buf[5] == 'f' &&
|
||||
buf[6] == '\0');
|
||||
|
||||
/* ---- A2: strstr(真实 DLL) ---- */
|
||||
const char *hay = "hello deshab world";
|
||||
char *hit = strstr(hay, "deshab");
|
||||
check("A2 strstr", hit == hay + 6);
|
||||
|
||||
/* ---- A3: atoi(真实 DLL) ---- */
|
||||
check("A3 atoi", atoi("12345") == 12345 && atoi("-42") == -42);
|
||||
|
||||
/* ---- B1: LoadLibraryA 真实 DLL ---- */
|
||||
HMODULE ucrt = LoadLibraryA("ucrtbase.dll");
|
||||
check("B1 LoadLibraryA(ucrtbase.dll)", ucrt != NULL);
|
||||
|
||||
if (ucrt) {
|
||||
/* ---- B2: GetProcAddress strlen + 调用 ---- */
|
||||
strlen_fn p_strlen = (strlen_fn)GetProcAddress(ucrt, "strlen");
|
||||
check("B2 GetProcAddress(strlen)", p_strlen != NULL);
|
||||
if (p_strlen) {
|
||||
check("B2 strlen call", p_strlen("hello world") == 11);
|
||||
}
|
||||
|
||||
/* ---- B3: GetProcAddress memcpy + 调用 ---- */
|
||||
memcpy_fn p_memcpy = (memcpy_fn)GetProcAddress(ucrt, "memcpy");
|
||||
check("B3 GetProcAddress(memcpy)", p_memcpy != NULL);
|
||||
if (p_memcpy) {
|
||||
char dst[16];
|
||||
for (int i = 0; i < 16; i++) dst[i] = 0;
|
||||
p_memcpy(dst, "0123456789", 10);
|
||||
check("B3 memcpy call", dst[0] == '0' && dst[9] == '9' && dst[10] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
printf("[REALDLL] end fails=%d\n", g_fails);
|
||||
ExitProcess(g_fails);
|
||||
return g_fails;
|
||||
}
|
||||
Binary file not shown.
@@ -294,6 +294,7 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
}
|
||||
|
||||
da_init(&g_ac, ctx);
|
||||
ns_apply_lease_from_ctx((u64)(const void *)ctx); /* netman 租约覆盖默认 slirp IP */
|
||||
term_init_layout();
|
||||
term_clear();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user