fix/boot-to-desktop

fix(dsk,firstInit,build): let the system boot all the way to the desktop
This commit is contained in:
boonxiong2
2026-10-05 18:41:28 +08:00
committed by GitHub
5 changed files with 190 additions and 21 deletions
+80 -1
View File
@@ -950,7 +950,86 @@ static int fat32_write_root_file(const char *name11, const u8 *data, u32 size) {
}
} else {
/* Allocate new clusters */
if (free_entry < 0) { logl("[DSK] fat32 write: no free dir entry"); return -4; }
if (free_entry < 0) {
/* 根目录链里没有空位 —— 按 FAT32 规范扩展目录链:分配一个新簇,
* 挂到根目录链尾,在新簇的第一个条目里放目录项。(原来这里直接
* return -4,导致 ESP 根目录一簇写满后 USER.CONF 永远写不进去。) */
u32 tail = root_clus;
if (tail < 2) { logl("[DSK] fat32 write: bad root cluster"); return -4; }
for (;;) {
u32 nxt = fat32_fat_read_entry(fat_byte_off, tail);
if (nxt >= 2 && nxt < 0x0FFFFFF8) { tail = nxt; continue; }
break;
}
u32 newdir = fat32_find_free_cluster(fat_byte_off, (fat_sectors * 512) / 4);
if (newdir == 0) { logl("[DSK] fat32 write: no free cluster for dir growth"); return -3; }
if (fat32_fat_write_entry(fat_byte_off, newdir, 0x0FFFFFF8) != 0) return -7;
if (fat32_fat_write_entry(fat_byte_off, tail, newdir) != 0) return -7;
logh("[DSK] fat32 write: root dir grown, new clus=", newdir);
/* 新簇整簇清零(0x00 = 目录结束标记),再把目录项写在首项 */
u8 *nb = g_cluster;
for (u32 b = 0; b < cluster_bytes; b++) nb[b] = 0;
fat32_de *nd = (fat32_de *)nb;
for (int i = 0; i < 11; i++) nd[0].name[i] = name11[i];
nd[0].attr = 0x20;
nd[0].ntr = 0; nd[0].ctenth = 0; nd[0].ctime = 0; nd[0].cdate = 0;
nd[0].adate = 0; nd[0].wtime = 0; nd[0].wdate = 0;
/* 数据簇先分配好,再填进目录项(与下方 !found 分支同序) */
u32 dfirst = 0, dprev = 0;
for (u32 i = 0; i < clusters_needed; i++) {
u32 newc = fat32_find_free_cluster(fat_byte_off, (fat_sectors * 512) / 4);
if (newc == 0) { logl("[DSK] fat32 write: no free clusters"); return -3; }
logh("[DSK] fat32 write: alloc clus=", newc);
if (fat32_fat_write_entry(fat_byte_off, newc, 0x0FFFFFF8) != 0) return -7;
if (i == 0) dfirst = newc;
if (dprev >= 2) {
if (fat32_fat_write_entry(fat_byte_off, dprev, newc) != 0) return -7;
}
dprev = newc;
}
nd[0].chigh = (u16)((dfirst >> 16) & 0xFFFF);
nd[0].clow = (u16)(dfirst & 0xFFFF);
nd[0].fsize = size;
u32 nlba = data_lba + (newdir - 2) * spc;
if (fat32_write_sectors(nlba, spc, nb) != 0) {
logl("[DSK] fat32 write: new dir cluster write failed");
return -8;
}
/* 写数据簇 */
u32 rem2 = size;
const u8 *s2 = data;
u32 c2 = dfirst;
u32 ci2 = 0;
while (c2 >= 2 && c2 < 0x0FFFFFF8 && ci2 < clusters_needed) {
u32 clba2 = data_lba + (c2 - 2) * spc;
u32 chunk2 = rem2 < cluster_bytes ? rem2 : cluster_bytes;
if (fat32_read_sectors(clba2, spc, g_cluster) != 0) return -5;
for (u32 b = 0; b < chunk2; b++) g_cluster[b] = s2[b];
for (u32 b = chunk2; b < cluster_bytes; b++) g_cluster[b] = 0;
if (fat32_write_sectors(clba2, spc, g_cluster) != 0) return -6;
s2 += chunk2; rem2 -= chunk2; ci2++;
c2 = fat32_fat_read_entry(fat_byte_off, c2);
}
/* FAT 表写回(与下方第 6 步同逻辑:只写 g_disk 缓冲覆盖得到的那些 FAT 副本) */
{
u32 max_fat_sectors_in_buffer = (256 - fat_lba) / fat_sectors;
if (max_fat_sectors_in_buffer > bpb->fc) max_fat_sectors_in_buffer = bpb->fc;
for (u32 f = 0; f < max_fat_sectors_in_buffer; f++) {
u32 flba = fat_lba + f * fat_sectors;
u32 sectors_to_write = fat_sectors;
if (flba + sectors_to_write > 256) sectors_to_write = 256 - flba;
if (sectors_to_write > 0 && flba < 256) {
if (fat32_write_sectors(flba, sectors_to_write, disk + (u64)flba * 512) != 0) return -7;
}
}
}
return 0; /* 已自行完成写入,跳过下方通用路径 */
}
for (u32 i = 0; i < clusters_needed; i++) {
u32 newc = fat32_find_free_cluster(fat_byte_off, (fat_sectors * 512) / 4);
if (newc == 0) { logl("[DSK] fat32 write: no free clusters"); return -3; }
+39 -8
View File
@@ -1132,7 +1132,8 @@ static void draw_wifi_row(u32 *fb, i64 card_x, i64 card_y, setup_net *net, int r
#define NET_ACT_PASS 5
#define NET_ACT_SAVE 6
#define NET_ACT_CONNECT 7
#define NET_ACT_COUNT 8
#define NET_ACT_SKIP 8
#define NET_ACT_COUNT 9
static void redraw_network_card(u32 *fb, i64 card_x, i64 card_y, setup_net *net, int active, i64 mx, i64 my, u32 bg, u32 fg) {
u32 card = CARD_BG;
@@ -1167,6 +1168,13 @@ static void redraw_network_card(u32 *fb, i64 card_x, i64 card_y, setup_net *net,
stroke_rounded_rect(fb, card_x + 680, card_y + 310, 180, 44, 3, 1, active == NET_ACT_CONNECT ? 0xFF6A8AAA : 0xFF404060);
fb_text(fb, "Connect", card_x + 725, card_y + 322, 0xFFE0E0E0, btn_bg);
}
/* 跳过按钮:联网从不是进系统的前置条件,任何时刻都必须有退路。 */
{
u32 btn_bg = active == NET_ACT_SKIP ? 0xFF45454A : 0xFF303034;
fill_rounded_rect(fb, card_x + 890, card_y + 310, 150, 44, 3, btn_bg);
stroke_rounded_rect(fb, card_x + 890, card_y + 310, 150, 44, 3, 1, active == NET_ACT_SKIP ? 0xFF6C6C72 : 0xFF38383C);
fb_text(fb, "Skip", card_x + 935, card_y + 322, 0xFFB0B0B8, btn_bg);
}
/* 状态提示 */
if (net->connected) {
@@ -1178,6 +1186,7 @@ static void redraw_network_card(u32 *fb, i64 card_x, i64 card_y, setup_net *net,
}
fb_text(fb, "Save=save config only", card_x + 470, card_y + 510, 0xFF606068, card);
fb_text(fb, "Connect=save and connect", card_x + 470, card_y + 535, 0xFF606068, card);
fb_text(fb, "Skip=set up network later", card_x + 470, card_y + 560, 0xFF606068, card);
cursor_draw(fb, mx, my);
}
@@ -1254,6 +1263,14 @@ static void read_network_page(u32 *fb, i64 card_x, i64 card_y, setup_net *net, i
for (int i=0;i<30;i++) delay_frame();
return;
}
/* 跳过按钮:不配网络直接进系统(网络留到以后) */
if (*mx >= card_x + 890 && *mx < card_x + 1040 && *my >= card_y + 310 && *my < card_y + 354) {
net->connected = 0;
logl("[FirstInit] network setup skipped by user");
redraw_network_card(fb, card_x, card_y, net, NET_ACT_SKIP, *mx, *my, bg, fg);
for (int i=0;i<30;i++) delay_frame();
return;
}
}
prev_btns = btns;
continue;
@@ -1300,6 +1317,13 @@ static void read_network_page(u32 *fb, i64 card_x, i64 card_y, setup_net *net, i
redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg);
for (int i=0;i<30;i++) delay_frame();
return;
} else if (active == NET_ACT_SKIP) {
/* 不配网络直接进系统(网络留到以后) */
net->connected = 0;
logl("[FirstInit] network setup skipped by user");
redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg);
for (int i=0;i<30;i++) delay_frame();
return;
}
redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg);
continue;
@@ -1607,13 +1631,20 @@ void dsk_entry(const dsk_boot_context *ctx) {
net.connected = 0;
net.ssid[0] = 0;
net.password[0] = 0;
/* 始终显示网络设置页面,即使无无线设备也展示页面让用户确认 */
cursor_bg_valid = 0;
cursor_cur_x = -100;
cursor_cur_y = -100;
logl("[FirstInit] entering network setup page");
read_network_page(fb, card_x, card_y, &net, &mx, &my, bg, fg);
logl("[FirstInit] network setup page returned");
/* 无无线设备则直接跳过网络页:联网从来不是进系统的前置条件,
* 让用户在一个没有任何可选网络的页面上只能按 Save(或误按 Connect 吃
* NET-E01 panic)是设计缺陷。有设备时仍展示页面供确认/配置。 */
if (g_no_wireless_device) {
logl("[FirstInit] no wireless device; skipping network setup page");
net.connected = 0;
} else {
cursor_bg_valid = 0;
cursor_cur_x = -100;
cursor_cur_y = -100;
logl("[FirstInit] entering network setup page");
read_network_page(fb, card_x, card_y, &net, &mx, &my, bg, fg);
logl("[FirstInit] network setup page returned");
}
build_user_conf(pc, user, pass, &prefs, &net);
/* Pass config buffer to DSK via boot context reserved fields */
+17 -9
View File
@@ -167,17 +167,25 @@ if [[ -z "$p1" || -z "$p2" ]]; then
exit 1
fi
# 簇大小交给 mkfs.vfat 自动选择。
# ESP 的簇大小必须显式指定为 4 个扇区(2KiB),不能交给 mkfs 自动选。
#
# 历史坑一:这里曾经强制 -s 8(4KiB 簇)。256MiB 的 ESP 恰好被算成 65535
# 个簇(FAT32 的 0xFFF5 边界),该几何下 OVMF(EDK2 FatPkg) 拒挂该卷 ——
# 现象是 BdsDxe "Not Found",Shell 里分区只显示 BLKn、没有对应 FS。
# 对照实验:sidecar(CODE/linux/make_sata_sidecar.sh)用不带 -s 的
# mkfs.vfat 就一切正常,所以这里也不再手工指定簇大小。
# 历史坑一(OVMF 挂不上):曾经写死 -s 8(4KiB 簇)。256MiB 的 ESP 在该几何下
# 恰好得到 65535 个簇 —— 正是 FAT32 的 0xFFF5 边界 —— OVMF(EDK2 FatPkg) 拒挂
# 该卷,现象是 BdsDxe "Not Found",Shell 里分区只有 BLKn 没有 FS。
#
# 历史坑二:不要加 -f 1。UEFI 规范建议 ESP 用单 FAT 表,但实测 OVMF 对
# nfat=1/2 都能挂,加 -f 1 并不能解决上面的问题,反而偏离 sidecar 的行为。
mkfs.vfat -F 32 -n DESHABBOOT "$p1" >/dev/null
# 历史坑二(根目录写满):修坑一时改成不指定 -s 交给 mkfs 自动选,它给出
# spc=1(512B/簇)。根目录每簇只能放 16 个目录项,而打包进 ESP 根目录的内容
# (DESHABBOOT/EFI/LIMINE/BOOT/DRIVER/SYSTEM/USER/NVVARS + 它们的 LFN 项)
# 正好 16 项,根目录链又只有单簇 —— 于是 DSK 首次启动写 USER.CONF 时必然
# 拿到 "no free dir entry"(-4)→ FS-E02 panic,系统进不去。
#
# 256MiB ESP 各档簇大小的安全性(每档都算过):
# spc=1 16 项/簇 524288 簇 根目录一簇就满(坑二)
# spc=4 64 项/簇 131072 簇 ✅ 远离 FAT32 下限 65525 与 0xFFF5 边界
# spc=8 128 项/簇 65536 簇 ❌ 落在 65535 边界(坑一)
# spc=16 256 项/簇 32768 簇 ❌ 低于 FAT32 下限 65525
# 所以固定 -s 4:既让根目录有 64 项余量,又不碰任何边界。
mkfs.vfat -F 32 -s 4 -n DESHABBOOT "$p1" >/dev/null
# DSK 内嵌 ext4 读写驱动支持范围:4K block / 256B inode / extent 树,
# 不支持 journal、metadata_csum、64bit(遇到即 panic FS-E28)——
# 因此这里必须显式关闭这三个 feature。
+49
View File
@@ -793,6 +793,55 @@ if (Test-Path $GenTex) {
}
}
}
# Generate CJK bitmap font packs (.dbf) for the desktop / login / FirstInit.
# CODE/font/mkfont.py renders a TTF (GB2312 + ASCII by default) into the packed
# .dbf format read by CODE/font/dbf.h. Same class of artifact as the textures
# above: generated, gitignored (*.dbf), and therefore MUST be produced here —
# nothing else in the build regenerates it. Without it the desktop panics with
# DESK-E04 FONT PACK MISSING ([f32io] read_to rc=-3 on system/font/simhei_16.dbf).
$GenFont = Join-Path $Root 'CODE\font\mkfont.py'
$FontTtf = Join-Path $SystemDir 'system\font\simhei.ttf'
if ((Test-Path $GenFont) -and (Test-Path $FontTtf)) {
$fontPacks = @(
@{ Name = 'simhei_16.dbf'; Size = 16 },
@{ Name = 'simhei_24.dbf'; Size = 24 }
)
$fontTodo = @()
foreach ($fp in $fontPacks) {
$out = Join-Path $SystemDir ("system\font\" + $fp.Name)
if (-not (Test-Path $out) -or
((Get-Item $FontTtf).LastWriteTime -gt (Get-Item $out).LastWriteTime)) {
$fontTodo += ,@($fp.Name, $fp.Size, $out)
}
}
if ($fontTodo.Count -gt 0) {
Write-Host "[build] Generating CJK bitmap fonts ($($fontTodo.Count) pack(s))..."
$Python = $null
foreach ($p in @('python', 'python3',
'C:\msys64\mingw64\bin\python.exe')) {
try { $cmd = Get-Command $p -ErrorAction Stop; $Python = $cmd.Source; break } catch {}
}
foreach ($job in $fontTodo) {
$fname = $job[0]; $fsize = $job[1]; $fout = $job[2]
$ok = $false
if ($Python) {
& $Python $GenFont $FontTtf $fsize $fout
if ($LASTEXITCODE -eq 0) { $ok = $true }
}
if (-not $ok) {
# Fallback: WSL python3 (has Pillow on this machine)
$py = ($GenFont -replace '^([A-Za-z]):', '/mnt/$1')
$tt = ($FontTtf -replace '^([A-Za-z]):', '/mnt/$1')
$ou = ($fout -replace '^([A-Za-z]):', '/mnt/$1')
& wsl python3 $py $tt $fsize $ou
if ($LASTEXITCODE -eq 0) { $ok = $true }
}
if (-not $ok) {
Write-Host "[build] WARNING: font pack generation failed: $fname (desktop will panic DESK-E04)"
}
}
}
}
try {
& $make -C "$DskDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
+5 -3
View File
@@ -44,7 +44,7 @@
| e1000 | ✅ | RX/TX + DHCP/ARP/UDP/DNS(polling) | — |
| virtio_net | ✅ | legacy split-ring virtqueue(desc/avail/used)+ vq_setup + 32×2048B RX 缓冲 + TX/RX 环 + DHCP DISCOVER→OFFER selftest;VNET-E01/E02 零降级插桩。无设备时 WARN 后正常 ready(设计内回退) | `CODE/DKM/virtio_net/virtio_net.c`;未挂设备时串口 `[virtio_net] virtio-net device not found` → `driver ready` |
| 热卸载(D1) | ⬜ | 仅有 NO_UNLOAD 标志与状态机文档 | — |
| 按名 API 导出表(D3) | ⬜ | 固定偏移 struct ABI | `CODE/sdk/include/deshab/kernel_api.h` |
| 按名 API 导出表(D3) | ✅ | `dkm_kapi_table`(magic/version/count/entries/lookup)挂在 `kernel_api->kapi` 尾部(ABI 兼容追加);14 条按名导出(log.*/dma.alloc_pages/irq.register/irq.vector_*/mm.map_mmio/mm.unmap_mmio/apic.register_eoi/drr.*/utrw.debug_dump);selftest 含正例调用 + 未命中负例 + TST-E21..E24 插桩 | `CODE/UTSM/dkm/kernel_api.c` + `test/utsm_selftest.c`(需 FUCK `[boot] selftest=2` 才执行该段) |
| 彩色日志(D4) | ✅ | `dkm_log_set_driver`(驱动名 FNV hash → 8 色调色板,确定可复现);init 期间驱动日志带 ANSI 色(仅串口,BOOTLOG 纯文本);FUCK `[debug] color_log` 总开关 | `.build_tmp/qemu_serial_d4color.log`(1150 彩色行) |
## DSK 主内核 / 用户态
@@ -52,7 +52,7 @@
| 子系统 | 状态 | 边界/说明 | 证据 |
|--------|------|----------|------|
| 启动 Logo(静态莲花) | ✅ | 旋转加载环已移除 | QEMU 现象 |
| FirstInit 向导 | 🔶 | 输入+SHA256+写盘闭环;按键崩溃未复现(B6) | `.build_tmp/qemu_serial_b6.log` |
| FirstInit 向导 | ✅ | 输入+SHA256+写盘闭环;**联网不是进系统的前置条件**:检测到无无线设备直接跳过网络页,有设备时提供 Skip 按钮(渲染+鼠标+键盘三路),Connect 仍为零降级 NET-E01 panic | `.build_tmp/d2.log`(`no wireless device; skipping network setup page` → `USER.CONF` → login → desktop)|
| 可跳过 FirstInit(F5) | ✅ | `[dsk] skip_firstinit=1` 直达 desktop,跳过向导与登录 | `.build_tmp/qemu_serial_f1f4f5.log` |
| mouseInit(PS/2 鼠标) | ✅ | IRQ12 + 包解码 + 光标(M1) | `.build_tmp/qemu_serial_mouse.log` |
| desktop.elf | ✅ | 三页面+窗口+任务栏+双缓冲;打字机效果已删(整屏一次绘制);DSK-E01 fatal 插桩 | — |
@@ -81,7 +81,9 @@
| 项 | 状态 | 说明 | 证据 |
|----|------|------|------|
| ext4 读写驱动(CODE/dsk/ext4.c) | ✅ | GPT 定位 p2 + feature 校验 + extent 树(depth0/1)读写 + 块/inode 分配回收 + 目录项插入 + 写后回读校验;启动自检 PASS | `.build_tmp/qemu_run.log`([EXT4] selftest PASS) |
| FAT32 大盘写修复 | ✅ | 按需 FAT 表项 RMW(fat32_fat_read/write_entry)+ 块状空闲簇扫描(fat32_find_free_cluster);修复 rec_len +4 收缩偏移与 chunk 对齐两处错位 | 同上(USER.CONF verify ok → firstInit.txt flip ok) |
| FAT32 大盘写修复 | ✅ | 按需 FAT 表项 RMW(fat32_fat_read/write_entry)+ 块状空闲簇扫描(fat32_find_free_cluster);修复 rec_len +4 收缩偏移与 chunk 对齐两处错位;**根目录链满时自动扩展**(分配新簇挂链尾后写条目,不再直接 `-4` no free dir entry)| 同上(USER.CONF verify ok → firstInit.txt flip ok)|
| ESP 簇大小(打包侧) | ✅ | `-s 4`(2KiB 簇 / 64 目录项)固定:避开 `spc=8` 的 65535 簇 OVMF 拒绝边界,也避开 `spc=16` 的 FAT32 下限,同时给 ESP 根目录留出 64 项余量(`spc=1` 只有 16 项,打包内容正好塞满导致首次启动写 USER.CONF 必失败)| `CODE/linux/pack_system_image.sh`;`.build_tmp/d2.log` |
| 中文字体位图字库(.dbf) | ✅ | `build.ps1` 调用 `CODE/font/mkfont.py` 由 `simhei.ttf` 生成 `simhei_16.dbf` / `simhei_24.dbf`(GB2312+ASCII);此前无人生成,桌面必 panic DESK-E04 FONT PACK MISSING | `.build_tmp/d2.log`(`[zhfont] dbf loaded (16px ok)` → `desktop ready`)|
| FS-E01..E07 / FS-E2x / FS-E3x 插桩 | ✅ | 写失败/校验不符/feature 不支持/路径缺失 → 莲花 panic 零降级 | `.build_tmp/qemu_run.log`(FS-E02/E06/E22 复现) |
| 向导后强制重登 | ✅ | first-boot 分支 FirstInit→persist→login;登录跳过/失败 → FS-E06 panic | 同上(login success → desktop ready) |
| NET-E01 网络页 Connect panic | ✅ | 无 wlan 栈,Connect 即 panic;Save(仅存配置) 为唯一前进路径 | 同上(NET-E01 复现,fb pmemsave 确认莲花屏) |