From cc0fcf26e358f40697427b72bb68beacdb84a962 Mon Sep 17 00:00:00 2001 From: Linehu Date: Sat, 29 Aug 2026 18:47:54 +0800 Subject: [PATCH] FU --- CODE/DKM/ahci/ahci.c | 15 +- CODE/DKM/ehci/ehci.c | 1169 ++++++++++++++++++++++ CODE/DKM/xhci/xhci.c | 520 ++++++++-- CODE/UTSM/include/utsm/block.h | 1 + CODE/UTSM/include/utsm/log.h | 3 +- CODE/UTSM/kernel/block.c | 8 +- CODE/UTSM/kernel/block.o | Bin 3544 -> 3720 bytes CODE/UTSM/kernel/dsk_loader.c | 373 +++++++ CODE/UTSM/kernel/dsk_loader.o | Bin 18432 -> 26408 bytes CODE/UTSM/kernel/log.c | 3 +- CODE/UTSM/kernel/main.c | 75 +- CODE/UTSM/kernel/main.o | Bin 20832 -> 19912 bytes CODE/dsk/main.c | 4 + CODE/sdk/include/deshab/boot_log.h | 15 + CODE/sdk/include/deshab/kernel_api.h | 1 + CODE/tools/boot_log.h | 59 ++ CODE/tools/fat32_write.h | 856 ++++++++++++++++ CODE/tools/fileman/main.c | 29 +- CODE/tools/gpt_write.h | 406 ++++++++ CODE/tools/read_usb_logs.ps1 | 179 ++++ ISO/run_qemu.bat | 31 +- SYSTEM/boot/utsm.elf | Bin 276904 -> 281328 bytes SYSTEM/driver/block/ehci.drv | Bin 0 -> 29208 bytes SYSTEM/driver/block/xhci.drv | Bin 46792 -> 48832 bytes SYSTEM/driver/manifest.json | 8 + SYSTEM/limine/limine.conf | 2 + SYSTEM/system/deshab64/FUCK | 48 +- SYSTEM/system/deshab64/tools/fileman.elf | Bin 46888 -> 46888 bytes SYSTEM/system/user/use/firstInit.txt | 2 +- build.ps1 | 103 +- build/configs/dev/FUCK | 106 -- build/configs/dev/firstInit.txt | 2 - build/configs/realtest/FUCK | 84 -- build/configs/realtest/firstInit.txt | 1 - build/configs/release/FUCK | 112 --- build/configs/release/firstInit.txt | 2 - dcplogo.png | Bin 0 -> 248384 bytes debug-pe-compat-p1.md | 48 - tests/qemu/Run-All.ps1 | 115 --- tests/qemu/boot-regression.ps1 | 232 ----- tests/qemu/deaicup-e2e.ps1 | 369 ------- tests/qemu/driver-matrix.ps1 | 100 -- tests/qemu/lib/QemuTest.ps1 | 861 ---------------- tests/qemu/linux-compat.ps1 | 167 ---- tests/qemu/network-e2e.ps1 | 189 ---- tests/qemu/pe-compat.ps1 | 248 ----- tests/qemu/performance-baseline.ps1 | 139 --- tests/qemu/userapps-matrix.ps1 | 236 ----- tests/qemu/x86emu32-coverage.ps1 | 169 ---- 49 files changed, 3705 insertions(+), 3385 deletions(-) create mode 100644 CODE/DKM/ehci/ehci.c create mode 100644 CODE/sdk/include/deshab/boot_log.h create mode 100644 CODE/tools/boot_log.h create mode 100644 CODE/tools/fat32_write.h create mode 100644 CODE/tools/gpt_write.h create mode 100644 CODE/tools/read_usb_logs.ps1 create mode 100644 SYSTEM/driver/block/ehci.drv delete mode 100644 build/configs/dev/FUCK delete mode 100644 build/configs/dev/firstInit.txt delete mode 100644 build/configs/realtest/FUCK delete mode 100644 build/configs/realtest/firstInit.txt delete mode 100644 build/configs/release/FUCK delete mode 100644 build/configs/release/firstInit.txt create mode 100644 dcplogo.png delete mode 100644 debug-pe-compat-p1.md delete mode 100644 tests/qemu/Run-All.ps1 delete mode 100644 tests/qemu/boot-regression.ps1 delete mode 100644 tests/qemu/deaicup-e2e.ps1 delete mode 100644 tests/qemu/driver-matrix.ps1 delete mode 100644 tests/qemu/lib/QemuTest.ps1 delete mode 100644 tests/qemu/linux-compat.ps1 delete mode 100644 tests/qemu/network-e2e.ps1 delete mode 100644 tests/qemu/pe-compat.ps1 delete mode 100644 tests/qemu/performance-baseline.ps1 delete mode 100644 tests/qemu/userapps-matrix.ps1 delete mode 100644 tests/qemu/x86emu32-coverage.ps1 diff --git a/CODE/DKM/ahci/ahci.c b/CODE/DKM/ahci/ahci.c index e7db935..91a22b3 100644 --- a/CODE/DKM/ahci/ahci.c +++ b/CODE/DKM/ahci/ahci.c @@ -108,6 +108,8 @@ static u32 g_ready_port = 0xffffffffu; static ahci_cmd_header *g_cmd_header; static struct dkm_dma_buffer g_cmd_table; static struct dkm_dma_buffer g_data; +/* IDENTIFY 读出的用户可寻址扇区数;LBA48 优先,回退 LBA28 */ +static u64 g_sector_count; static u32 pci_read(u8 bus, u8 dev, u8 func, u8 reg) { return dkm_pci_read(bus, dev, func, reg); @@ -534,6 +536,17 @@ static int ahci_identify_port(u32 port, const struct dkm_dma_api *dma) { log_hex("[ahci] IDENTIFY word0=", id[0]); log_hex("[ahci] IDENTIFY word49=", id[49]); log_hex("[ahci] IDENTIFY word83=", id[83]); + /* 容量必须在 ahci_read_lba0 复用 data 缓冲区前提取, + * 否则 IDENTIFY 数据被 LBA0 内容覆盖后容量丢失。 */ + if ((id[83] & 0x0400u) && (id[103] | id[102] | id[101] | id[100])) { + /* LBA48: word100-103 小端 u16 序列拼接 */ + g_sector_count = (u64)id[100] | ((u64)id[101] << 16) + | ((u64)id[102] << 32) | ((u64)id[103] << 48); + } else { + /* LBA28: word60-61 */ + g_sector_count = (u64)id[60] | ((u64)id[61] << 16); + } + log_hex("[ahci] IDENTIFY sector_count=", g_sector_count); ahci_read_lba0(port, hdr, &table, &data); g_ready_port = port; g_cmd_header = hdr; @@ -678,7 +691,7 @@ int driver_init(const struct dkm_kernel_api *api, struct dkm_block_device_desc desc; desc.name = "ahci0"; desc.sector_size = 512; - desc.sector_count = 0; + desc.sector_count = g_sector_count; desc.ctx = 0; desc.read = ahci_block_read; desc.write = ahci_block_write; diff --git a/CODE/DKM/ehci/ehci.c b/CODE/DKM/ehci/ehci.c new file mode 100644 index 0000000..0dedb96 --- /dev/null +++ b/CODE/DKM/ehci/ehci.c @@ -0,0 +1,1169 @@ +/* DKM EHCI Driver — USB2 host controller + Mass Storage Class (BOT/SCSI) + * + * Stage 1 optional storage driver, depends on "pci", provides "block". + * Target: Intel 6/7-series EHCI (PCI class 0C/03/20, device 1C2D/1C26) + * and any standard EHCI controller — the USB2 path used on platforms + * without xHCI (e.g. H61/B75 with only EHCI controllers). + * + * Synchronous polling model (no IRQ): async list + QH/TD execution, + * transfer completion detected by polling the QH overlay token. + * + * Pipeline: + * 1. PCI discovery: class=0x0C/03/20 + * 2. MMIO mapping: BAR0 via mm_map_mmio / HHDM + * 3. HC init: HCRESET, port power, RUN + * 4. Port scan: reset, wait PED, read speed + * 5. Enumeration: SET_ADDRESS, GET_DESCRIPTOR(dev), SET_CONFIGURATION + * 6. MSC: BOT (CBW/CSW) + SCSI (INQUIRY/RD_CAP/READ10/WRITE10) + * 7. Block provider: register usb2 via kernel_api.block + */ + +#include "../dkm_shared.h" +#include "../dkm_instr.h" + +DKM_STAT_DECL(ehci_pci_found); +DKM_STAT_DECL(ehci_port_ok); +DKM_STAT_DECL(ehci_msc_found); +DKM_STAT_DECL(ehci_block_reads); +DKM_STAT_DECL(ehci_block_writes); + +static const char *const g_depends[] = { "pci" }; +static const char *const g_provides[] = { "block" }; + +__attribute__((visibility("default"), used)) +const struct dkm_driver_desc driver_desc = { + .magic = DKM_DRIVER_MAGIC, + .abi_version = DKM_ABI_VERSION, + .desc_size = sizeof(struct dkm_driver_desc), + .name = "ehci", + .version = "0.1.0", + .vendor = "Deshab", + .driver_class = 6, /* DKM_CLASS_STORAGE */ + .stage = 1, + .flags = 0, + .priority = 0, + .depends = g_depends, + .depends_count = 1, + .provides = g_provides, + .provides_count = 1, + .min_kernel_abi = 1, + .feature_bits = 0, + .reserved0 = 0, + .reserved1 = 0 +}; + +/* ================================================================ + * Constants — EHCI registers, QH/TD, USB protocol + * ================================================================ */ + +#define EHCI_CLASS_SERIAL 0x0C +#define EHCI_SUBCLASS_USB 0x03 +#define EHCI_PROGIF_EHCI 0x20 + +/* Capability registers (MMIO + 0x00) */ +#define EHCI_CAP_CAPLENGTH 0x00 +#define EHCI_CAP_HCSPARAMS 0x04 +#define EHCI_CAP_HCCPARAMS 0x08 + +/* Operational registers (MMIO + caplength) */ +#define EHCI_OP_USBCMD 0x00 +#define EHCI_OP_USBSTS 0x04 +#define EHCI_OP_USBINTR 0x08 +#define EHCI_OP_FRINDEX 0x0C +#define EHCI_OP_ASYNCLIST 0x18 +#define EHCI_OP_CONFIGFLAG 0x40 +#define EHCI_OP_PORTSC 0x44 +#define EHCI_PORT_STRIDE 0x04 + +/* USBCMD bits */ +#define USBCMD_RUN (1u << 0) +#define USBCMD_HCRESET (1u << 1) +#define USBCMD_PSE (1u << 4) +#define USBCMD_ASE (1u << 5) + +/* USBSTS bits */ +#define USBSTS_USBINT (1u << 0) +#define USBSTS_PCD (1u << 2) +#define USBSTS_FLR (1u << 3) +#define USBSTS_HSE (1u << 4) +#define USBSTS_IAA (1u << 6) +#define USBSTS_HCHALT (1u << 12) + +/* PORTSC bits — 与 QEMU include/hw/usb/ehci-regs.h / EHCI 1.0 spec 一致 + * BUG-FIX: 原定义位错(PED=bit1 应为 bit2,speed 在 LINESTAT bit10:11 而非 + * bit14:15,change 位是 bit1/3/5 而非 bit17/18/20/21)——端口永不使能、 + * 速度永远读 0,导致 HC 不执行该端口上的传输。 */ +#define PORTSC_CONNECT (1u << 0) /* CCS */ +#define PORTSC_CSC (1u << 1) /* Connect Status Change (W1C) */ +#define PORTSC_PED (1u << 2) /* Port Enable */ +#define PORTSC_PEDC (1u << 3) /* Port Enable/Disable Change (W1C) */ +#define PORTSC_OCA (1u << 4) /* Over-Current Active */ +#define PORTSC_OCC (1u << 5) /* Over-Current Change (W1C) */ +#define PORTSC_FPR (1u << 6) /* Force Port Resume */ +#define PORTSC_SUSP (1u << 7) /* Suspend */ +#define PORTSC_PR (1u << 8) /* Port Reset */ +#define PORTSC_LS_SHIFT 10 /* Line Status = 端口速度 (bit10:11) */ +#define PORTSC_LS_MASK 0x3 +#define PORTSC_PP (1u << 12) /* Port Power */ +#define PORTSC_POWNER (1u << 13) + +/* RWC 位(W1C):CSC|PEDC|OCC —— 清 change 时写这些位 */ +#define PORTSC_RWC (PORTSC_CSC | PORTSC_PEDC | PORTSC_OCC) + +/* PLS/LS 值(Line Status 字段,非 PLS 状态机) */ +#define LS_FULL 0 +#define LS_LOW 1 +#define LS_HIGH 2 + +/* TD token bits */ +#define TD_ACTIVE (1u << 7) +#define TD_HALTED (1u << 6) +#define TD_BUFERR (1u << 5) +#define TD_BABBLE (1u << 4) +#define TD_XACTERR (1u << 3) +#define TD_PID_SHIFT 8 +#define TD_PID_MASK 0x3 +#define TD_CERR_SHIFT 10 +#define TD_CERR_MASK 0x3 +#define TD_IOC (1u << 15) +#define TD_BYTES_SHIFT 16 +#define TD_BYTES_MASK 0x7FFF +#define TD_TOGGLE (1u << 31) + +#define PID_OUT 0 +#define PID_IN 1 +#define PID_SETUP 2 + +/* QH 链接:最低位 1 = 列表尾(T 位),0 = QH 指针 */ +#define QH_TERMINATE 0x1u + +/* USB speeds (EPCAP/QH ep speed 字段) */ +#define USB_SPEED_FULL 0 +#define USB_SPEED_LOW 1 +#define USB_SPEED_HIGH 2 + +/* BOT signatures */ +#define BOT_CBW_SIG 0x43425355u /* "USBC" */ +#define BOT_CSW_SIG 0x53425355u /* "USBS" */ + +/* SCSI opcodes */ +#define SCSI_TEST_UNIT_READY 0x00 +#define SCSI_INQUIRY 0x12 +#define SCSI_READ_CAPACITY10 0x25 +#define SCSI_READ10 0x28 +#define SCSI_WRITE10 0x2A + +/* ================================================================ + * Data structures + * ================================================================ */ + +/* EHCI Transfer Descriptor — 32 字节,32 字节对齐 */ +typedef struct { + u32 next; /* +0 */ + u32 alt_next; /* +4 */ + u32 token; /* +8 */ + u32 buf[5]; /* +12 */ +} ehci_td; + +/* EHCI Queue Head — 44 字节,32 字节对齐 + * 布局必须与 QEMU v11 EHCIqh / EHCI 1.0 spec 完全一致: + * +0 hlink(next), +4 epchar, +8 epcap(端点能力: hub addr+smask/cmask), + * +12 current_qtd, +16 next_qtd(overlay), +20 altnext(overlay), +24 token(overlay), + * +28 buf[5] + * BUG-FIX(关键): epcap 字段不能删!QEMU 从 +16 读 next_qtd、+20 读 altnext、 + * +24 读 token。删掉 epcap 使 overlay 错位 4 字节——驱动写的 ov_next 落到 + * current_qtd 位、ov_alt(0x1=T) 落到 next_qtd 位 → QEMU 认为 QH 无 TD + * 永不 FETCHQTD(token 恒 ACTIVE 0x80280)。 */ +typedef struct { + u32 hlink; /* +0 */ + u32 epchar; /* +4 */ + u32 epcap; /* +8 */ + u32 curqtd; /* +12 */ + u32 ov_next; /* +16 overlay.next_qtd */ + u32 ov_alt; /* +20 overlay.altnext */ + u32 ov_token; /* +24 overlay.token */ + u32 ov_buf[5]; /* +28 */ +} ehci_qh; + +/* MSC device state */ +typedef struct { + u8 dev_addr; + u8 speed; + u8 bulk_out_ep; /* endpoint number (1..15) */ + u8 bulk_in_ep; + u16 bulk_out_mps; + u16 bulk_in_mps; + u32 cbw_tag; + u64 sector_count; + u32 sector_size; + int ready; +} ehci_msc_dev; + +/* ================================================================ + * Global state + * ================================================================ */ + +static const struct dkm_kernel_api *g_api; +static const struct dkm_log_api *g_log; +static volatile u8 *g_mmio; /* EHCI BAR0 virtual */ +static u32 g_caplen; +static u32 g_nports; +static u32 g_64bit; /* HCCPARAMS bit0 */ +static u32 g_ppc; /* per-port power control bit4 */ + +/* DMA:异步列表区(QH/TD 池)+ 数据缓冲 + CSW 缓冲 */ +static struct dkm_dma_buffer g_qh_dma; /* 4KB:QH×4 + TD×8 */ +static struct dkm_dma_buffer g_xfer; /* 数据 bounce buffer(>=64KB) */ +static struct dkm_dma_buffer g_csw; /* CSW 独立缓冲(防覆盖数据区前 13 字节) */ +static ehci_qh *g_qh_ctrl; +static ehci_qh *g_qh_bulk_out; +static ehci_qh *g_qh_bulk_in; +static ehci_td *g_td_pool; +static u32 g_td_next; + +static ehci_msc_dev g_msc; +static u8 g_pci_bus, g_pci_dev, g_pci_func; + +/* ================================================================ + * Utility + * ================================================================ */ + +static void log_hex(const char *prefix, u64 value) { + static const char hex[] = "0123456789abcdef"; + char buf[19]; + u32 pos = 0; + buf[pos++] = '0'; + buf[pos++] = 'x'; + for (int i = 15; i >= 0; i--) + buf[pos++] = hex[(value >> (i * 4)) & 0xf]; + buf[pos] = 0; + g_log->info(prefix); + g_log->info(buf); +} + +static void ehci_zero(void *ptr, u32 len) { + u8 *p = (u8 *)ptr; + for (u32 i = 0; i < len; i++) p[i] = 0; +} + +static void ehci_memcpy(void *dst, const void *src, u32 len) { + u8 *d = (u8 *)dst; + const u8 *s = (const u8 *)src; + for (u32 i = 0; i < len; i++) d[i] = s[i]; +} + +static u32 mmio_read32(u32 off) { + return *(volatile u32 *)(void *)(g_mmio + off); +} + +static void mmio_write32(u32 off, u32 value) { + *(volatile u32 *)(void *)(g_mmio + off) = value; +} + +static void ehci_mb(void) { + __asm__ volatile("" ::: "memory"); +} + +/* 把物理地址编码进 32 位链接字段(64 位控制器用 CTRLDSSEGMENT 高 32 位) */ +/* (内联使用,见 qh_submit_control/qh_enqueue) */ + +/* ================================================================ + * Phase 1: PCI discovery + MMIO mapping + * ================================================================ */ + +static int ehci_find(u8 *out_bus, u8 *out_dev, u8 *out_func) { + for (u16 bus = 0; bus < 256; bus++) { + for (u8 dev = 0; dev < 32; dev++) { + u32 vd = dkm_pci_read((u8)bus, dev, 0, PCI_VENDOR_ID); + if ((vd & 0xffff) == 0xffff) continue; + u8 header = (u8)(dkm_pci_read((u8)bus, dev, 0, PCI_HEADER) >> 16); + u8 func_count = (header & 0x80) ? 8 : 1; + for (u8 func = 0; func < func_count; func++) { + u32 vd2 = dkm_pci_read((u8)bus, dev, func, PCI_VENDOR_ID); + if ((vd2 & 0xffff) == 0xffff) continue; + u32 class_reg = dkm_pci_read((u8)bus, dev, func, 0x08); + u8 prog_if = (u8)((class_reg >> 8) & 0xff); + u8 subclass = (u8)((class_reg >> 16) & 0xff); + u8 class_code = (u8)((class_reg >> 24) & 0xff); + if (class_code == EHCI_CLASS_SERIAL && + subclass == EHCI_SUBCLASS_USB && + prog_if == EHCI_PROGIF_EHCI) { + *out_bus = (u8)bus; + *out_dev = dev; + *out_func = func; + return 0; + } + } + } + } + return -1; +} + +static volatile u8 *ehci_map_bar(u64 bar_phys) { + u64 bar_virt = g_api->hhdm_offset + bar_phys; + if (g_api->mm_map_mmio) { + void *mapped = g_api->mm_map_mmio(bar_phys, 0x1000); + if (mapped) { + g_log->info("[ehci] BAR mapped via mm_map_mmio"); + return (volatile u8 *)mapped; + } + } + if (g_api->mmio && g_api->mmio->is_mapped) { + if (g_api->mmio->is_mapped(bar_virt) > 0) { + g_log->info("[ehci] HHDM covers BAR"); + return (volatile u8 *)(uintptr_t)bar_virt; + } + if (g_api->mmio->map_mmio) { + if (g_api->mmio->map_mmio(bar_phys, 0x1000) == 0 && + g_api->mmio->is_mapped(bar_virt) > 0) { + g_log->info("[ehci] BAR mapped via page tables"); + return (volatile u8 *)(uintptr_t)bar_virt; + } + } + } + if (bar_phys < 0x100000000ULL) { + g_log->info("[ehci] assuming HHDM covers low BAR"); + return (volatile u8 *)(uintptr_t)bar_virt; + } + g_log->error("[ehci] BAR MMIO unavailable"); + return 0; +} + +/* ================================================================ + * Phase 1: HC reset and start + * ================================================================ */ + +static int ehci_hc_reset(void) { + /* 等待 HCHALT=1(Halted 时才能写 HCRESET) */ + u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 100; + while (dkm_rdtsc() < deadline) { + if (mmio_read32(g_caplen + EHCI_OP_USBSTS) & USBSTS_HCHALT) break; + __asm__ volatile("pause"); + } + + mmio_write32(g_caplen + EHCI_OP_USBCMD, USBCMD_HCRESET); + deadline = dkm_rdtsc() + dkm_tsc_per_ms * 100; + while (dkm_rdtsc() < deadline) { + if (!(mmio_read32(g_caplen + EHCI_OP_USBCMD) & USBCMD_HCRESET)) { + g_log->info("[ehci] HC reset complete"); + return 0; + } + __asm__ volatile("pause"); + } + g_log->error("[ehci] HC reset timeout"); + return -1; +} + +/* ================================================================ + * Phase 2: QH/TD management + * ================================================================ */ + +static ehci_td *td_alloc(void) { + if (g_td_next >= 8) return 0; + ehci_td *td = &g_td_pool[g_td_next++]; + ehci_zero(td, sizeof(ehci_td)); + td->next = QH_TERMINATE; + td->alt_next = QH_TERMINATE; + return td; +} + +/* 初始化一个 QH:endpoint=0 控制 / 指定端点 bulk。 + * 注意:不能动 hlink —— ehci_hc_start 在启动时已把 ctrl/out/in 三个 QH + * 链成环;每次提交只更新 epchar/overlay,hlink 必须保留(否则环断裂, + * HC 停止遍历,overlay token 恒 0 → qh_wait 假成功返回空数据)。 */ +static void qh_init(ehci_qh *qh, u8 dev_addr, u8 ep_num, u8 speed, + u16 mps, int control) { + qh->curqtd = 0; + qh->ov_next = QH_TERMINATE; + qh->ov_alt = QH_TERMINATE; + qh->ov_token = 0; /* inactive */ + + u32 epchar = (u32)dev_addr; /* [6:0] */ + epchar |= ((u32)ep_num << 8); /* [11:8] */ + epchar |= ((u32)speed << 12); /* [14:12] */ + epchar |= ((u32)mps << 16); /* [26:16] */ + if (control) epchar |= (1u << 27); /* control endpoint */ + /* DTC 清 0:由 HC 管理 data toggle。 + * H 位(bit15) 保留:ctrl 是环头 QH,每次提交重建 epchar 后必须 + * 重新置 H,否则 QEMU WAITLISTHEAD 找不到入口(只在 H=1 的 QH + * 处进入 FETCHENTRY)。 */ + u32 keep_h = qh->epchar & (1u << 15); + qh->epchar = epchar | keep_h; + /* epcap(端点能力):直连根端口 hub addr=0, smask/cmask=0。 + * 该字段必须存在且清零——QEMU 依赖它使 overlay 偏移正确。 */ + qh->epcap = 0; +} + +/* 构建控制传输 TD 链:SETUP -> [DATA] -> STATUS,挂到 qh。 */ +static int qh_submit_control(ehci_qh *qh, const u8 *setup8, + u8 *data, u32 data_len, u8 dir) { + /* 清空旧链:重置 overlay */ + qh->curqtd = 0; + qh->ov_next = QH_TERMINATE; + qh->ov_token = 0; + g_td_next = 0; /* 复用池(单传输假设) */ + + ehci_td *t_setup = td_alloc(); + ehci_td *t_data = 0; + ehci_td *t_status = td_alloc(); + if (!t_setup || !t_status) return -1; + if (data_len > 0) { + t_data = td_alloc(); + if (!t_data) return -2; + } + + /* SETUP TD:8 字节,toggle=0,pid=SETUP + * 注意:必须先分配 t_data 再算 t_setup->next(原实现引用未分配 + * 的 t_data → TD 链指向垃圾地址 → HC 走链崩溃/超时)。 */ + if (data_len > 0) { + t_setup->next = (u32)(g_qh_dma.phys + (u64)((u8 *)t_data - (u8 *)g_qh_dma.virt)); + } else { + t_setup->next = (u32)(g_qh_dma.phys + (u64)((u8 *)t_status - (u8 *)g_qh_dma.virt)); + } + /* setup 数据嵌入 QH overlay buffer?标准做法:SETUP TD buffer 指向含 8 字节 setup 的内存 */ + /* 简化:setup 包拷贝到 xfer 缓冲头部,TD buffer 指向它 */ + { + u8 *sb = (u8 *)g_xfer.virt; + ehci_memcpy(sb, setup8, 8); + __asm__ volatile("wbinvd" ::: "memory"); + t_setup->buf[0] = (u32)(g_xfer.phys & 0xFFFFFFFFu); + u32 tok = TD_ACTIVE | (PID_SETUP << TD_PID_SHIFT) | (8u << TD_BYTES_SHIFT); + t_setup->token = tok; /* toggle=0 */ + } + + /* DATA TD(可选)——t_data 已在上方分配,此处不再重复分配 */ + if (data_len > 0 && t_data) { + /* 数据在 g_xfer.virt + 8 偏移处(IN:设备写这里;OUT:软件先拷入) */ + u8 *db = (u8 *)g_xfer.virt + 8; + if (dir == PID_OUT) { + ehci_memcpy(db, data, data_len); + __asm__ volatile("wbinvd" ::: "memory"); + } + t_data->buf[0] = (u32)((g_xfer.phys + 8) & 0xFFFFFFFFu); + t_data->next = (u32)(g_qh_dma.phys + (u64)((u8 *)t_status - (u8 *)g_qh_dma.virt)); + u32 tok = TD_ACTIVE | ((u32)dir << TD_PID_SHIFT) | ((u32)data_len << TD_BYTES_SHIFT) | TD_TOGGLE; + t_data->token = tok; /* toggle=1 */ + } + + /* STATUS TD:反向 pid,toggle=1,长度 0 */ + { + u8 spid = (dir == PID_IN) ? PID_OUT : PID_IN; + if (data_len == 0) spid = (dir == PID_IN) ? PID_OUT : PID_IN; + u32 tok = TD_ACTIVE | ((u32)spid << TD_PID_SHIFT) | TD_TOGGLE; + t_status->token = tok; + t_status->next = QH_TERMINATE; + } + + /* QH overlay 指向第一个 TD */ + qh->ov_next = (u32)(g_qh_dma.phys + (u64)((u8 *)t_setup - (u8 *)g_qh_dma.virt)); + /* 写完 QH/TD 后刷新 CPU cache——DMA 控制器读物理内存需要; + * 缺此步在 WHPX 下 QEMU 读不到 ov_next(竞态),TCG 下侥幸通过。 */ + __asm__ volatile("wbinvd" ::: "memory"); + ehci_mb(); + return 0; +} + +/* 构建 bulk 传输单 TD:dir 0=OUT 1=IN。 + * in_dma: IN 方向的目标 DMA 缓冲(数据用 g_xfer,CSW 用 g_csw—— + * 防止 CSW 读覆盖数据区前 13 字节)。OUT 方向 in_dma 忽略(数据先拷入 + * in_dma 指向的缓冲)。 */ +static int qh_submit_bulk(ehci_qh *qh, u8 dir, const void *data, u32 data_len, + struct dkm_dma_buffer *in_dma) { + qh->curqtd = 0; + qh->ov_next = QH_TERMINATE; + qh->ov_token = 0; + g_td_next = 0; + + ehci_td *td = td_alloc(); + if (!td) return -1; + + struct dkm_dma_buffer *dst = in_dma ? in_dma : &g_xfer; + u8 *buf = (u8 *)dst->virt; + if (dir == PID_OUT) { + ehci_memcpy(buf, data, data_len); + __asm__ volatile("wbinvd" ::: "memory"); + } + td->buf[0] = (u32)(dst->phys & 0xFFFFFFFFu); + u32 tok = TD_ACTIVE | ((u32)dir << TD_PID_SHIFT) | ((u32)data_len << TD_BYTES_SHIFT); + td->token = tok; /* toggle 由 HC 管理(DTC=0) */ + + qh->ov_next = (u32)(g_qh_dma.phys + (u64)((u8 *)td - (u8 *)g_qh_dma.virt)); + __asm__ volatile("wbinvd" ::: "memory"); + ehci_mb(); + return 0; +} + +/* 等待 QH 上的传输完成。轮询首个 TD 的 token(HC 执行后清 ACTIVE、 + * 出错置 HALTED)——比检查 overlay 可靠:HC 未执行时 overlay token + * 保持 inactive,轮询 overlay 会假成功返回空数据。first_td 传 TD 池指针, + * 由调用方从 qh->ov_next 反推或直接记录。 + * BUG-FIX(缓存一致性):EHCI HC 经 DMA 写 TD 到物理内存,CPU 轮询前 + * 必须失效缓存行,否则读到 write-back cache 旧值(Active 恒 1)→ 假超时。 + * QEMU 复现证据:first_td token=0x80280 显示 SETUP 已执行成功(Active 清、 + * 无错误位),但轮询超时——正是 cache 陈旧。 */ +static int qh_wait(ehci_qh *qh, ehci_td *first_td, u32 timeout_ms) { + (void)qh; + u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * timeout_ms; + for (;;) { + /* 失效 TD 所在缓存行,强制从内存读 HC 的 DMA 写结果 */ + __asm__ volatile("clflush (%0)" : : "r"(first_td) : "memory"); + u32 tok = first_td->token; + if (!(tok & TD_ACTIVE)) { + if (tok & TD_HALTED) { + g_log->warn("[ehci] transfer halted"); + log_hex("[ehci] token=", tok); + return -2; + } + return 0; + } + if (dkm_rdtsc() > deadline) { + g_log->warn("[ehci] transfer timeout"); + log_hex("[ehci] first_td token=", tok); + return -1; + } + __asm__ volatile("pause"); + } +} + +/* 启动 HC:把三个 QH 链成环(ctrl→bulk_out→bulk_in→ctrl), + * 在 ASE=0(async schedule 停止)时写一次 ASYNCLIST,然后 RUN|ASE。 + * 之后每次提交只改 QH overlay,不再碰 ASYNCLIST(规范要求该寄存器 + * 只能在 async schedule 停止时写——QEMU 会拒绝运行期写入)。 */ +static int ehci_hc_start(void) { + u64 ctrl_phys = g_qh_dma.phys + (u64)((u8 *)g_qh_ctrl - (u8 *)g_qh_dma.virt); + u64 out_phys = g_qh_dma.phys + (u64)((u8 *)g_qh_bulk_out - (u8 *)g_qh_dma.virt); + u64 in_phys = g_qh_dma.phys + (u64)((u8 *)g_qh_bulk_in - (u8 *)g_qh_dma.virt); + + /* hlink 链接指针必须带类型位 bit1=1(QH 类型)。 + * QEMU v11 的 NLPTR_TYPE_GET(x)=((x)>>1)&3,QH 类型=1 → 写物理地址|2。 + * BUG-FIX: 裸地址(bit1=0)→ QEMU fetchentry 报 "non queue head + * request in async schedule" 并重置 HC,TD 永不执行。 */ + g_qh_ctrl->hlink = (u32)((out_phys & 0xFFFFFFFFu) | 2u); + g_qh_bulk_out->hlink = (u32)((in_phys & 0xFFFFFFFFu) | 2u); + g_qh_bulk_in->hlink = (u32)((ctrl_phys & 0xFFFFFFFFu) | 2u); + /* H 位(bit15) = 回收头标记:QEMU 的 WAITLISTHEAD 状态只在环中找到 + * H=1 的 QH 时进入 FETCHENTRY→FETCHQH。不置 H 位 → 状态机永远停在 + * WAITLISTHEAD,TD 永不执行。ctrl 作为环头必须置 H。 */ + g_qh_ctrl->epchar |= (1u << 15); + ehci_mb(); + + /* 确保 ASE=0(复位后默认 0) */ + u32 cmd = mmio_read32(g_caplen + EHCI_OP_USBCMD); + mmio_write32(g_caplen + EHCI_OP_USBCMD, cmd & ~USBCMD_ASE); + ehci_mb(); + mmio_write32(g_caplen + EHCI_OP_ASYNCLIST, (u32)((ctrl_phys & 0xFFFFFFFFu) | 2u)); + ehci_mb(); + mmio_write32(g_caplen + EHCI_OP_USBCMD, + mmio_read32(g_caplen + EHCI_OP_USBCMD) | USBCMD_ASE | USBCMD_RUN); + ehci_mb(); + g_log->info("[ehci] async schedule started"); + return 0; +} + +/* 简单控制传输封装 */ +static int ehci_control(u8 dev_addr, u8 speed, u16 mps0, + const u8 *setup8, u8 *data, u32 data_len, u8 dir) { + qh_init(g_qh_ctrl, dev_addr, 0, speed, mps0, 1); + if (qh_submit_control(g_qh_ctrl, setup8, data, data_len, dir) != 0) return -1; + int rc = qh_wait(g_qh_ctrl, &g_td_pool[0], 5000); + /* IN 方向:失效数据缓冲缓存行后把 HC DMA 写入的数据拷回调用方 */ + if (rc == 0 && dir == PID_IN && data && data_len > 0) { + __asm__ volatile("clflush (%0)" : : "r"(g_xfer.virt) : "memory"); + __asm__ volatile("clflush 64(%0)" : : "r"(g_xfer.virt) : "memory"); + ehci_memcpy(data, (u8 *)g_xfer.virt + 8, data_len); + } + return rc; +} + +/* 简单 bulk 传输封装(端点号已含方向编码在 QH)。 + * in_dma: IN 方向的目标 DMA 缓冲(g_xfer 或 g_csw)。 + * BUG-FIX: CSW 必须用独立缓冲——若与数据共用 g_xfer,13 字节 CSW 会 + * 覆盖数据区前 13 字节(bps 字段被清零)。 */ +static int ehci_bulk(ehci_qh *qh, u8 dev_addr, u8 speed, u16 mps, + u8 ep_num, u8 dir, const void *data, u32 data_len, + struct dkm_dma_buffer *in_dma) { + qh_init(qh, dev_addr, ep_num, speed, mps, 0); + if (qh_submit_bulk(qh, dir, data, data_len, in_dma) != 0) return -1; + int rc = qh_wait(qh, &g_td_pool[0], 10000); + if (rc == 0 && dir == PID_IN) { + struct dkm_dma_buffer *dst = in_dma ? in_dma : &g_xfer; + /* 失效缓存行后拷贝 */ + __asm__ volatile("clflush (%0)" : : "r"(dst->virt) : "memory"); + ehci_memcpy((void *)data, dst->virt, data_len); + } + return rc; +} + +/* ================================================================ + * Phase 3: Port scan and reset + * ================================================================ */ + +static int ehci_reset_port(u32 port) { + u32 po = g_caplen + EHCI_OP_PORTSC + port * EHCI_PORT_STRIDE; + u32 ps = mmio_read32(po); + if (!(ps & PORTSC_CONNECT)) return -1; + + /* 复位序列(EHCI 1.0 §4.2.4 + QEMU 模型兼容): + * 1) 置 PR=1 请求复位 + * 2) 保持 PR=1 一小段时间(QEMU 不置 PRC,等待只是给模拟器处理窗口) + * 3) 清 PR —— QEMU 在 PR 1→0 下降沿执行 usb_port_reset 并置 PED(仅高速) + * 4) 轮询 PED 确认使能 + * BUG-FIX: 原实现等待 PRC(bit20),但 QEMU 从不置该位 → 200ms 必超时 + * 枚举中止(QEMU hcd-ehci.c ehci_port_write 只处理 PR 下降沿)。 */ + mmio_write32(po, ps | PORTSC_PR); + dkm_delay_ms(50); + + /* 清 PR + 清 change 位(1→0 下降沿触发复位) */ + ps = mmio_read32(po); + mmio_write32(po, (ps & ~PORTSC_PR) | PORTSC_RWC); + + /* 轮询 PED */ + u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 200; + while (dkm_rdtsc() < deadline) { + ps = mmio_read32(po); + if (ps & PORTSC_PED) { + DKM_STAT_INC(ehci_port_ok); + return 0; + } + if (!(ps & PORTSC_CONNECT)) return -2; /* 复位中断开 */ + __asm__ volatile("pause"); + } + g_log->warn("[ehci] port not enabled after reset"); + return -3; +} + +static u8 ehci_port_speed(u32 port) { + u32 po = g_caplen + EHCI_OP_PORTSC + port * EHCI_PORT_STRIDE; + u32 ps = mmio_read32(po); + return (u8)((ps >> PORTSC_LS_SHIFT) & PORTSC_LS_MASK); +} + +/* ================================================================ + * Phase 4: USB enumeration + * ================================================================ */ + +/* 前置声明(BOT/SCSI 定义在 enum_msc 之后) */ +static int ehci_scsi_inquiry(void); +static int ehci_bot_send_cbw(u8 lun, u8 flags, u32 data_len, + const u8 *cb, u8 cb_len); +static int ehci_bot_receive_csw(void); + +static int ehci_get_device_descriptor(u8 *out, u32 len) { + u8 setup[8]; + setup[0] = 0x80; setup[1] = 0x06; + setup[2] = 0x00; setup[3] = 0x01; /* Device descriptor */ + setup[4] = 0x00; setup[5] = 0x00; + setup[6] = (u8)(len & 0xFF); setup[7] = (u8)((len >> 8) & 0xFF); + ehci_zero(out, len); + /* GET_DESCRIPTOR 阶段 EP0 MPS 未知,按速度用默认值 */ + u16 mps0 = 64; + if (g_msc.speed == LS_LOW) mps0 = 8; + return ehci_control(g_msc.dev_addr, g_msc.speed, mps0, setup, out, len, PID_IN); +} + +static int ehci_set_address(u8 addr, u16 mps0) { + u8 setup[8]; + setup[0] = 0x00; setup[1] = 0x05; + setup[2] = (u8)(addr & 0xFF); setup[3] = 0x00; + setup[4] = 0x00; setup[5] = 0x00; + setup[6] = 0x00; setup[7] = 0x00; + return ehci_control(0, g_msc.speed, mps0, setup, 0, 0, PID_OUT); +} + +static int ehci_set_configuration(u8 addr, u8 cfg, u16 mps0) { + u8 setup[8]; + setup[0] = 0x00; setup[1] = 0x09; + setup[2] = cfg; setup[3] = 0x00; + setup[4] = 0x00; setup[5] = 0x00; + setup[6] = 0x00; setup[7] = 0x00; + return ehci_control(addr, g_msc.speed, mps0, setup, 0, 0, PID_OUT); +} + +static int ehci_get_config_descriptor(u8 addr, u8 *out, u32 len) { + u8 setup[8]; + setup[0] = 0x80; setup[1] = 0x06; + setup[2] = 0x00; setup[3] = 0x02; /* Configuration descriptor */ + setup[4] = 0x00; setup[5] = 0x00; + setup[6] = (u8)(len & 0xFF); setup[7] = (u8)((len >> 8) & 0xFF); + ehci_zero(out, len); + return ehci_control(addr, g_msc.speed, 64, setup, out, len, PID_IN); +} + +/* 枚举 MSC 设备:SET_ADDRESS -> GET_DESCRIPTOR -> SET_CONFIGURATION -> 端点解析 */ +static int ehci_enum_msc(u32 port) { + g_msc.speed = ehci_port_speed(port); + log_hex("[ehci] port speed=", g_msc.speed); + u16 mps0 = 64; /* 全速/高速 EP0 默认 64 */ + if (g_msc.speed == LS_LOW) mps0 = 8; + + /* SET_ADDRESS(用默认 pipe,mps 按速度) */ + if (ehci_set_address(1, mps0) != 0) { + g_log->warn("[ehci] SET_ADDRESS failed"); + return -1; + } + g_msc.dev_addr = 1; + g_msc.bulk_out_mps = mps0; /* 后续控制传输用已知 mps0 */ + + /* GET_DESCRIPTOR(device, 18) */ + u8 dev_desc[18]; + if (ehci_get_device_descriptor(dev_desc, 18) != 0) { + g_log->warn("[ehci] GET_DESCRIPTOR(Device) failed"); + return -2; + } + u8 dev_class = dev_desc[4]; + u16 vid = (u16)dev_desc[8] | ((u16)dev_desc[9] << 8); + u16 pid = (u16)dev_desc[10] | ((u16)dev_desc[11] << 8); + u8 real_mps0 = dev_desc[7]; + log_hex("[ehci] dev class=", dev_class); + log_hex("[ehci] VID=", vid); + log_hex("[ehci] PID=", pid); + log_hex("[ehci] bMaxPacketSize0=", real_mps0); + /* 设备声明的真实 EP0 MPS 与猜测不同时,用真实值 */ + if (real_mps0 >= 8 && real_mps0 <= 64) mps0 = real_mps0; + + /* SET_CONFIGURATION(1) */ + if (ehci_set_configuration(g_msc.dev_addr, 1, mps0) != 0) { + g_log->warn("[ehci] SET_CONFIGURATION failed"); + return -3; + } + + /* GET_DESCRIPTOR(config, 255) 并解析 MSC 接口 + bulk 端点 */ + u8 cfg_desc[255]; + if (ehci_get_config_descriptor(g_msc.dev_addr, cfg_desc, 255) != 0) { + g_log->warn("[ehci] GET_DESCRIPTOR(Config) failed"); + return -4; + } + u16 total_len = (u16)cfg_desc[2] | ((u16)cfg_desc[3] << 8); + u8 bulk_out_ep = 0, bulk_in_ep = 0; + u16 bulk_out_mps = 512, bulk_in_mps = 512; + int msc_found = 0; + + u32 pos = 9; /* 跳过 config descriptor */ + while (pos + 2 <= (u32)total_len && pos + cfg_desc[pos] <= (u32)total_len) { + u8 desc_len = cfg_desc[pos]; + u8 desc_type = cfg_desc[pos + 1]; + if (desc_type == 4 && desc_len >= 9) { + u8 iface_class = cfg_desc[pos + 5]; + u8 iface_sub = cfg_desc[pos + 6]; + u8 iface_proto = cfg_desc[pos + 7]; + if (iface_class == 0x08 && iface_sub == 0x06 && iface_proto == 0x50) { + msc_found = 1; + g_log->info("[ehci] MSC BOT interface found"); + u32 ep_pos = pos + desc_len; + while (ep_pos + 2 <= (u32)total_len) { + u8 ep_len = cfg_desc[ep_pos]; + if (ep_len == 0 || ep_pos + ep_len > (u32)total_len) break; + if (cfg_desc[ep_pos + 1] == 5) { + u8 ep_addr = cfg_desc[ep_pos + 2]; + u8 ep_attr = cfg_desc[ep_pos + 3]; + u16 ep_mps = (u16)cfg_desc[ep_pos + 4] | ((u16)cfg_desc[ep_pos + 5] << 8); + if ((ep_attr & 0x03) == 0x02) { + if (ep_addr & 0x80) { + bulk_in_ep = ep_addr & 0x0F; + bulk_in_mps = ep_mps; + } else { + bulk_out_ep = ep_addr & 0x0F; + bulk_out_mps = ep_mps; + } + } + } else if (cfg_desc[ep_pos + 1] == 4) { + break; + } + ep_pos += ep_len; + } + break; + } + } + pos += desc_len; + } + + if (!msc_found || bulk_out_ep == 0 || bulk_in_ep == 0) { + g_log->warn("[ehci] no MSC BOT interface found"); + return -5; + } + log_hex("[ehci] Bulk OUT EP=", bulk_out_ep); + log_hex("[ehci] Bulk IN EP=", bulk_in_ep); + log_hex("[ehci] Bulk OUT MPS=", bulk_out_mps); + log_hex("[ehci] Bulk IN MPS=", bulk_in_mps); + + g_msc.bulk_out_ep = bulk_out_ep; + g_msc.bulk_in_ep = bulk_in_ep; + g_msc.bulk_out_mps = bulk_out_mps; + g_msc.bulk_in_mps = bulk_in_mps; + g_msc.cbw_tag = 1; + + /* INQUIRY 验证设备是块设备 */ + if (ehci_scsi_inquiry() != 0) { + g_log->warn("[ehci] INQUIRY failed or not block device"); + } + return 0; +} + +/* ================================================================ + * Phase 5: BOT + SCSI(复用 xhci 已验证的 CBW/CSW/SCSI 组包逻辑) + * ================================================================ */ + +static int ehci_bot_send_cbw(u8 lun, u8 flags, u32 data_len, + const u8 *cb, u8 cb_len) { + u8 cbw[31]; + ehci_zero(cbw, 31); + cbw[0] = 0x55; cbw[1] = 0x53; cbw[2] = 0x42; cbw[3] = 0x43; + u32 tag = g_msc.cbw_tag++; + cbw[4] = (u8)(tag & 0xFF); + cbw[5] = (u8)((tag >> 8) & 0xFF); + cbw[6] = (u8)((tag >> 16) & 0xFF); + cbw[7] = (u8)((tag >> 24) & 0xFF); + cbw[8] = (u8)(data_len & 0xFF); + cbw[9] = (u8)((data_len >> 8) & 0xFF); + cbw[10] = (u8)((data_len >> 16) & 0xFF); + cbw[11] = (u8)((data_len >> 24) & 0xFF); + cbw[12] = flags; + cbw[13] = lun; + cbw[14] = cb_len; + for (u8 i = 0; i < cb_len && i < 16; i++) cbw[15 + i] = cb[i]; + + return ehci_bulk(g_qh_bulk_out, g_msc.dev_addr, g_msc.speed, + g_msc.bulk_out_mps, g_msc.bulk_out_ep, PID_OUT, cbw, 31, 0); +} + +static int ehci_bot_receive_csw(void) { + u8 csw[13]; + /* CSW 用独立 DMA 缓冲,避免覆盖数据阶段读入 g_xfer 的数据 */ + int rc = ehci_bulk(g_qh_bulk_in, g_msc.dev_addr, g_msc.speed, + g_msc.bulk_in_mps, g_msc.bulk_in_ep, PID_IN, csw, 13, &g_csw); + if (rc != 0) return rc; + if (csw[0] != 0x55 || csw[1] != 0x53 || csw[2] != 0x42 || csw[3] != 0x53) { + g_log->error("[ehci] CSW signature mismatch"); + return -2; + } + if (csw[12] != 0) { + log_hex("[ehci] CSW status=", csw[12]); + return -3; + } + return 0; +} + +static int ehci_scsi_inquiry(void) { + u8 cb[16]; + ehci_zero(cb, 16); + cb[0] = SCSI_INQUIRY; + cb[4] = 36; + if (ehci_bot_send_cbw(0, 0x80, 36, cb, 6) != 0) return -1; + u8 inq[36]; + if (ehci_bulk(g_qh_bulk_in, g_msc.dev_addr, g_msc.speed, + g_msc.bulk_in_mps, g_msc.bulk_in_ep, PID_IN, inq, 36, &g_xfer) != 0) return -2; + if (ehci_bot_receive_csw() != 0) return -3; + log_hex("[ehci] INQ PeripheralType=", inq[0] & 0x1F); + return ((inq[0] & 0x1F) == 0) ? 0 : -4; +} + +static int ehci_scsi_read_capacity(u64 *out_sectors, u32 *out_blk) { + u8 cb[16]; + ehci_zero(cb, 16); + cb[0] = SCSI_READ_CAPACITY10; + if (ehci_bot_send_cbw(0, 0x80, 8, cb, 10) != 0) return -1; + u8 cap[8]; + if (ehci_bulk(g_qh_bulk_in, g_msc.dev_addr, g_msc.speed, + g_msc.bulk_in_mps, g_msc.bulk_in_ep, PID_IN, cap, 8, &g_xfer) != 0) return -2; + if (ehci_bot_receive_csw() != 0) return -3; + u32 max_lba = ((u32)cap[0] << 24) | ((u32)cap[1] << 16) | ((u32)cap[2] << 8) | (u32)cap[3]; + u32 blk = ((u32)cap[4] << 24) | ((u32)cap[5] << 16) | ((u32)cap[6] << 8) | (u32)cap[7]; + *out_sectors = (u64)max_lba + 1; + *out_blk = blk; + log_hex("[ehci] capacity sectors=", *out_sectors); + log_hex("[ehci] block size=", blk); + return 0; +} + +static int ehci_scsi_test_unit_ready(void) { + u8 cb[16]; + ehci_zero(cb, 16); + cb[0] = SCSI_TEST_UNIT_READY; + if (ehci_bot_send_cbw(0, 0x00, 0, cb, 6) != 0) return -1; + return ehci_bot_receive_csw(); +} + +static int ehci_scsi_read10(u64 lba, u32 count, void *buffer) { + u8 cb[16]; + ehci_zero(cb, 16); + cb[0] = SCSI_READ10; + cb[2] = (u8)((lba >> 24) & 0xFF); + cb[3] = (u8)((lba >> 16) & 0xFF); + cb[4] = (u8)((lba >> 8) & 0xFF); + cb[5] = (u8)(lba & 0xFF); + cb[7] = (u8)((count >> 8) & 0xFF); + cb[8] = (u8)(count & 0xFF); + u32 data_len = count * 512; + if (ehci_bot_send_cbw(0, 0x80, data_len, cb, 10) != 0) return -1; + if (ehci_bulk(g_qh_bulk_in, g_msc.dev_addr, g_msc.speed, + g_msc.bulk_in_mps, g_msc.bulk_in_ep, PID_IN, buffer, data_len, &g_xfer) != 0) return -2; + return ehci_bot_receive_csw(); +} + +static int ehci_scsi_write10(u64 lba, u32 count, const void *buffer) { + u8 cb[16]; + ehci_zero(cb, 16); + cb[0] = SCSI_WRITE10; + cb[2] = (u8)((lba >> 24) & 0xFF); + cb[3] = (u8)((lba >> 16) & 0xFF); + cb[4] = (u8)((lba >> 8) & 0xFF); + cb[5] = (u8)(lba & 0xFF); + cb[7] = (u8)((count >> 8) & 0xFF); + cb[8] = (u8)(count & 0xFF); + u32 data_len = count * 512; + if (ehci_bot_send_cbw(0, 0x00, data_len, cb, 10) != 0) return -1; + if (ehci_bulk(g_qh_bulk_out, g_msc.dev_addr, g_msc.speed, + g_msc.bulk_out_mps, g_msc.bulk_out_ep, PID_OUT, buffer, data_len, 0) != 0) return -2; + return ehci_bot_receive_csw(); +} + +/* ================================================================ + * Block provider + * ================================================================ */ + +static int ehci_block_read(void *ctx, u64 lba, u32 count, void *buffer) { + (void)ctx; + if (!g_msc.ready || !buffer || count == 0) return -1; + DKM_STAT_INC(ehci_block_reads); + u8 *buf = (u8 *)buffer; + while (count > 0) { + u32 chunk = count > 16 ? 16 : count; + if (ehci_scsi_read10(lba, chunk, g_xfer.virt) != 0) { + log_hex("[ehci] READ10 failed lba=", lba); + return -1; + } + ehci_memcpy(buf, g_xfer.virt, chunk * 512); + buf += chunk * 512; + lba += chunk; + count -= chunk; + } + return 0; +} + +static int ehci_block_write(void *ctx, u64 lba, u32 count, const void *buffer) { + (void)ctx; + if (!g_msc.ready || !buffer || count == 0) return -1; + DKM_STAT_INC(ehci_block_writes); + const u8 *buf = (const u8 *)buffer; + while (count > 0) { + u32 chunk = count > 16 ? 16 : count; + ehci_memcpy(g_xfer.virt, buf, chunk * 512); + __asm__ volatile("wbinvd" ::: "memory"); + if (ehci_scsi_write10(lba, chunk, g_xfer.virt) != 0) { + log_hex("[ehci] WRITE10 failed lba=", lba); + return -1; + } + buf += chunk * 512; + lba += chunk; + count -= chunk; + } + return 0; +} + +/* ================================================================ + * driver_init + * ================================================================ */ + +__attribute__((visibility("default"))) +int driver_init(const struct dkm_kernel_api *api, + struct dkm_driver_handle *handle) { + (void)handle; + dkm_tsc_calibrate(); + if (!api || !api->log) return -1; + g_api = api; + g_log = api->log; + dkm_instr_init(api); + + g_log->info("[ehci] init begin"); + + /* PCI 发现 0C/03/20 */ + u8 bus = 0, dev = 0, func = 0; + if (ehci_find(&bus, &dev, &func) != 0) { + g_log->info("[ehci] EHCI controller not found"); + g_log->info("[ehci] driver ready"); + return 0; + } + g_pci_bus = bus; g_pci_dev = dev; g_pci_func = func; + DKM_STAT_INC(ehci_pci_found); + g_log->info("[ehci] EHCI controller found"); + log_hex("[ehci] bus=", bus); + log_hex("[ehci] dev=", dev); + log_hex("[ehci] func=", func); + + u32 vd = dkm_pci_read(bus, dev, func, PCI_VENDOR_ID); + log_hex("[ehci] vendor=", vd & 0xffff); + log_hex("[ehci] device=", vd >> 16); + + /* 使能 MEM + BUSM */ + u32 command = dkm_pci_read(bus, dev, func, PCI_COMMAND) & 0xffff; + command |= PCI_CMD_MEM | PCI_CMD_BUSM; + dkm_pci_write(bus, dev, func, PCI_COMMAND, command); + log_hex("[ehci] PCI command=", command); + + /* BAR0(EHCI 通常是 32 位 MMIO) */ + u32 bar0_lo = dkm_pci_read(bus, dev, func, PCI_BAR0); + u64 bar_phys; + if ((bar0_lo & 0x7) == 0x4) { + u32 bar1_hi = dkm_pci_read(bus, dev, func, PCI_BAR1); + bar_phys = ((u64)bar1_hi << 32) | (u64)(bar0_lo & 0xFFFFFFF0u); + } else { + bar_phys = (u64)(bar0_lo & 0xFFFFFFF0u); + } + log_hex("[ehci] BAR0 phys=", bar_phys); + if (!api->hhdm_offset || !bar_phys) { + g_log->warn("[ehci] missing HHDM or BAR0"); + g_log->info("[ehci] driver ready"); + return 0; + } + + g_mmio = ehci_map_bar(bar_phys); + if (!g_mmio) { + g_log->error("[ehci] BAR MMIO unavailable"); + g_log->info("[ehci] driver ready"); + return 0; + } + + /* 能力寄存器 */ + g_caplen = mmio_read32(EHCI_CAP_CAPLENGTH) & 0xFF; + u32 hcsp = mmio_read32(EHCI_CAP_HCSPARAMS); + u32 hccp = mmio_read32(EHCI_CAP_HCCPARAMS); + g_nports = hcsp & 0x0F; + g_64bit = hccp & 1u; + g_ppc = (hcsp >> 4) & 1u; + log_hex("[ehci] CAPLEN=", g_caplen); + log_hex("[ehci] nports=", g_nports); + log_hex("[ehci] 64bit=", g_64bit); + log_hex("[ehci] PPC=", g_ppc); + if (g_nports == 0) { + g_log->error("[ehci] no ports"); + g_log->info("[ehci] driver ready"); + return 0; + } + + /* HC 复位 */ + if (ehci_hc_reset() != 0) { + g_log->error("[ehci] HC reset failed"); + g_log->info("[ehci] driver ready"); + return 0; + } + + /* 64 位控制器:CTRLDSSEGMENT = 0 */ + if (g_64bit) mmio_write32(g_caplen + 0x10, 0); + + /* DMA:异步列表页 + 数据缓冲 */ + if (!api->dma) { + g_log->warn("[ehci] DMA API unavailable"); + g_log->info("[ehci] driver ready"); + return 0; + } + if (api->dma->alloc_pages(1, 4096, 0x100000000ULL, &g_qh_dma) != 0) { + g_log->error("[ehci] async list alloc failed"); + g_log->info("[ehci] driver ready"); + return 0; + } + if (api->dma->alloc_pages(16, 4096, 0x100000000ULL, &g_xfer) != 0) { + g_log->error("[ehci] xfer buf alloc failed"); + g_log->info("[ehci] driver ready"); + return 0; + } + if (api->dma->alloc_pages(1, 4096, 0x100000000ULL, &g_csw) != 0) { + g_log->error("[ehci] csw buf alloc failed"); + g_log->info("[ehci] driver ready"); + return 0; + } + ehci_zero(g_qh_dma.virt, g_qh_dma.size); + ehci_zero(g_xfer.virt, g_xfer.size); + ehci_zero(g_csw.virt, g_csw.size); + + /* QH 布局:必须 32 字节对齐(EHCI 规范)。ctrl@0, out@64, in@128; + * TD 池 @256(TD 32 字节对齐)。原 48 字节间距导致 out/in 未对齐, + * HC 无法处理 QH → TD 永不被执行 → 超时。 */ + g_qh_ctrl = (ehci_qh *)((u8 *)g_qh_dma.virt + 0); + g_qh_bulk_out = (ehci_qh *)((u8 *)g_qh_dma.virt + 64); + g_qh_bulk_in = (ehci_qh *)((u8 *)g_qh_dma.virt + 128); + g_td_pool = (ehci_td *)((u8 *)g_qh_dma.virt + 256); + + /* 建环并启动 async schedule(必须在 CONFIGFLAG/端口操作前) */ + ehci_hc_start(); + + /* CONFIGFLAG=1(OS 所有端口) */ + mmio_write32(g_caplen + EHCI_OP_CONFIGFLAG, 1u); + + /* 端口电源 */ + for (u32 p = 0; p < g_nports; p++) { + u32 po = g_caplen + EHCI_OP_PORTSC + p * EHCI_PORT_STRIDE; + u32 ps = mmio_read32(po); + if (g_ppc && !(ps & PORTSC_PP)) { + mmio_write32(po, ps | PORTSC_PP); + dkm_delay_ms(20); + } + } + dkm_delay_ms(100); /* 端口稳定 */ + + /* 扫描端口,枚举 MSC */ + g_msc.ready = 0; + for (u32 p = 0; p < g_nports; p++) { + u32 po = g_caplen + EHCI_OP_PORTSC + p * EHCI_PORT_STRIDE; + u32 ps = mmio_read32(po); + log_hex("[ehci] port=", p); + log_hex("[ehci] PORTSC=", ps); + if (!(ps & PORTSC_CONNECT)) continue; + + if (ehci_reset_port(p) != 0) { + g_log->warn("[ehci] port reset failed"); + continue; + } + dkm_delay_ms(100); + if (ehci_enum_msc(p) != 0) { + g_log->warn("[ehci] MSC enumeration failed"); + continue; + } + /* TEST UNIT READY(重试 5 次) */ + for (int i = 0; i < 5; i++) { + if (ehci_scsi_test_unit_ready() == 0) break; + dkm_delay_ms(100); + } + /* READ CAPACITY */ + u64 sectors = 0; + u32 blk = 512; + if (ehci_scsi_read_capacity(§ors, &blk) != 0) { + g_log->warn("[ehci] READ CAPACITY failed"); + sectors = 0; blk = 512; + } + g_msc.sector_count = sectors; + g_msc.sector_size = blk; + g_msc.ready = 1; + DKM_STAT_INC(ehci_msc_found); + log_hex("[ehci] MSC device ready, sectors=", sectors); + log_hex("[ehci] MSC device block_size=", blk); + break; + } + + /* 注册 block provider */ + if (g_msc.ready && api->block && api->block->register_device) { + struct dkm_block_device_desc desc; + desc.name = "usb2"; + desc.sector_size = g_msc.sector_size; + desc.sector_count = g_msc.sector_count; + desc.ctx = 0; + desc.read = ehci_block_read; + desc.write = ehci_block_write; + int index = api->block->register_device(&desc); + log_hex("[ehci] block provider index=", (u64)(i64)index); + } else { + g_log->info("[ehci] no MSC device found; block provider not registered"); + } + + g_log->info("[ehci] driver ready"); + return 0; +} + +__attribute__((visibility("default"))) +int driver_exit(struct dkm_driver_handle *handle) { + (void)handle; + if (g_mmio) { + mmio_write32(g_caplen + EHCI_OP_USBCMD, + mmio_read32(g_caplen + EHCI_OP_USBCMD) & ~USBCMD_RUN); + } + return 0; +} \ No newline at end of file diff --git a/CODE/DKM/xhci/xhci.c b/CODE/DKM/xhci/xhci.c index 44786a8..63613b7 100644 --- a/CODE/DKM/xhci/xhci.c +++ b/CODE/DKM/xhci/xhci.c @@ -92,6 +92,9 @@ const struct dkm_driver_desc driver_desc = { #define XHCI_RT_IR0_ERDP_LO 0x38 #define XHCI_RT_IR0_ERDP_HI 0x3C +/* ERDP bits: bit3 = Event Handler Busy (软件写 1 清挂起) */ +#define ERDP_EHB (1u << 3) + /* USBCMD bits */ #define USBCMD_RS (1u << 0) #define USBCMD_RST (1u << 1) @@ -109,8 +112,11 @@ const struct dkm_driver_desc driver_desc = { #define PORTSC_PR (1u << 4) #define PORTSC_PLS_SHIFT 5 #define PORTSC_PLS_MASK 0xF -#define PORTSC_SPEED_SHIFT 17 -#define PORTSC_SPEED_MASK 0x7 +/* Port Speed 字段在 PORTSC bits[13:10](xHCI 1.2 §5.4.8)。 + * BUG-FIX: 原实现 shift=17/mask=0x7 是错的——QEMU 用 10:13, + * 误读导致 SuperSpeed(4) 被当成 FULL(1),EP0 MPS 错误。 */ +#define PORTSC_SPEED_SHIFT 10 +#define PORTSC_SPEED_MASK 0xF /* TRB types */ #define TRB_TYPE_NORMAL 0x01 @@ -124,12 +130,14 @@ const struct dkm_driver_desc driver_desc = { #define TRB_TYPE_CONFIGURE_EP 0x0C #define TRB_TYPE_NOOP 0x17 -/* Event TRB types — same namespace as command/transfer TRB types. - * Per xHCI spec §6.4: these are the TRB Type field values in the - * Event TRB control dword [15:10]. */ -#define EVT_TYPE_TRANSFER 0x01 /* Transfer Event */ -#define EVT_TYPE_CMD_COMPLETE 0x02 /* Command Completion Event */ -#define EVT_TYPE_PORT_CHANGE 0x21 /* Port Status Change Event */ +/* Event TRB types — xHCI 1.2 spec Table 6-28 (TRB Type field [15:10]). + * 规范值:Transfer=0x20, Command Completion=0x21, Port Status Change=0x22。 + * BUG-FIX: 原定义 0x01/0x02/0x21 与规范冲突——CMD_COMPLETE(0x21) 被 + * 误判为 PORT_CHANGE 事件消费,导致 Enable Slot 等命令事件环被提前 + * 清空,后续命令全部超时(QEMU hcd-xhci.c xhci_write_event 佐证)。 */ +#define EVT_TYPE_TRANSFER 0x20 /* Transfer Event */ +#define EVT_TYPE_CMD_COMPLETE 0x21 /* Command Completion Event */ +#define EVT_TYPE_PORT_CHANGE 0x22 /* Port Status Change Event */ /* TRB control field helpers */ #define TRB_TYPE_SHIFT 10 @@ -281,6 +289,149 @@ static void log_hex(const char *prefix, u64 value) { g_log->info(buf); } +/* ================================================================ + * Framebuffer 阶段诊断(实机无串口时屏幕直接显示 xhci 失败点) + * 失败时整屏填背景色 + 中央画大号两位十六进制状态码,然后 halt。 + * 码表(调用点注释中注明): + * 红底 0x01..0x07 基础设施失败(PCI/MMIO/caps/reset/ring/start) + * 红底 0x11..0x14 PCI 自检细分(见 xhci_pci_diag_code) + * 黄底 0x08 所有端口无设备可枚举 + * 黄底 0x0A..0x0F 枚举失败(slot/address/descriptor/configure/scsi) + * 依赖 dkm_kernel_api.fb_* 字段(32bpp,与 console_fb 一致)。 + * ================================================================ */ +static const u8 g_fb_font_hex[16][8] = { + {0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, /* 0 */ + {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00}, /* 1 */ + {0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00}, /* 2 */ + {0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, /* 3 */ + {0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00}, /* 4 */ + {0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, /* 5 */ + {0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00}, /* 6 */ + {0x7E,0x66,0x06,0x0C,0x18,0x18,0x18,0x00}, /* 7 */ + {0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, /* 8 */ + {0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00}, /* 9 */ + {0x3C,0x66,0x66,0x7E,0x66,0x66,0x66,0x00}, /* A */ + {0x7C,0x66,0x66,0x7C,0x66,0x66,0x7C,0x00}, /* B */ + {0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00}, /* C */ + {0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00}, /* D */ + {0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00}, /* E */ + {0x7E,0x60,0x60,0x7C,0x60,0x60,0x60,0x00}, /* F */ +}; + +static void fb_draw_char(u32 *fb, u64 pitch, int x0, int y0, int scale, + u32 color, int ch) { + const u8 *g = g_fb_font_hex[ch & 0xF]; + for (int row = 0; row < 8; row++) { + u8 bits = g[row]; + for (int col = 0; col < 8; col++) { + if (!(bits & (0x80u >> col))) continue; + for (int sy = 0; sy < scale; sy++) { + u32 *line = (u32 *)((u8 *)fb + (u64)(y0 + row * scale + sy) * pitch); + for (int sx = 0; sx < scale; sx++) + line[x0 + col * scale + sx] = color; + } + } + } +} + +static void fb_diag_show(const struct dkm_kernel_api *api, u32 bg, u32 code) { + if (!api || !api->fb_address || !api->fb_width || !api->fb_height) + return; /* 无 framebuffer 时退化为仅串口日志 */ + u32 *fb = (u32 *)api->fb_address; + u64 pitch = api->fb_pitch; + u64 w = api->fb_width, h = api->fb_height; + for (u64 y = 0; y < h; y++) { + u32 *line = (u32 *)((u8 *)fb + y * pitch); + for (u64 x = 0; x < w; x++) line[x] = bg; + } + int scale = 8; + int cw = 8 * scale; + int cx = (int)(w / 2) - cw; /* 两位数字整体居中 */ + int cy = (int)(h / 2) - (8 * scale) / 2; + u32 fg = 0xFFFFFFFFu; + if (code >= 0x10) fb_draw_char(fb, pitch, cx, cy, scale, fg, (int)(code >> 4)); + fb_draw_char(fb, pitch, cx + cw, cy, scale, fg, (int)(code & 0xF)); + /* 停住屏幕,等待实机拍照/观察 */ + for (;;) { + __asm__ volatile("cli; hlt"); + } +} + +/* 任意位数 hex(n=1..8),低位在前填充 */ +static void fb_draw_hexn(u32 *fb, u64 pitch, int x0, int y0, int scale, + u32 color, u32 v, int n) { + for (int i = 0; i < n; i++) { + int nib = (int)((v >> (4 * (n - 1 - i))) & 0xF); + fb_draw_char(fb, pitch, x0 + i * 8 * scale, y0, scale, color, nib); + } +} + +/* PCI 自检诊断清单项(bus/dev/device_id/class/progif) */ +struct usb_ctl_info { + u32 bus; + u32 dev; + u32 devid; /* PCI device ID */ + u32 cls; /* (class<<8)|subclass */ + u32 progif; +}; + +/* 扩展诊断:大字标题码 + 下方逐行列出扫描到的 PCI 设备。 + * 每行:bus(1) dev(1) deviceid(4) = 左 6 hex,class(2) progif(2) = 右 4 hex。 + * scale=2 支持 16 行(640x480 完整容纳)。device ID 用于区分芯片型号: + * Intel 7系: xHCI=1e31, EHCI1=1e2d, EHCI2=1e26 + * Intel 6系: EHCI1=1c2d, EHCI2=1c26 (H61 仅 1 个 EHCI) + * 第三方 USB3: ASMedia=1b21, Renesas=1033 */ +/* 诊断用,保留 */ +static void fb_diag_show_usb(const struct dkm_kernel_api *api, u32 bg, u32 code, + const struct usb_ctl_info *usb, u32 usb_count) __attribute__((unused)); +static void fb_diag_show_usb(const struct dkm_kernel_api *api, u32 bg, u32 code, + const struct usb_ctl_info *usb, u32 usb_count) { + if (!api || !api->fb_address || !api->fb_width || !api->fb_height) + return; + u32 *fb = (u32 *)api->fb_address; + u64 pitch = api->fb_pitch; + u64 w = api->fb_width, h = api->fb_height; + for (u64 y = 0; y < h; y++) { + u32 *line = (u32 *)((u8 *)fb + y * pitch); + for (u64 x = 0; x < w; x++) line[x] = bg; + } + u32 fg = 0xFFFFFFFFu; + /* 标题大字(scale 6,两位居中,固定顶部) */ + int s = 6, cw = 8 * s; + int cx = (int)(w / 2) - cw; + int cy = 8; + if (code >= 0x10) fb_draw_char(fb, pitch, cx, cy, s, fg, (int)(code >> 4)); + fb_draw_char(fb, pitch, cx + cw, cy, s, fg, (int)(code & 0xF)); + /* 信息行(scale 2,最多 16 行): + * 左:bus(1)+dev(1)+devid(4)=6 hex;右:class(2)+progif(2)=4 hex */ + int s2 = 2, cw2 = 8 * s2; + int row_y = cy + 8 * s + 8; + int total = (6 + 1 + 4) * cw2; /* 176px */ + int xl = (int)(w / 2) - total / 2; + int xr = xl + (6 + 1) * cw2; + u32 max_rows = usb_count < 16 ? usb_count : 16; + for (u32 i = 0; i < max_rows; i++) { + u32 bd = ((usb[i].bus & 0xFF) << 4) | (usb[i].dev & 0x1F); + fb_draw_hexn(fb, pitch, xl, row_y, s2, fg, bd, 2); + fb_draw_hexn(fb, pitch, xl + 2 * cw2, row_y, s2, fg, usb[i].devid & 0xFFFF, 4); + fb_draw_hexn(fb, pitch, xr, row_y, s2, fg, usb[i].cls & 0xFFFF, 2); + fb_draw_hexn(fb, pitch, xr + 2 * cw2, row_y, s2, fg, usb[i].progif & 0xFFFF, 2); + row_y += 8 * s2 + 4; /* 20px/行 */ + } + for (;;) { + __asm__ volatile("cli; hlt"); + } +} + +/* 红底 = 基础设施失败,halt(0xFFFF0000 在 BGRA 下是纯红; + * 原 0xFF0000FF 实际渲染为蓝——0x01 那次实机测试显示“蓝底”即此原因) */ +#define FB_FATAL(api, code) fb_diag_show((api), 0xFFFF0000u, (code)) +/* 红底 PCI 自检:标题码 + USB 控制器清单 */ +#define FB_FATAL_show_usb(api, code) \ + fb_diag_show_usb((api), 0xFFFF0000u, (code), g_diag_usb, g_diag_usb_count) +/* 黄底 = 枚举失败,halt */ +#define FB_ENUM_FAIL(code) fb_diag_show(g_api, 0xFFFFC800u, (code)) + static void xhci_zero(void *ptr, u32 len) { u8 *p = (u8 *)ptr; for (u32 i = 0; i < len; i++) p[i] = 0; @@ -321,6 +472,96 @@ static __inline__ void xhci_mb(void) { * Phase 1: PCI discovery + MMIO mapping * ================================================================ */ +/* PCI 自检:区分“传统 0xCF8/0xCFC 配置机制失效” vs “确实无 xHCI 控制器”。 + * 实机现象:xhci_find 全扫描失败(诊断码 0x01)——USB3 主板必然有 xHCI, + * 扫不到只可能是 (a) 配置机制读不到任何设备 (b) 设备在异常位置 (c) 控制器 + * 是 EHCI 等非 xHCI。返回细分诊断码: + * 0x11 bus0/dev0/func0 vendor=0xFFFF → 0xCF8/0xCFC 机制失效 + * 0x12 机制可用但全扫描无任何 USB 控制器 (class 0x0C/03) + * 0x13 有 USB 控制器但无 prog_if=0x30(全部 EHCI/UHCI/OHCI) + * 0x14 存在 prog_if=0x30 的 xHCI 但 xhci_find 漏匹配(防御性,理论不可能) + * 同时把扫描到的设备填入 g_diag_usb 供屏幕显示——记录策略: + * 第一遍收集全部 USB 控制器(class 0C/03,含 bus1+ 第三方芯片), + * 第二遍收集其他设备(bus0 优先),总上限 16 行。 + * 关键 device ID 判读:Intel 7系 xHCI=1e31 / EHCI1=1e2d / EHCI2=1e26; + * Intel 6系 EHCI1=1c2d / EHCI2=1c26(H61 仅 1 个);ASMedia=1b21; + * Renesas=1033。 */ +static struct usb_ctl_info g_diag_usb[16]; +static u32 g_diag_usb_count; + +#define DIAG_MAX 16 + +static void diag_add(u32 bus, u32 dev, u32 devid, u32 cls, u32 progif) { + if (g_diag_usb_count >= DIAG_MAX) return; + g_diag_usb[g_diag_usb_count].bus = bus; + g_diag_usb[g_diag_usb_count].dev = dev; + g_diag_usb[g_diag_usb_count].devid = devid; + g_diag_usb[g_diag_usb_count].cls = cls; + g_diag_usb[g_diag_usb_count].progif = progif; + g_diag_usb_count++; +} + +/* 诊断用,保留(无 xHCI 场景默认不调用,可通过临时改 driver_init 调试) */ +static u32 xhci_pci_diag_code(void) __attribute__((unused)); +static u32 xhci_pci_diag_code(void) { + u32 vd0 = dkm_pci_read(0, 0, 0, PCI_VENDOR_ID); + g_diag_usb_count = 0; + if ((vd0 & 0xffff) == 0xffff) return 0x11; + + u32 usb_total = 0, xhci_total = 0; + + /* 第一遍:USB 控制器(全 bus,含 bus1+ 第三方芯片) */ + for (u16 bus = 0; bus < 256; bus++) { + for (u8 dev = 0; dev < 32; dev++) { + u32 vd = dkm_pci_read((u8)bus, dev, 0, PCI_VENDOR_ID); + if ((vd & 0xffff) == 0xffff) continue; + u8 header = (u8)(dkm_pci_read((u8)bus, dev, 0, PCI_HEADER) >> 16); + u8 func_count = (header & 0x80) ? 8 : 1; + for (u8 func = 0; func < func_count; func++) { + u32 vd2 = dkm_pci_read((u8)bus, dev, func, PCI_VENDOR_ID); + if ((vd2 & 0xffff) == 0xffff) continue; + u32 class_reg = dkm_pci_read((u8)bus, dev, func, 0x08); + u8 prog_if = (u8)((class_reg >> 8) & 0xff); + u8 subclass = (u8)((class_reg >> 16) & 0xff); + u8 class_code = (u8)((class_reg >> 24) & 0xff); + if (class_code == 0x0C && subclass == 0x03) { + usb_total++; + if (prog_if == 0x30) xhci_total++; + diag_add(bus, dev, vd2 >> 16, + (u32)((class_code << 8) | subclass), prog_if); + } + } + } + } + + /* 第二遍:其他设备补位(bus0 优先) */ + if (g_diag_usb_count < DIAG_MAX) { + for (u16 bus = 0; bus < 256 && g_diag_usb_count < DIAG_MAX; bus++) { + for (u8 dev = 0; dev < 32 && g_diag_usb_count < DIAG_MAX; dev++) { + u32 vd = dkm_pci_read((u8)bus, dev, 0, PCI_VENDOR_ID); + if ((vd & 0xffff) == 0xffff) continue; + u8 header = (u8)(dkm_pci_read((u8)bus, dev, 0, PCI_HEADER) >> 16); + u8 func_count = (header & 0x80) ? 8 : 1; + for (u8 func = 0; func < func_count && g_diag_usb_count < DIAG_MAX; func++) { + u32 vd2 = dkm_pci_read((u8)bus, dev, func, PCI_VENDOR_ID); + if ((vd2 & 0xffff) == 0xffff) continue; + u32 class_reg = dkm_pci_read((u8)bus, dev, func, 0x08); + u8 prog_if = (u8)((class_reg >> 8) & 0xff); + u8 subclass = (u8)((class_reg >> 16) & 0xff); + u8 class_code = (u8)((class_reg >> 24) & 0xff); + if (class_code == 0x0C && subclass == 0x03) continue; /* 已收集 */ + diag_add(bus, dev, vd2 >> 16, + (u32)((class_code << 8) | subclass), prog_if); + } + } + } + } + + if (usb_total == 0) return 0x12; + if (xhci_total > 0) return 0x14; + return 0x13; +} + static int xhci_find(u8 *out_bus, u8 *out_dev, u8 *out_func) { for (u16 bus = 0; bus < 256; bus++) { for (u8 dev = 0; dev < 32; dev++) { @@ -518,9 +759,18 @@ static int xhci_parse_caps(void) { g_doorbell_off = mmio_read32(XHCI_CAP_DBOFF) & 0xFFFFFFF0u; g_runtime_off = mmio_read32(XHCI_CAP_RTSOFF) & 0xFFFFFFE0u; - /* Page size */ + /* Page size: PAGESIZE 寄存器 bit[n] 置位表示支持 2^(n+12) 字节页。 + * 取置位的最低 bit(xHCI 规范要求软件使用最小支持页)。 + * 原实现 (ps_reg & 0xF) 语义错误,实机多 bit 置位时会得到错误页大小。 */ u32 ps_reg = mmio_read32(g_cap_length + XHCI_OP_PAGESIZE); - g_page_size = 1u << ((ps_reg & 0xF) + 12); + u32 ps_bits = ps_reg & 0xFFFFu; + u32 ps_shift = 0; + if (ps_bits) { + while (!(ps_bits & 1u)) { ps_bits >>= 1; ps_shift++; } + g_page_size = 4096u << ps_shift; + } else { + g_page_size = 4096; + } log_hex("[xhci] CAPLEN=", g_cap_length); log_hex("[xhci] MaxSlots=", g_max_slots); @@ -591,9 +841,13 @@ static u32 xhci_ring_enqueue(xhci_ring *ring, u64 param, u32 status, u32 control return idx; } -/* Ring a doorbell: slot_id=0 for command, otherwise endpoint doorbell */ +/* Ring a doorbell: slot_id=0 for command, otherwise endpoint doorbell。 + * Doorbell Registers 每个 4 字节(xHCI 1.2 §5.6):offset = slot_id * 4。 + * BUG-FIX: 原实现 slot_id * 0x20 偏移错误——QEMU 门铃区 reg>>=2 后 + * reg 即 slot_id,0x20 偏移会敲到 slot 8,该 slot 无 EP → 控制器不 + * 处理 TRB → 无 Transfer Event → 轮询超时。 */ static void xhci_ring_doorbell(u32 slot_id, u32 target) { - mmio_write32(g_doorbell_off + slot_id * 0x20, target); + mmio_write32(g_doorbell_off + slot_id * 4, target); } /* ================================================================ @@ -627,9 +881,9 @@ static int xhci_poll_event(u32 expected_type, u64 timeout_ms, g_evt_ring.enqueue = (g_evt_ring.enqueue + 1) % (g_evt_ring.size - 1); if (g_evt_ring.enqueue == 0) g_evt_ccs ^= 1; - /* Update ERDP */ + /* Update ERDP: 地址 16 字节对齐,bit0 保持 0,bit3=EHB 清挂起 */ u64 erdp = g_evt_ring.dma.phys + (u64)g_evt_ring.enqueue * sizeof(xhci_trb); - mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | g_evt_ccs); + mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | ERDP_EHB); /* Check completion code */ u32 cc = (evt->status >> 24) & 0xFF; @@ -659,7 +913,7 @@ static int xhci_poll_event(u32 expected_type, u64 timeout_ms, g_evt_ring.enqueue = (g_evt_ring.enqueue + 1) % (g_evt_ring.size - 1); if (g_evt_ring.enqueue == 0) g_evt_ccs ^= 1; u64 erdp2 = g_evt_ring.dma.phys + (u64)g_evt_ring.enqueue * sizeof(xhci_trb); - mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp2 | g_evt_ccs); + mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp2 | ERDP_EHB); continue; } @@ -677,7 +931,7 @@ static int xhci_poll_event(u32 expected_type, u64 timeout_ms, static int xhci_cmd_submit(u64 param, u32 status, u32 control, u64 timeout_ms, - u64 *out_param, u32 *out_status) { + u64 *out_param, u32 *out_status, u32 *out_control) { /* Clear Event Ring interrupt pending */ mmio_write32(g_runtime_off + XHCI_RT_IR0_IMAN, mmio_read32(g_runtime_off + XHCI_RT_IR0_IMAN) | 2u); @@ -705,7 +959,7 @@ static int xhci_cmd_submit(u64 param, u32 status, u32 control, } int rc = xhci_poll_event(EVT_TYPE_CMD_COMPLETE, timeout_ms, - out_param, out_status, 0); + out_param, out_status, out_control); /* Clear EINT */ mmio_write32(g_cap_length + XHCI_OP_USBSTS, @@ -810,8 +1064,8 @@ static int xhci_controller_start(void) { /* Configure Event Ring */ mmio_write32(g_runtime_off + XHCI_RT_IR0_ERSTSZ, 1); mmio_write64(g_runtime_off + XHCI_RT_IR0_ERSTBA_LO, g_erst.phys); - u64 erdp = g_evt_ring.dma.phys | (u64)g_evt_ccs; - mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp); + u64 erdp = g_evt_ring.dma.phys; + mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | ERDP_EHB); /* Enable interrupt (IMAN.IE=1) */ mmio_write32(g_runtime_off + XHCI_RT_IR0_IMAN, @@ -883,7 +1137,7 @@ static int xhci_reset_port(u32 port_idx) { g_evt_ring.enqueue = (g_evt_ring.enqueue + 1) % (g_evt_ring.size - 1); if (g_evt_ring.enqueue == 0) g_evt_ccs ^= 1; u64 erdp = g_evt_ring.dma.phys + (u64)g_evt_ring.enqueue * sizeof(xhci_trb); - mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | g_evt_ccs); + mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | ERDP_EHB); } return 0; @@ -911,15 +1165,18 @@ static int xhci_enable_slot(u8 *out_slot_id) { u32 control = (TRB_TYPE_ENABLE_SLOT << TRB_TYPE_SHIFT); u64 out_param = 0; u32 out_status = 0; + u32 out_control = 0; - int rc = xhci_cmd_submit(0, 0, control, 5000, &out_param, &out_status); + int rc = xhci_cmd_submit(0, 0, control, 5000, &out_param, &out_status, &out_control); if (rc != 0) { g_log->error("[xhci] Enable Slot failed"); return rc; } - /* Slot ID is in the upper bits of the completion event parameter */ - *out_slot_id = (u8)((out_param >> 24) & 0xFF); + /* Slot ID 位于 Command Completion Event 的 TRB control[31:24]。 + * (xHCI 1.2 §6.4.2.2:CMD_COMPLETE 事件 TRB parameter = 命令 TRB 地址, + * slot id 在 control 高字节 —— QEMU hcd-xhci.c xhci_write_event 佐证) */ + *out_slot_id = (u8)((out_control >> 24) & 0xFF); log_hex("[xhci] slot_id=", *out_slot_id); return 0; } @@ -940,7 +1197,8 @@ static int xhci_address_device(u8 slot_id, int bsr) { u64 out_param = 0; u32 out_status = 0; - int rc = xhci_cmd_submit(input_ctx_phys, 0, control, 5000, &out_param, &out_status); + u32 out_control = 0; + int rc = xhci_cmd_submit(input_ctx_phys, 0, control, 5000, &out_param, &out_status, &out_control); if (rc != 0) { g_log->error("[xhci] Address Device failed"); return rc; @@ -958,7 +1216,8 @@ static int xhci_configure_endpoint(u8 slot_id) { u64 out_param = 0; u32 out_status = 0; - int rc = xhci_cmd_submit(input_ctx_phys, 0, control, 5000, &out_param, &out_status); + u32 out_control = 0; + int rc = xhci_cmd_submit(input_ctx_phys, 0, control, 5000, &out_param, &out_status, &out_control); if (rc != 0) { g_log->error("[xhci] Configure Endpoint failed"); return rc; @@ -979,7 +1238,16 @@ static int xhci_control_transfer(const u8 *setup_pkt, u8 *data, u32 data_len, ((u64)setup_pkt[2] << 16) | ((u64)setup_pkt[3] << 24); u32 setup_status = 8; /* Setup packet length */ - u32 setup_control = (TRB_TYPE_SETUP_STAGE << TRB_TYPE_SHIFT) | TRB_IOC; + /* Setup Stage TRB(不设 IOC:控制传输只需在最后一个 TRB 上报完成, + * 中间 TRB 设 IOC 会让 QEMU/实机产生多余 Transfer Event,残留事件 + * 污染后续 CMD_COMPLETE 轮询——日志表现为 "unexpected event type") */ + u32 setup_control = (TRB_TYPE_SETUP_STAGE << TRB_TYPE_SHIFT); + /* Setup Stage TRB 必须设置 IDT (Immediate Data, bit6): + * setup 包 8 字节直接内嵌在 TRB parameter 字段。 + * BUG-FIX: 原实现缺 IDT,QEMU xhci_fire_ctl_transfer 校验 + * `!(trb_setup->control & TRB_TR_IDT)` 直接返回 -1 → 无 Transfer + * Event → 轮询超时(GET_DESCRIPTOR 失败)。 */ + setup_control |= (1u << 6); /* TRB_TR_IDT */ /* Set direction in TRB: bit 16 of Setup Stage TRB */ if (direction) setup_control |= (1u << 16); @@ -995,7 +1263,8 @@ static int xhci_control_transfer(const u8 *setup_pkt, u8 *data, u32 data_len, u64 data_phys = direction ? g_xfer_buf.phys : g_xfer_buf.phys; u32 data_status = data_len; - u32 data_control = (TRB_TYPE_DATA_STAGE << TRB_TYPE_SHIFT) | TRB_IOC; + /* Data Stage 不设 IOC(避免多余事件残留) */ + u32 data_control = (TRB_TYPE_DATA_STAGE << TRB_TYPE_SHIFT); if (direction) data_control |= (1u << 16); /* DIR=IN */ xhci_ring_enqueue(&g_ep0_ring, data_phys, data_status, data_control); @@ -1088,7 +1357,11 @@ static int xhci_bulk_transfer(xhci_ring *ring, u8 ep_num, xhci_ring_enqueue(ring, g_xfer_buf.phys, data_len, control); xhci_mb(); - xhci_ring_doorbell(g_msc.slot_id, ep_num); + /* Doorbell target = Endpoint ID:EPn OUT→2n,EPn IN→2n+1。 + * BUG-FIX: 原实现传 EP 号(2/1),QEMU xhci_kick_ep 按 Endpoint ID + * 索引 eps[epid-1] → 敲错端点 → 无 Transfer Event → 轮询超时。 */ + u8 db_target = (direction ? (u8)(2u * ep_num + 1) : (u8)(2u * ep_num)); + xhci_ring_doorbell(g_msc.slot_id, db_target); /* Poll for Transfer Event */ u64 out_param = 0; @@ -1097,6 +1370,7 @@ static int xhci_bulk_transfer(xhci_ring *ring, u8 ep_num, &out_param, &out_status, 0); if (rc != 0) { g_log->warn("[xhci] bulk transfer failed"); + log_hex("[xhci] bulk rc=", (u64)(i64)rc); return rc; } @@ -1343,32 +1617,48 @@ static int xhci_block_write(void *ctx, u64 lba, u32 count, const void *buffer) { * Phase 3: Build Input Context for Address Device * ================================================================ */ -static void xhci_build_input_ctx_slot(u8 slot_id, u8 speed, u16 mps0) { +static void xhci_build_input_ctx_slot(u8 slot_id, u8 speed, u16 mps0, u32 port_idx) { (void)slot_id; u32 ctx_sz = g_ctx_size; u8 *ic = (u8 *)g_input_ctx.virt; xhci_zero(ic, g_input_ctx.size); - /* Input Control Context: enable Slot Context (bit 0) and EP0 (bit 1) */ + /* Input Control Context (xHCI 1.2 §6.4.3): + * DWORD0 = Drop Context Flags(本次全 0,不 drop 任何上下文) + * DWORD1 = Add Context Flags(bit0=Slot, bit1=EP0) + * BUG-FIX: 原实现把 Add flags 写进 DWORD0(Drop 槽), + * QEMU/实机校验 ictl_ctx[0]==0 && ictl_ctx[1]==0x3 直接失败 CC_TRB_ERROR。 */ u32 *icc = (u32 *)ic; - icc[0] = (1u << 1) | (1u << 0); /* A0=1 (Slot), A1=1 (EP0) */ + icc[0] = 0x0; /* Drop Context Flags */ + icc[1] = (1u << 0) | (1u << 1); /* Add: Slot + EP0 */ - /* Slot Context starts at offset ctx_sz */ + /* Slot Context (§6.2.2): + * DWORD0: Route String [0:19] + Speed [20:22] + Context Entries [27:31] + * DWORD1: Max Exit Latency [0:7] + Root Hub Port Number [16:23] + * BUG-FIX: 原实现 speed<<29(占用 Context Entries 位域)、MPS0 写入 + * DWORD1(规范 Slot Context 无 MPS0 字段,MPS0 在 EP0 Context)—— + * QEMU xhci_lookup_uport 读 DWORD1[16:23] 找端口,原值 port=0 查不到 + * 设备 → CC_TRB_ERROR。 */ u32 *slot_ctx = (u32 *)(ic + ctx_sz); u32 route_string = 0; /* direct-connect, no hub */ - slot_ctx[0] = route_string | ((u32)speed << 29); - slot_ctx[1] = (u32)mps0; /* Max Packet Size for EP0 */ - slot_ctx[2] = (1u << 27); /* Context Entries = 1 (only EP0) */ + slot_ctx[0] = route_string | ((u32)speed << 20) | (1u << 27); + slot_ctx[1] = (port_idx + 1) << 16; /* Root Hub Port Number (1-based) */ - /* EP0 Context starts at offset ctx_sz * 2 */ + /* EP0 Context starts at offset ctx_sz * 2 + * Endpoint Context (§6.2.3) 布局: + * DWORD1: CErr[4:5] + EP Type[6:8] + Max Packet Size[16:31] + * DWORD2: Dequeue Pointer Low[4:31](bit0=DCS, bits1:3=reserved) + * DWORD3: Dequeue Pointer High + * DWORD4: Average TRB Length[0:15] + * BUG-FIX: 原实现把 dequeue 写到 DWORD6/7——QEMU xhci_init_epctx + * 从 DWORD2/3 读 dequeue(ctx[2]&~0xf, ctx[3]),读到 0 → ring 无 + * dequeue → kick_epctx 直接返回,TRB 永不处理 → 无 Transfer Event。 */ u32 *ep0_ctx = (u32 *)(ic + ctx_sz * 2); - /* EP Type = Control, Max Packet Size, etc. */ ep0_ctx[1] = (4u << 3) | ((u32)mps0 << 16); /* EP Type=4 (Control IN), MPS */ - ep0_ctx[4] = 8; /* Average TRB Length */ - /* Dequeue Pointer: EP0 transfer ring */ u64 ep0_deq = g_ep0_ring.dma.phys | (u64)g_ep0_ring.ccs; - ep0_ctx[6] = (u32)(ep0_deq & 0xFFFFFFFFu); - ep0_ctx[7] = (u32)(ep0_deq >> 32); + ep0_ctx[2] = (u32)(ep0_deq & 0xFFFFFFF0u) | (u32)g_ep0_ring.ccs; + ep0_ctx[3] = (u32)(ep0_deq >> 32); + ep0_ctx[4] = 8; /* Average TRB Length */ } static void xhci_build_input_ctx_bulk_eps(u8 bulk_out_ep, u8 bulk_in_ep, @@ -1376,31 +1666,45 @@ static void xhci_build_input_ctx_bulk_eps(u8 bulk_out_ep, u8 bulk_in_ep, u32 ctx_sz = g_ctx_size; u8 *ic = (u8 *)g_input_ctx.virt; - /* Update Input Control Context: enable EP0, Bulk OUT, Bulk IN */ + /* Input Control Context(Configure Endpoint 语义,xHCI 1.2 §6.4.3): + * DWORD0 = Drop(0),DWORD1 = Add。 + * Configure 时 bit0=Slot 必须 Add,bit1=EP0 不可重复 Add + * (QEMU xhci_configure_slot 校验 (ictl[1]&0x3)==0x1)。 + * 端点位用 Context Index:EPn OUT→2n,EPn IN→2n+1 + * (QEMU 从 i=2 开始遍历 Add flags,按 ictx+32*i 读 EP Context)。 */ u32 *icc = (u32 *)ic; - icc[0] = (1u << 1) | (1u << 0) | - (1u << bulk_out_ep) | (1u << bulk_in_ep); + u32 out_idx = 2u * bulk_out_ep; + u32 in_idx = 2u * bulk_in_ep + 1; + icc[0] = 0x0; + icc[1] = (1u << 0) | (1u << out_idx) | (1u << in_idx); - /* Update Slot Context: Context Entries = max(bulk_in_ep, bulk_out_ep) */ + /* Update Slot Context: Context Entries = max Context Index + * (DWORD0 bits[27:31],保留 Speed/Route 原值)。 */ u32 *slot_ctx = (u32 *)(ic + ctx_sz); - u32 max_ep = bulk_out_ep > bulk_in_ep ? bulk_out_ep : bulk_in_ep; - slot_ctx[2] = (max_ep << 27); + u32 max_idx = out_idx > in_idx ? out_idx : in_idx; + slot_ctx[0] &= ~(0x1Fu << 27); + slot_ctx[0] |= (max_idx << 27); - /* Bulk OUT Endpoint Context (at offset ctx_sz * (1 + bulk_out_ep)) */ - u32 *out_ctx = (u32 *)(ic + ctx_sz * (1 + bulk_out_ep)); + /* Bulk OUT Endpoint Context。 + * 偏移 = ctx_sz * (out_idx + 1):input context 布局是 + * [ICC][Slot][EP0][EP1]... 每个占 1 个 ctx_sz,Context Index i 的 + * EP context 在 (i+1) 个槽处(QEMU xhci_configure_slot 用 + * ictx+32+(32*i) 读取)。BUG-FIX: 原实现 ctx_sz*out_idx 少了 ICC + * 槽 → 全部错位 32 字节 → QEMU 读到零 EP context。 */ + u32 *out_ctx = (u32 *)(ic + ctx_sz * (out_idx + 1)); out_ctx[1] = (EP_TYPE_BULK_OUT << 3) | ((u32)out_mps << 16); - out_ctx[4] = 512; /* Average TRB Length */ u64 out_deq = g_bulk_out_ring.dma.phys | (u64)g_bulk_out_ring.ccs; - out_ctx[6] = (u32)(out_deq & 0xFFFFFFFFu); - out_ctx[7] = (u32)(out_deq >> 32); + out_ctx[2] = (u32)(out_deq & 0xFFFFFFF0u) | (u32)g_bulk_out_ring.ccs; + out_ctx[3] = (u32)(out_deq >> 32); + out_ctx[4] = 512; /* Average TRB Length */ - /* Bulk IN Endpoint Context (at offset ctx_sz * (1 + bulk_in_ep)) */ - u32 *in_ctx = (u32 *)(ic + ctx_sz * (1 + bulk_in_ep)); + /* Bulk IN Endpoint Context (at offset ctx_sz * (in_idx + 1)) */ + u32 *in_ctx = (u32 *)(ic + ctx_sz * (in_idx + 1)); in_ctx[1] = (EP_TYPE_BULK_IN << 3) | ((u32)in_mps << 16); - in_ctx[4] = 512; /* Average TRB Length */ u64 in_deq = g_bulk_in_ring.dma.phys | (u64)g_bulk_in_ring.ccs; - in_ctx[6] = (u32)(in_deq & 0xFFFFFFFFu); - in_ctx[7] = (u32)(in_deq >> 32); + in_ctx[2] = (u32)(in_deq & 0xFFFFFFF0u) | (u32)g_bulk_in_ring.ccs; + in_ctx[3] = (u32)(in_deq >> 32); + in_ctx[4] = 512; /* Average TRB Length */ } /* ================================================================ @@ -1411,18 +1715,22 @@ static int xhci_enum_msc_device(u32 port_idx) { u8 speed = xhci_get_port_speed(port_idx); log_hex("[xhci] port speed=", speed); - /* Default MaxPacketSize for EP0 based on speed */ - u16 mps0 = 64; /* High/Super-Speed default */ - if (speed == USB_SPEED_FULL) mps0 = 64; + /* Default MaxPacketSize for EP0 based on speed + * (xHCI 1.2 §4.3: Full=8? No — FS/HS EP0 通常 64,LS=8,SS=512) */ + u16 mps0 = 64; /* Full/High-Speed default */ if (speed == USB_SPEED_LOW) mps0 = 8; + if (speed == USB_SPEED_SUPER) mps0 = 512; /* Step 1: Enable Slot */ u8 slot_id = 0; - if (xhci_enable_slot(&slot_id) != 0) return -1; + if (xhci_enable_slot(&slot_id) != 0) { + FB_ENUM_FAIL(0x0A); /* 黄: Enable Slot 失败 */ + return -1; + } g_msc.slot_id = slot_id; g_msc.speed = speed; - /* Step 2: Allocate per-device DMA buffers */ + /* Step 1.5: Allocate per-device DMA buffers(在 DCBAA 指向输出上下文之前) */ if (g_api->dma->alloc_pages(4, 4096, 0x100000000ULL, &g_input_ctx) != 0) return -2; if (g_api->dma->alloc_pages(4, 4096, 0x100000000ULL, &g_dev_ctx) != 0) return -2; if (g_api->dma->alloc_pages(4, 4096, 0x100000000ULL, &g_xfer_buf) != 0) return -2; @@ -1431,18 +1739,30 @@ static int xhci_enum_msc_device(u32 port_idx) { if (xhci_ring_init(&g_bulk_out_ring, XFER_RING_SIZE) != 0) return -2; if (xhci_ring_init(&g_bulk_in_ring, XFER_RING_SIZE) != 0) return -2; + /* DCBAA[slot_id] 必须指向输出上下文(Device Context)。 + * Address Device/Configure Endpoint 命令都会读取该地址; + * 缺失时 QEMU/实机将 octx=0 → DMA 写失败/CC_TRB_ERROR。 */ + xhci_zero(g_dev_ctx.virt, g_dev_ctx.size); + u64 *dcbaa = (u64 *)g_dcbaa.virt; + dcbaa[slot_id] = g_dev_ctx.phys; + log_hex("[xhci] DCBAA[slot]=", dcbaa[slot_id]); + /* Step 3: Build Input Context and Address Device (BSR=1 first) */ - xhci_build_input_ctx_slot(slot_id, speed, mps0); + xhci_build_input_ctx_slot(slot_id, speed, mps0, port_idx); if (xhci_address_device(slot_id, 1) != 0) { /* BSR=1 failed, try without BSR */ - xhci_build_input_ctx_slot(slot_id, speed, mps0); - if (xhci_address_device(slot_id, 0) != 0) return -3; + xhci_build_input_ctx_slot(slot_id, speed, mps0, port_idx); + if (xhci_address_device(slot_id, 0) != 0) { + FB_ENUM_FAIL(0x0B); /* 黄: Address Device 失败(BSR=1/0 均失败) */ + return -3; + } } /* Step 4: Get Device Descriptor */ u8 dev_desc[18]; if (xhci_get_device_descriptor(dev_desc) != 0) { g_log->warn("[xhci] GET_DESCRIPTOR(Device) failed"); + FB_ENUM_FAIL(0x0C); /* 黄: GET_DESCRIPTOR(Device) 失败 */ return -4; } @@ -1459,7 +1779,7 @@ static int xhci_enum_msc_device(u32 port_idx) { /* Step 5: Re-address with real MPS if different */ if (real_mps0 != mps0 && real_mps0 >= 8) { mps0 = real_mps0; - xhci_build_input_ctx_slot(slot_id, speed, mps0); + xhci_build_input_ctx_slot(slot_id, speed, mps0, port_idx); if (xhci_address_device(slot_id, 0) != 0) { g_log->warn("[xhci] re-address failed"); /* Continue anyway — device may still work */ @@ -1470,12 +1790,16 @@ static int xhci_enum_msc_device(u32 port_idx) { u8 cfg_desc[255]; if (xhci_get_config_descriptor(cfg_desc, 255) != 0) { g_log->warn("[xhci] GET_DESCRIPTOR(Config) failed"); + FB_ENUM_FAIL(0x0C); /* 黄: GET_DESCRIPTOR(Config) 失败 */ return -5; } /* Parse configuration descriptor to find MSC interface and endpoints */ u16 total_len = (u16)cfg_desc[2] | ((u16)cfg_desc[3] << 8); - if (total_len < 9) return -5; + if (total_len < 9) { + FB_ENUM_FAIL(0x0D); /* 黄: 配置描述符过短 */ + return -5; + } u8 bulk_out_ep = 0, bulk_in_ep = 0; u16 bulk_out_mps = 512, bulk_in_mps = 512; @@ -1497,24 +1821,34 @@ static int xhci_enum_msc_device(u32 port_idx) { msc_found = 1; g_log->info("[xhci] MSC BOT interface found"); - /* Scan following endpoint descriptors */ + /* Scan following endpoint descriptors. + * SuperSpeed 设备在 endpoint 后紧跟 6 字节 SS Endpoint + * Companion descriptor (type 0x30)——必须跳过继续找下一 + * 个 endpoint,不能因 type!=5 提前终止(Bulk OUT 常在 + * IN 之后且被 companion 隔开)。 */ u32 ep_pos = pos + desc_len; - while (ep_pos + 7 <= (u32)total_len && - cfg_desc[ep_pos + 1] == 5) { /* endpoint descriptor */ - u8 ep_addr = cfg_desc[ep_pos + 2]; - u8 ep_attr = cfg_desc[ep_pos + 3]; - u16 ep_mps = (u16)cfg_desc[ep_pos + 4] | ((u16)cfg_desc[ep_pos + 5] << 8); + while (ep_pos + 2 <= (u32)total_len) { + u8 ep_len = cfg_desc[ep_pos]; + if (ep_len == 0 || ep_pos + ep_len > (u32)total_len) break; + if (cfg_desc[ep_pos + 1] == 5) { /* endpoint descriptor */ + u8 ep_addr = cfg_desc[ep_pos + 2]; + u8 ep_attr = cfg_desc[ep_pos + 3]; + u16 ep_mps = (u16)cfg_desc[ep_pos + 4] | ((u16)cfg_desc[ep_pos + 5] << 8); - if ((ep_attr & 0x03) == 0x02) { /* Bulk */ - if (ep_addr & 0x80) { - bulk_in_ep = ep_addr & 0x0F; - bulk_in_mps = ep_mps; - } else { - bulk_out_ep = ep_addr & 0x0F; - bulk_out_mps = ep_mps; + if ((ep_attr & 0x03) == 0x02) { /* Bulk */ + if (ep_addr & 0x80) { + bulk_in_ep = ep_addr & 0x0F; + bulk_in_mps = ep_mps; + } else { + bulk_out_ep = ep_addr & 0x0F; + bulk_out_mps = ep_mps; + } } + } else if (cfg_desc[ep_pos + 1] == 4) { + /* 下一个 interface — 停止扫描 */ + break; } - ep_pos += cfg_desc[ep_pos]; + ep_pos += ep_len; } break; } @@ -1524,6 +1858,7 @@ static int xhci_enum_msc_device(u32 port_idx) { if (!msc_found || bulk_out_ep == 0 || bulk_in_ep == 0) { g_log->warn("[xhci] no MSC BOT interface found"); + FB_ENUM_FAIL(0x0D); /* 黄: 未找到 MSC BOT 接口/端点 */ return -6; } @@ -1543,6 +1878,7 @@ static int xhci_enum_msc_device(u32 port_idx) { bulk_out_mps, bulk_in_mps); if (xhci_configure_endpoint(slot_id) != 0) { g_log->warn("[xhci] Configure Endpoint failed"); + FB_ENUM_FAIL(0x0E); /* 黄: Configure Endpoint 失败 */ return -7; } @@ -1601,7 +1937,10 @@ int driver_init(const struct dkm_kernel_api *api, u8 bus = 0, dev = 0, func = 0; if (xhci_find(&bus, &dev, &func) != 0) { g_log->info("[xhci] xHCI controller not found"); - g_log->info("[xhci] driver ready"); + /* BUG-FIX: 不 halt。xhci 是可选驱动(manifest required=false), + * 无 xHCI 的平台(Intel 6 系/纯 EHCI)必须继续引导到 EHCI/DSK, + * 否则 `for(;;)hlt` 卡死整条启动链。仅记日志,如需诊断可开 + * xhci_pci_diag_code() 打印控制器清单(见注释)。 */ return 0; } @@ -1628,7 +1967,7 @@ int driver_init(const struct dkm_kernel_api *api, u64 bar_phys; if (bar0_lo & 1u) { g_log->warn("[xhci] BAR0 is IO space; unsupported"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x02); /* 红: BAR0 是 IO 空间 */ return 0; } if ((bar0_lo & 0x6u) == 0x4u) { @@ -1644,7 +1983,7 @@ int driver_init(const struct dkm_kernel_api *api, if (!api->hhdm_offset || !bar_phys) { g_log->warn("[xhci] missing HHDM or BAR0; skip"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x02); /* 红: 缺 HHDM/BAR */ return 0; } @@ -1652,14 +1991,14 @@ int driver_init(const struct dkm_kernel_api *api, g_mmio = xhci_map_bar(api, bar_phys); if (!g_mmio) { g_log->error("[xhci] BAR MMIO unavailable; driver inactive"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x03); /* 红: BAR MMIO 映射失败 */ return 0; } /* ---- Phase 1: Parse capabilities ---- */ if (xhci_parse_caps() != 0) { g_log->error("[xhci] capability parse failed"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x04); /* 红: capability 解析失败 */ return 0; } @@ -1669,27 +2008,27 @@ int driver_init(const struct dkm_kernel_api *api, /* ---- Phase 1: Controller Reset ---- */ if (xhci_controller_reset() != 0) { g_log->error("[xhci] controller reset failed"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x05); /* 红: 控制器 reset 失败 */ return 0; } /* ---- Phase 2: Setup Rings + DCBAA + Scratchpad ---- */ if (!api->dma) { g_log->warn("[xhci] DMA API unavailable"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x06); /* 红: DMA API 缺失 */ return 0; } if (xhci_setup_rings() != 0) { g_log->error("[xhci] ring setup failed"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x06); /* 红: ring 初始化失败 */ return 0; } /* ---- Phase 2: Start Controller ---- */ if (xhci_controller_start() != 0) { g_log->error("[xhci] controller start failed"); - g_log->info("[xhci] driver ready"); + FB_FATAL(api, 0x07); /* 红: 控制器启动失败 */ return 0; } @@ -1765,6 +2104,7 @@ int driver_init(const struct dkm_kernel_api *api, } } else { g_log->info("[xhci] no MSC device found; block provider not registered"); + FB_FATAL(api, 0x08); /* 黄: 所有端口无设备/枚举全部失败 */ } g_log->info("[xhci] driver ready"); diff --git a/CODE/UTSM/include/utsm/block.h b/CODE/UTSM/include/utsm/block.h index 803fe16..e14bf40 100644 --- a/CODE/UTSM/include/utsm/block.h +++ b/CODE/UTSM/include/utsm/block.h @@ -25,6 +25,7 @@ typedef struct dkm_block_api { u64 (*sector_size)(u32 index); const char *(*device_name)(u32 index); int (*set_write_fn)(u32 index, dkm_block_write_fn fn); + u64 (*sector_count)(u32 index); /* 0x38: tail-append, 老内核该槽为 0 表示未知 */ } dkm_block_api; void block_init(void); diff --git a/CODE/UTSM/include/utsm/log.h b/CODE/UTSM/include/utsm/log.h index 88ba96d..cbaf163 100644 --- a/CODE/UTSM/include/utsm/log.h +++ b/CODE/UTSM/include/utsm/log.h @@ -13,7 +13,8 @@ void log_error(const char *msg); void log_hex64(const char *prefix, u64 value); void panic(const char *msg); -/* ---- 内存日志缓冲区(实机启动日志持久化) ---- */ +/* ---- 内存日志缓冲区(实机启动日志持久化) ---- + * 由 disk_log_flush() 写入 FAT32 文件 SYSTEM/DESHAB64/DEV/BOOTLOG.TXT。 */ void log_capture_enable(int enable); const char *log_get_buffer(void); u32 log_get_length(void); diff --git a/CODE/UTSM/kernel/block.c b/CODE/UTSM/kernel/block.c index f118f25..fb7578c 100644 --- a/CODE/UTSM/kernel/block.c +++ b/CODE/UTSM/kernel/block.c @@ -58,6 +58,11 @@ static int block_set_write_fn_impl(u32 index, dkm_block_write_fn fn) { return 0; } +static u64 block_sector_count_impl(u32 index) { + if (index >= g_device_count) return 0; + return g_devices[index].sector_count; +} + static const dkm_block_api g_block_api = { .register_device = block_register_device_impl, .device_count = block_device_count_impl, @@ -65,7 +70,8 @@ static const dkm_block_api g_block_api = { .write = block_write_impl, .sector_size = block_sector_size_impl, .device_name = block_device_name_impl, - .set_write_fn = block_set_write_fn_impl + .set_write_fn = block_set_write_fn_impl, + .sector_count = block_sector_count_impl }; const dkm_block_api *block_get_api(void) { diff --git a/CODE/UTSM/kernel/block.o b/CODE/UTSM/kernel/block.o index 41f66394f6b2db2794f6ea1f69940011d8d6a2d2..48a2432337aa44b58504353ce2d26aae1cc33d7e 100644 GIT binary patch delta 1108 zcmZ9LO=uHQ5Xax!k9N1aNt3vAT_l*$L#kLz1c^CF(~BZ%sZazDl0fkwSW`5u6k4LU ziim+Z3xd#Fky1f=P&^496pvnndQk8n7${Ofi8Fok%5EoQ=e_@L=FPnKc0V6k&3DI) zRd3_?vH>H;xG98yHDeRXrUTc^j#H6BSfba5q8Y%Bc_Io?kYWJil-1dPgqn3SYT(Sk zUm2sweC5OscwmLm8qSDznVw_%1U8VqVwy3OJj(PQe6_;td8YH4LeJ{tU0f-LD;795 z#r`Vl8SMEGJhOe7hIKoPhMAEId2FH2!55?%R>(DZOr_G08VI8_j{f$QJ!0zZ(X&j; ztUgcBIi>|x-z4Z$rn>g07#)TWNOP#@y5E?pp8p}G9qGC|F&e=5prR96_X2k*?&-y^eM>S*k|e3 uDCU}%`Yn|2t7!GiaR0o7@L~qj9eD~4o%SXwfzU+LYN-_ delta 969 zcmZ9L&ubGw6vt;aJ4rUX+a{*CT}m)TXc|E*J(ygy@nZcALP0O0C!vQ@1fe~s?NJbm z^3I~cix;s{@n-QK5Za6MBAA1M2f>0zOMK(|PRP0o^Jc%F_iNtnKCJCcJel_PN^g(d z^XRj;MNe~UI;Dy~pZ1kfTIG3_s}1{}?)WG4Jazpf-DS<=yQ2l3h0g}5lpT1m=K*N9ZG3X4`ml^s2v<`JULw|ynq3#UP5vmoEw8Dz3`wdij zzRc8~$kqKlM5}Zkbk3=msg14DYtRMIu?+pfbojgrpfZiPDAd)x3o6q%1?qZ!m!V6b z?!4bKnV*4iF)x^ zupUVEYxEP^wSpH$v>95AT6&YZQ6oLa0k*qYL4$RZdQoHYBq(|q(0Wd`ZED6x zsO;w_lMygrF5a@PqI;xJA?=5W XX|lIxqo+TjzWaq3C_8ND??(F<$>4$j diff --git a/CODE/UTSM/kernel/dsk_loader.c b/CODE/UTSM/kernel/dsk_loader.c index fb47c9f..a86dc15 100644 --- a/CODE/UTSM/kernel/dsk_loader.c +++ b/CODE/UTSM/kernel/dsk_loader.c @@ -520,6 +520,379 @@ int fat32_read_path(const char *path, u8 **out_data, u32 *out_size) { return -8; /* 路径为空或无效 */ } +/* ---- FAT32 写入(实机启动日志落盘,2026-08 移植自 DSK fat32_write_*) ---- + * 相比 DSK 版本的两点增强: + * 1) FAT 条目支持按需读-改-写(超出 256 扇区预读缓冲时逐扇区读写, + * 并同步镜像到所有 FAT 副本),不再受小分区限制。 + * 2) 目录扫描跨簇 + 目录满时自动扩展,文件链支持多簇(日志可达 128KB)。 + * 注意:与 DSK 一致,不更新 FSInfo(free count 会过期,测试工具可接受)。 */ + +/* 读取 FAT 表条目(28 位),支持按需读扇区。 */ +static u32 fat32_get_cluster_entry(u32 fat_byte_off, u32 clus, + const fat32_bpb *bpb, const u8 *disk) { + u32 ent_off = fat_byte_off + clus * 4; + if (ent_off + 4 <= sizeof(g_dsk_fat32_disk)) { + return fat32_read_u32(disk + ent_off) & 0x0FFFFFFF; + } + return fat32_get_next_cluster(fat_byte_off, clus, bpb, disk); +} + +/* 写 FAT 表条目。缓冲内条目直接改内存(同步镜像副本 1), + * 缓冲外条目按 FAT 副本逐个读-改-写扇区。返回 0 成功。 */ +static int fat32_set_cluster_entry(u32 fat_byte_off, u32 fat_sectors, u32 clus, + u32 value, const fat32_bpb *bpb, u8 *disk) { + u32 ent_off = fat_byte_off + clus * 4; + if (ent_off + 4 <= sizeof(g_dsk_fat32_disk)) { + disk[ent_off] = (u8)(value & 0xFF); + disk[ent_off + 1] = (u8)((value >> 8) & 0xFF); + disk[ent_off + 2] = (u8)((value >> 16) & 0xFF); + disk[ent_off + 3] = (u8)((disk[ent_off + 3] & 0xF0) | ((value >> 24) & 0x0F)); + /* 同步 FAT 副本 1 的缓冲镜像(若在缓冲内) */ + if (bpb->fat_count >= 2) { + u32 e1 = ent_off + fat_sectors * 512; + if (e1 + 4 <= sizeof(g_dsk_fat32_disk)) { + disk[e1] = disk[ent_off]; + disk[e1 + 1] = disk[ent_off + 1]; + disk[e1 + 2] = disk[ent_off + 2]; + disk[e1 + 3] = disk[ent_off + 3]; + } + } + return 0; + } + /* 缓冲外:逐 FAT 副本读-改-写 */ + const dkm_kernel_api *api = dkm_get_kernel_api(); + if (!api || !api->block || !api->block->read || !api->block->write) return -1; + u32 sec_index = (clus * 4) / 512; + u32 ent_in_sec = (clus * 4) % 512; + u32 copies = bpb->fat_count; + if (copies > 4) copies = 4; + for (u32 f = 0; f < copies; f++) { + u32 sec_lba = bpb->reserved_sector_count + f * fat_sectors + sec_index; + if (api->block->read(0, sec_lba, 1, g_dsk_fat32_fat_sec) != 0) return -2; + g_dsk_fat32_fat_sec[ent_in_sec] = (u8)(value & 0xFF); + g_dsk_fat32_fat_sec[ent_in_sec + 1] = (u8)((value >> 8) & 0xFF); + g_dsk_fat32_fat_sec[ent_in_sec + 2] = (u8)((value >> 16) & 0xFF); + g_dsk_fat32_fat_sec[ent_in_sec + 3] = (u8)((g_dsk_fat32_fat_sec[ent_in_sec + 3] & 0xF0) | ((value >> 24) & 0x0F)); + if (api->block->write(0, sec_lba, 1, g_dsk_fat32_fat_sec) != 0) return -3; + } + return 0; +} + +/* 顺序扫描 FAT 找空闲簇(缓冲内直读,缓冲外按 8 扇区批量读)。 + * from_clus: 起始簇号(建议传入上次分配结果+1,避免反复扫描)。 + * 返回空闲簇号,失败返回 0。 */ +static u32 fat32_find_free_cluster(u32 fat_lba, u32 fat_sectors, u32 from_clus) { + const dkm_kernel_api *api = dkm_get_kernel_api(); + u8 *disk = g_dsk_fat32_disk; + u32 total_entries = (fat_sectors * 512) / 4; + u32 fat_byte_off = fat_lba * 512; + u32 c = from_clus; + while (c < total_entries) { + u32 ent_off = fat_byte_off + c * 4; + if (ent_off + 4 <= sizeof(g_dsk_fat32_disk)) { + if ((fat32_read_u32(disk + ent_off) & 0x0FFFFFFF) == 0) return c; + c++; + } else { + u32 sec_index = (c * 4) / 512; + u32 sec_lba = fat_lba + sec_index; + u32 secs = 8; + if (sec_lba + secs > fat_lba + fat_sectors) secs = fat_lba + fat_sectors - sec_lba; + if (api->block->read(0, sec_lba, secs, g_dsk_fat32_cluster) != 0) return 0; + u32 first_in_chunk = sec_index * 128; + u32 last_in_chunk = (sec_index + secs) * 128; + for (u32 cc = (c < first_in_chunk ? first_in_chunk : c); + cc < last_in_chunk && cc < total_entries; cc++) { + if ((fat32_read_u32(g_dsk_fat32_cluster + (cc - first_in_chunk) * 4) & 0x0FFFFFFF) == 0) + return cc; + } + c = last_in_chunk; + } + } + return 0; +} + +/* 分配并链接 needed 个簇,全部标记 EOC。scan_from 记录扫描游标。 + * 返回 0 成功;first/last 返回链首/链尾。 */ +static int fat32_alloc_clusters(const fat32_bpb *bpb, u32 fat_lba, + u32 fat_sectors, u32 needed, + u32 *scan_from, u32 *first, u32 *last) { + u32 prev = 0; + u32 fc = 0, lc = 0; + u32 cur_scan = *scan_from; + for (u32 i = 0; i < needed; i++) { + u32 nc = fat32_find_free_cluster(fat_lba, fat_sectors, cur_scan); + if (nc == 0) return -1; + cur_scan = nc + 1; + if (fat32_set_cluster_entry(bpb->reserved_sector_count * 512, fat_sectors, + nc, 0x0FFFFFFF, bpb, g_dsk_fat32_disk) != 0) + return -2; + if (prev) { + if (fat32_set_cluster_entry(bpb->reserved_sector_count * 512, fat_sectors, + prev, nc, bpb, g_dsk_fat32_disk) != 0) + return -3; + } else { + fc = nc; + } + prev = nc; + lc = nc; + } + *scan_from = cur_scan; + *first = fc; + *last = lc; + return 0; +} + +/* 在指定目录(dir_clus 起始簇)写入文件(8.3 名,11 字符)。 + * 存在则覆盖(复用旧链/扩展/截断),否则新建。支持多簇目录与多簇文件。 + * data 必须指向可寻址静态内存(如 log 缓冲)。返回 0 成功。 */ +static int fat32_write_file_in_dir(u32 dir_clus, const char *name11, + const u8 *data, u32 size) { + const dkm_kernel_api *api = dkm_get_kernel_api(); + if (!api || !api->block || !api->block->read || !api->block->write) return -1; + u8 *disk = g_dsk_fat32_disk; + if (api->block->read(0, 0, 256, disk) != 0) return -2; + const fat32_bpb *bpb = (const fat32_bpb *)disk; + if (bpb->boot_sig != 0xAA55 || bpb->bytes_per_sector != 512) return -3; + + u32 spc = bpb->sectors_per_cluster; + u32 fat_lba = bpb->reserved_sector_count; + u32 fat_sectors = bpb->sectors_per_fat; + u32 data_lba = bpb->reserved_sector_count + (u32)bpb->fat_count * bpb->sectors_per_fat; + u32 fat_byte_off = fat_lba * 512; + u32 cluster_bytes = spc * 512; + + /* ---- 1. 扫描目录链:找同名条目或空闲槽 ---- */ + u32 cur = dir_clus; + u32 last_dir_clus = dir_clus; + u32 entry_clus = 0; /* 目录项所在目录簇 */ + u32 entry_slot = 0; + int found = 0; /* 同名文件已存在 */ + u32 old_first_clus = 0; + int done = 0; + while (cur >= 2 && cur < 0x0FFFFFF8) { + last_dir_clus = cur; + u32 clus_lba = data_lba + (cur - 2) * spc; + if (api->block->read(0, clus_lba, spc, g_dsk_fat32_cluster) != 0) return -4; + u32 me = cluster_bytes / 32; + const fat32_dir_entry *d = (const fat32_dir_entry *)g_dsk_fat32_cluster; + for (u32 e = 0; e < me; e++) { + if (d[e].name[0] == 0) { /* 目录结束标记 */ + if (entry_clus == 0) { entry_clus = cur; entry_slot = e; } + done = 1; + break; + } + if ((u8)d[e].name[0] == 0xE5) { /* 已删除项,可复用 */ + if (entry_clus == 0) { entry_clus = cur; entry_slot = e; } + continue; + } + if (d[e].attr == 0x0F || (d[e].attr & 0x08)) continue; + if (fat32_name11_eq(d[e].name, name11)) { + found = 1; + entry_clus = cur; + entry_slot = e; + old_first_clus = fat32_read_u16((const u8 *)&d[e].cluster_low) | + ((u32)fat32_read_u16((const u8 *)&d[e].cluster_high) << 16); + done = 1; + break; + } + } + if (done) break; + cur = fat32_get_cluster_entry(fat_byte_off, cur, bpb, disk); + } + + /* ---- 2. 目录满时扩展一个簇 ---- */ + if (entry_clus == 0) { + u32 scan_from = 2; + u32 nc = fat32_find_free_cluster(fat_lba, fat_sectors, scan_from); + if (nc == 0) return -9; + if (fat32_set_cluster_entry(fat_byte_off, fat_sectors, nc, 0x0FFFFFFF, + bpb, disk) != 0) + return -10; + if (fat32_set_cluster_entry(fat_byte_off, fat_sectors, last_dir_clus, nc, + bpb, disk) != 0) + return -11; + for (u32 b = 0; b < cluster_bytes; b++) g_dsk_fat32_cluster[b] = 0; + u32 nlba = data_lba + (nc - 2) * spc; + if (api->block->write(0, nlba, spc, g_dsk_fat32_cluster) != 0) return -12; + entry_clus = nc; + entry_slot = 0; + } + + /* ---- 3. 文件簇链:复用/扩展/截断 ---- */ + u32 needed = (size + cluster_bytes - 1) / cluster_bytes; + if (needed == 0) needed = 1; + u32 first = 0; + u32 scan_from = 2; + + if (found && old_first_clus >= 2) { + /* 走旧链,收集前 needed 个簇 */ + u32 c = old_first_clus; + u32 cnt = 0; + u32 last = 0; + u32 tail = 0; + while (c >= 2 && c < 0x0FFFFFF8 && cnt < needed) { + last = c; + cnt++; + c = fat32_get_cluster_entry(fat_byte_off, c, bpb, disk); + } + first = old_first_clus; + if (cnt == needed) { + /* 截断:last 指向 EOC,释放旧链尾部 */ + tail = fat32_get_cluster_entry(fat_byte_off, last, bpb, disk); + if (fat32_set_cluster_entry(fat_byte_off, fat_sectors, last, 0x0FFFFFFF, + bpb, disk) != 0) + return -13; + while (tail >= 2 && tail < 0x0FFFFFF8) { + u32 nn = fat32_get_cluster_entry(fat_byte_off, tail, bpb, disk); + if (fat32_set_cluster_entry(fat_byte_off, fat_sectors, tail, 0, + bpb, disk) != 0) + return -14; + tail = nn; + } + } else { + /* 旧链不足:last 之后补簇 */ + u32 need_more = needed - cnt; + u32 nf = 0, nl = 0; + if (fat32_alloc_clusters(bpb, fat_lba, fat_sectors, need_more, + &scan_from, &nf, &nl) != 0) + return -15; + if (fat32_set_cluster_entry(fat_byte_off, fat_sectors, last, nf, + bpb, disk) != 0) + return -16; + } + } else { + u32 nf = 0, nl = 0; + if (fat32_alloc_clusters(bpb, fat_lba, fat_sectors, needed, + &scan_from, &nf, &nl) != 0) + return -17; + first = nf; + } + + /* ---- 4. 写数据簇 ---- */ + { + u32 c = first; + u32 remaining = size; + u32 off = 0; + while (c >= 2 && c < 0x0FFFFFF8 && remaining > 0) { + u32 lba = data_lba + (c - 2) * spc; + if (remaining >= cluster_bytes) { + if (api->block->write(0, lba, spc, data + off) != 0) return -18; + } else { + u8 *cb = g_dsk_fat32_cluster; + for (u32 b = 0; b < cluster_bytes; b++) cb[b] = 0; + for (u32 b = 0; b < remaining; b++) cb[b] = data[off + b]; + if (api->block->write(0, lba, spc, cb) != 0) return -19; + } + if (remaining >= cluster_bytes) { + off += cluster_bytes; + remaining -= cluster_bytes; + } else { + off += remaining; + remaining = 0; + } + c = fat32_get_cluster_entry(fat_byte_off, c, bpb, disk); + } + } + + /* ---- 5. 更新目录项 ---- */ + { + u32 elba = data_lba + (entry_clus - 2) * spc; + if (api->block->read(0, elba, spc, g_dsk_fat32_cluster) != 0) return -20; + fat32_dir_entry *dir = (fat32_dir_entry *)g_dsk_fat32_cluster; + fat32_dir_entry *e = &dir[entry_slot]; + if (!found) { + for (int i = 0; i < 11; i++) e->name[i] = name11[i]; + e->attr = 0x20; /* archive */ + e->nt_reserved = 0; + e->creation_tenth = 0; + e->creation_time = 0; + e->creation_date = 0; + e->access_date = 0; + e->write_time = 0; + e->write_date = 0; + } + e->cluster_high = (u16)((first >> 16) & 0xFFFF); + e->cluster_low = (u16)(first & 0xFFFF); + e->file_size = size; + if (api->block->write(0, elba, spc, g_dsk_fat32_cluster) != 0) return -21; + } + + /* ---- 6. 回写 FAT:缓冲内区间按副本刷回(缓冲外条目已即时写盘) ---- */ + { + u32 copies = bpb->fat_count; + if (copies > 4) copies = 4; + for (u32 f = 0; f < copies; f++) { + u32 flba = fat_lba + f * fat_sectors; + if (flba >= 256) continue; + u32 ns = fat_sectors; + if (flba + ns > 256) ns = 256 - flba; + if (ns == 0) continue; + if (api->block->write(0, flba, ns, disk + (u64)flba * 512) != 0) return -22; + } + } + + log_info("[UTSM] fat32 write: ok"); + return 0; +} + +/* 在根目录写入文件(8.3 名)。 */ +static int fat32_write_root_file(const char *name11, const u8 *data, u32 size) { + const dkm_kernel_api *api = dkm_get_kernel_api(); + if (!api || !api->block || !api->block->read) return -1; + u8 *disk = g_dsk_fat32_disk; + if (api->block->read(0, 0, 256, disk) != 0) return -2; + const fat32_bpb *bpb = (const fat32_bpb *)disk; + if (bpb->boot_sig != 0xAA55) return -3; + return fat32_write_file_in_dir(bpb->root_cluster, name11, data, size); +} + +/* 按 8.3 路径(每段 11 字符,'/' 分隔)定位子目录,返回起始簇,失败返回 0。 */ +static u32 fat32_find_dir_path(const char *path) { + const dkm_kernel_api *api = dkm_get_kernel_api(); + if (!api || !api->block || !api->block->read) return 0; + u8 *disk = g_dsk_fat32_disk; + if (api->block->read(0, 0, 256, disk) != 0) return 0; + const fat32_bpb *bpb = (const fat32_bpb *)disk; + if (bpb->boot_sig != 0xAA55 || bpb->bytes_per_sector != 512) return 0; + + u32 cur = bpb->root_cluster; + u32 pos = 0; + while (path[pos]) { + char comp[12]; + u32 ci = 0; + while (path[pos] && path[pos] != '/') { + if (ci < 11) comp[ci++] = path[pos]; + pos++; + } + while (ci < 11) comp[ci++] = ' '; + comp[11] = 0; + if (path[pos] == '/') pos++; + if (comp[0] == ' ') break; /* 空段防呆 */ + u32 sub = 0, sz = 0; + if (fat32_find_in_dir(cur, comp, 1, &sub, &sz, 0) != 0) return 0; + cur = sub; + } + return cur; +} + +/* 实机启动日志落盘:写 FAT32 文件。 + * 优先写 SYSTEM/DESHAB64/DEV/BOOTLOG.TXT;DEV 目录缺失时回退根目录 BOOTLOG.TXT。 + * 相比旧原始扇区方案不再覆盖 GPT header/entries,主机可直接读取该文件。 + * 返回 0 成功。 */ +int utsm_bootlog_write(const u8 *data, u32 size) { + const dkm_kernel_api *api = dkm_get_kernel_api(); + if (!api || !api->block || !api->block->device_count) return -1; + if (api->block->device_count() == 0) return -2; + if (!api->block->write) return -3; + if (!data || size == 0) return -4; + + static const char name11[11] = {'B','O','O','T','L','O','G',' ','T','X','T'}; + u32 dev_clus = fat32_find_dir_path("SYSTEM /DESHAB64 /DEV "); + if (dev_clus >= 2) + return fat32_write_file_in_dir(dev_clus, name11, data, size); + return fat32_write_root_file(name11, data, size); +} + /* ---- ELF checks ---- */ static int dsk_check_elf(const void *address, u64 size) { if (!address || size < sizeof(elf64_ehdr)) return -1; diff --git a/CODE/UTSM/kernel/dsk_loader.o b/CODE/UTSM/kernel/dsk_loader.o index 924b19df653b305136ad5a6b4164a84bf78b94ab..625279052a676c6d31588dca2388c0c8c8943ef8 100644 GIT binary patch literal 26408 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 diff --git a/CODE/UTSM/kernel/log.c b/CODE/UTSM/kernel/log.c index 5e7687a..a6117f5 100644 --- a/CODE/UTSM/kernel/log.c +++ b/CODE/UTSM/kernel/log.c @@ -2,7 +2,8 @@ /* ---- 内存日志缓冲区(实机启动日志持久化) ---- * 默认启用捕获:所有日志同时写串口和内存缓冲。 - * 块设备就绪后由 disk_log_flush() 把缓冲区写入磁盘原始扇区。 */ + * 块设备就绪后由 disk_log_flush() 把缓冲区写入 FAT32 文件 + * SYSTEM/DESHAB64/DEV/BOOTLOG.TXT(DEV 缺失时回退根目录 BOOTLOG.TXT)。 */ #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; diff --git a/CODE/UTSM/kernel/main.c b/CODE/UTSM/kernel/main.c index f4754a4..aeb0ede 100644 --- a/CODE/UTSM/kernel/main.c +++ b/CODE/UTSM/kernel/main.c @@ -26,6 +26,10 @@ void dsm_load_by_manifest_ex(const ini_config *cfg); * Phase 1 已支持 LFN 长文件名 + 子目录遍历。 */ extern int fat32_read_path(const char *path, u8 **out_data, u32 *out_size); +/* dsk_loader.c: 实机启动日志落盘。把日志缓冲写入 FAT32 文件: + * SYSTEM/DESHAB64/DEV/BOOTLOG.TXT(DEV 缺失时回退根目录 BOOTLOG.TXT)。 */ +extern int utsm_bootlog_write(const u8 *data, u32 size); + /* ---- 运行期配置全局变量(从 FUCK 文件初始化) ---- */ u32 g_utsm_max_segments = UTSM_MAX_SEGMENTS_DEFAULT; u32 g_utsm_max_capabilities = UTSM_MAX_CAPABILITIES_DEFAULT; @@ -149,12 +153,16 @@ 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) { +/* === 实机日志持久化:把内存日志缓冲区写入 FAT32 文件 === + * 目标: SYSTEM/DESHAB64/DEV/BOOTLOG.TXT(DEV 目录不存在时回退根目录 BOOTLOG.TXT)。 + * 由 FUCK [boot] disk_log=1 启用。 + * 相比旧原始扇区方案(LBA 2-1000): + * - 不再覆盖 GPT header/entries,不破坏 Limine 引导链 + * - 日志以普通 FAT32 文件呈现,主机直接读取即可,无需 WinHex/dd + * 日志超出 BOOTLOG_MAX_BYTES 时截断(128KB 足够覆盖完整启动链)。 */ +#define BOOTLOG_MAX_BYTES (128u * 1024u) + +static void disk_log_flush(void) { 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"); @@ -164,10 +172,6 @@ static void disk_log_flush(int start_lba, int end_lba) { 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(); @@ -175,45 +179,16 @@ static void disk_log_flush(int start_lba, int end_lba) { log_warn("[UTSM] disk_log: log buffer empty, skip"); return; } + if (len > BOOTLOG_MAX_BYTES) len = BOOTLOG_MAX_BYTES; - 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; + int rc = utsm_bootlog_write((const u8 *)buf, len); + if (rc == 0) { + log_info("[UTSM] disk_log: BOOTLOG.TXT written"); + log_hex64("[UTSM] disk_log: bytes=", len); + } else { + log_error("[UTSM] disk_log: FAT32 write failed"); + log_hex64("[UTSM] disk_log: rc=", (u64)(i64)rc); } - - /* 最后一个不完整扇区:补零后写入 */ - 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) { @@ -497,13 +472,11 @@ void kernel_main(void) { log_info("[UTSM] PE DLL manager initialized (SYSTEM/lib)"); } - /* === 实机日志持久化:把启动日志写入磁盘原始扇区(开发测试用) === */ + /* === 实机日志持久化:把启动日志写入 FAT32 文件 BOOTLOG.TXT === */ { 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); + disk_log_flush(); } } diff --git a/CODE/UTSM/kernel/main.o b/CODE/UTSM/kernel/main.o index f65d639ccef206dc32b3408e761ca5b3110e2edf..25a10f961e4d768c2d15ade761cc73cb4379c86c 100644 GIT binary patch literal 19912 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 literal 20832 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 OcPe~1et0i~ diff --git a/CODE/dsk/main.c b/CODE/dsk/main.c index 5ccde4e..630f600 100644 --- a/CODE/dsk/main.c +++ b/CODE/dsk/main.c @@ -34,6 +34,8 @@ typedef struct dsk_block_api_mirror { void *pad0; void *pad1; /* register_device, device_count */ block_read_fn read; block_write_fn write; + void *pad2; void *pad3; void *pad4; /* sector_size, device_name, set_write_fn */ + u64 (*sector_count)(u32 index); /* 0x38: tail-append, 老内核该槽为 NULL */ } dsk_block_api_mirror; typedef struct dsk_kernel_api_mirror { @@ -55,6 +57,8 @@ _Static_assert(offsetof(dsk_kernel_api_mirror, block) == 0xA8, "dsk_kernel_api_mirror.block offset must be 0xA8 (matching dkm_kernel_api)"); _Static_assert(offsetof(dsk_block_api_mirror, read) == 16, "dsk_block_api_mirror.read offset must be 16 (matching dkm_block_api)"); +_Static_assert(offsetof(dsk_block_api_mirror, sector_count) == 0x38, + "dsk_block_api_mirror.sector_count offset must be 0x38 (matching dkm_block_api tail-append)"); static void sputc(char c) { for(unsigned i=0;i<100000;i++){if(inb(COM1+5)&0x20)break;} outb(COM1,(u8)c); } static void swrite(const char *s) { while(*s){if(*s=='\n')sputc('\r');sputc(*s++);} } diff --git a/CODE/sdk/include/deshab/boot_log.h b/CODE/sdk/include/deshab/boot_log.h new file mode 100644 index 0000000..7f95c14 --- /dev/null +++ b/CODE/sdk/include/deshab/boot_log.h @@ -0,0 +1,15 @@ +/* deshab/boot_log.h — SDK 侧 boot log 绑定(见 CODE/tools/boot_log.h) */ +#ifndef DESHAB_BOOT_LOG_H +#define DESHAB_BOOT_LOG_H + +#include "../../tools/boot_log.h" +#include "boot_context.h" + +static inline void dsb_bootlog_bind(dsb_boot_log_info **slot, const dsk_boot_context *ctx) { + boot_log_bind((boot_log_info **)slot, ctx ? ctx->utsm_state : 0); +} + +/* 兼容别名 */ +typedef boot_log_info dsb_boot_log_info; + +#endif /* DESHAB_BOOT_LOG_H */ diff --git a/CODE/sdk/include/deshab/kernel_api.h b/CODE/sdk/include/deshab/kernel_api.h index 1c33997..5a5cb97 100644 --- a/CODE/sdk/include/deshab/kernel_api.h +++ b/CODE/sdk/include/deshab/kernel_api.h @@ -117,6 +117,7 @@ typedef struct dkm_block_api { u64 (*sector_size)(u32 index); const char *(*device_name)(u32 index); int (*set_write_fn)(u32 index, dkm_block_write_fn fn); + u64 (*sector_count)(u32 index); /* 0x38: tail-append, 与 utsm/block.h 同步 */ } dkm_block_api; /* ---- MMIO / 页表 API ---- diff --git a/CODE/tools/boot_log.h b/CODE/tools/boot_log.h new file mode 100644 index 0000000..c66bd1d --- /dev/null +++ b/CODE/tools/boot_log.h @@ -0,0 +1,59 @@ +/* boot_log.h — 跨 UTSM/DSK/应用 ELF 的启动日志环形缓冲(freestanding,无 libc) + * + * boot_log_info 由 UTSM 在静态内存中创建,经 dsk_boot_context.utsm_state 传递。 + * DSK 注册 flush 回调,将缓冲写入 FAT32: SYSTEM/DESHAB64/DEV/BOOTLOG.TXT + * (DEV 目录不存在时回退 ESP 根目录 BOOTLOG.TXT)。 + */ +#ifndef BOOT_LOG_H +#define BOOT_LOG_H + +typedef unsigned int u32; +typedef unsigned long long u64; + +#define BOOT_LOG_MAGIC 0x424C4746u /* "BLGF" */ +#define BOOT_LOG_VERSION 1u + +typedef struct boot_log_info boot_log_info; + +typedef int (*boot_log_flush_fn)(void); + +struct boot_log_info { + u32 magic; + u32 version; + char *buf; + u32 capacity; + u32 length; + u32 dropped; + boot_log_flush_fn flush; +}; + +static inline void boot_log_append(boot_log_info *bi, const char *s) { + if (!bi || bi->magic != BOOT_LOG_MAGIC || !s || !bi->buf) + return; + while (*s) { + if (bi->length < bi->capacity) + bi->buf[bi->length++] = *s; + else + bi->dropped++; + s++; + } +} + +static inline void boot_log_bind(boot_log_info **slot, u64 utsm_state) { + if (!slot) + return; + *slot = 0; + if (!utsm_state) + return; + boot_log_info *bi = (boot_log_info *)(u64)utsm_state; + if (bi && bi->magic == BOOT_LOG_MAGIC && bi->buf && bi->capacity) + *slot = bi; +} + +static inline int boot_log_try_flush(boot_log_info *bi) { + if (bi && bi->flush) + return bi->flush(); + return -1; +} + +#endif /* BOOT_LOG_H */ diff --git a/CODE/tools/fat32_write.h b/CODE/tools/fat32_write.h new file mode 100644 index 0000000..1d21835 --- /dev/null +++ b/CODE/tools/fat32_write.h @@ -0,0 +1,856 @@ +/* fat32_write.h - Deshab 共享 FAT32 写库(static inline,无库) + * + * 前置条件:包含者已定义 u8/u16/u32/u64/i64 基础类型。 + * 通过 f32w_init() 注入 block_read/block_write 函数指针(签名与 f32_/gpt_ 库一致)。 + * + * 与 fat32_io.h 的分工: + * fat32_io.h - 8.3 短名读写,依赖 256 扇区 BPB+FAT+根目录缓存窗口 + * fat32_write.h - LFN 长名创建 / mkdir / 目录簇链扩展,FAT 按需单扇区访问 + * + * 本库解决 fat32_io.h 的三个写缺口(大 ESP 场景,2GB: spc=8, FAT=4086 扇区, + * 根目录在分区 LBA 8204,均远超 256 扇区窗口): + * 1. LFN 生成:VFAT LFN 链(每项 13 UTF-16 字符,逆序存储,0x40 标志 + + * 短名 checksum),配套 ~N 数字尾巴的 8.3 短名生成与唯一化 + * 2. mkdir:分配目录簇 + 写入 "." / ".." 项 + 父目录插入 LFN 条目 + * 3. 目录簇链扩展:目录槽位不足时自动分配新簇并接入链尾(槽位可跨簇) + * + * FAT 访问策略:按需读改写单个 FAT 扇区(含缓存),修改后立即回写所有 FAT + * 副本(BPB->fc 份)。不依赖任何批量缓存窗口,任意簇号/FAT 偏移均可访问。 + * + * 用法: + * f32w_init(block_read, block_write) - 初始化(注入块设备) + * f32w_write_file(dir_clus, "user.conf", d, n) - 目录中写/替换长名文件 + * f32w_mkdir(dir_clus, "MyDir", &out_clus) - 创建子目录(LFN) + * f32w_resolve_dir("system/deshab64", &clus) - 按路径定位目录簇 + * f32w_write_path("system/deshab64/user.conf", d, n)- 按路径写文件(LFN) + * f32w_mkdir_path("system/新建目录", &clus) - 按路径建目录 + * f32w_find(dir_clus, name, want_dir, ...) - 按长名查找(LFN 优先) + * + * 缓冲区:每包含者约 9KB BSS(512 BPB + 512 FAT 扇区 + 簇缓冲 ×2)。 + * 与 fat32_io.h 可同时包含(前缀 f32w_ / f32_ 不冲突),但二者状态独立。 + */ + +#ifndef DESHAB_FAT32_WRITE_H +#define DESHAB_FAT32_WRITE_H + +#include "fat32_lfn.h" + +/* ---- block 设备函数类型(与 fat32_io.h / gpt_write.h 一致:index 固定 0) ---- */ +typedef int (*f32w_block_read_fn)(u32 index, u64 lba, u32 count, void *buf); +typedef int (*f32w_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf); + +static f32w_block_read_fn f32w_blk_read = 0; +static f32w_block_write_fn f32w_blk_write = 0; + +/* ---- 目录簇缓冲大小(含 data 暂存缓冲)。build.ps1 mkfs spc=8 -> 4096。 + * 包含者可在 include 前 #define F32W_CLUS_BYTES 覆盖(须为 512 的倍数)。 */ +#ifndef F32W_CLUS_BYTES +#define F32W_CLUS_BYTES 4096 +#endif + +#define F32W_EOC 0x0FFFFFFFu +#define F32W_EOC_VAL 0x0FFFFFFFu /* 写入 FAT 的 EOC 值(高 4 位保留位置 0x0Fxxxxxx 已含) */ + +/* ---- BPB(512B,与 f32_bpb 布局一致;独立定义避免依赖 fat32_io.h) ---- */ +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; +} f32w_bpb_t; + +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; +} f32w_dirent_t; + +/* ---- 挂载上下文(f32w_bpb_load 填充) ---- */ +typedef struct { + u32 spc; /* 每簇扇区数 */ + u32 clus_bytes; /* spc * 512 */ + u32 fat_lba; /* FAT 副本 0 起始 LBA(= rsvd) */ + u32 fat_copies; /* FAT 副本数(BPB->fc,实际至少 2) */ + u32 fat_sectors; /* 单份 FAT 扇区数(BPB->spf) */ + u32 data_lba; /* 数据区起始 LBA */ + u32 root_clus; /* 根目录首簇 */ + u32 fat_entries; /* FAT 项数上界 = spf*512/4 */ + int loaded; +} f32w_ctx; + +static f32w_ctx f32w_c; + +/* ---- 内部缓冲(每包含者独立) ---- */ +static u8 f32w_sec[512]; /* BPB / FSInfo 扇区缓冲 */ +static u8 f32w_fat[512]; /* FAT 单扇区缓存 */ +static u32 f32w_fat_sec_no = 0xFFFFFFFFu; /* 缓存的 FAT 扇区号(FAT 内偏移) */ +static u8 f32w_dir[F32W_CLUS_BYTES]; /* 目录簇缓存(槽位扫描/条目写入) */ +static u32 f32w_dir_no = 0; /* 缓存的目录簇号(0=无,簇号最小为 2) */ +static int f32w_dir_dirty = 0; +static u8 f32w_stage[F32W_CLUS_BYTES]; /* 数据写入暂存(尾簇零填充) */ +static u32 f32w_alloc_hint = 2; /* 空闲簇分配起点提示 */ + +/* ---- 初始化:注入 block_read/block_write 函数指针 ---- */ +static inline void f32w_init(f32w_block_read_fn rd, f32w_block_write_fn wr) { + f32w_blk_read = rd; + f32w_blk_write = wr; + f32w_c.loaded = 0; + f32w_fat_sec_no = 0xFFFFFFFFu; + f32w_dir_no = 0; + f32w_dir_dirty = 0; + f32w_alloc_hint = 2; +} + +static inline int f32w_read_sectors(u32 lba, u32 count, u8 *out) { + return f32w_blk_read ? f32w_blk_read(0, lba, count, out) : -1; +} +static inline int f32w_write_sectors(u32 lba, u32 count, const u8 *buf) { + return f32w_blk_write ? f32w_blk_write(0, lba, count, buf) : -1; +} + +/* ---- 小工具(无库约束:手写循环替代 mem*) ---- */ +static inline void f32w_mem_set(u8 *p, u8 v, u32 n) { for (u32 i = 0; i < n; i++) p[i] = v; } +static inline u32 f32w_r32(const u8 *p) { + return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); +} +static inline u16 f32w_r16(const u8 *p) { return (u16)p[0] | ((u16)p[1] << 8); } + +/* ---- BPB 加载 / 挂载上下文 ---- */ +static inline int f32w_bpb_load(void) { + if (f32w_c.loaded) return 0; + if (f32w_read_sectors(0, 1, f32w_sec) != 0) return -1; + const f32w_bpb_t *b = (const f32w_bpb_t *)f32w_sec; + if (b->boot_sig != 0xAA55 || b->bps != 512) return -2; + if (b->spf == 0 || b->root_clus < 2 || b->spc == 0) return -3; + u32 fc = b->fc ? b->fc : 1; + u32 cb = (u32)b->spc * 512; + if (cb > F32W_CLUS_BYTES) return -4; /* 簇超出缓冲,包含者应调大 F32W_CLUS_BYTES */ + f32w_c.spc = b->spc; + f32w_c.clus_bytes = cb; + f32w_c.fat_lba = b->rsvd; + f32w_c.fat_copies = fc; + f32w_c.fat_sectors = b->spf; + f32w_c.data_lba = b->rsvd + fc * b->spf; + f32w_c.root_clus = b->root_clus; + f32w_c.fat_entries = b->spf * 512 / 4; + f32w_c.loaded = 1; + f32w_fat_sec_no = 0xFFFFFFFFu; /* 新挂载,作废 FAT 缓存 */ + return 0; +} + +static inline u32 f32w_clus_lba(u32 clus) { + return f32w_c.data_lba + (clus - 2) * f32w_c.spc; +} + +/* ---- FAT 按需访问(单扇区缓存 + 全副本回写) ---- */ + +/* 读 FAT 项。返回 0 成功,*out 为 28 位簇值。 */ +static inline int f32w_fat_get(u32 clus, u32 *out) { + if (clus < 2 || clus >= f32w_c.fat_entries) return -1; + u32 sec = clus >> 7; /* 每扇区 128 项 */ + u32 off = (clus & 127) * 4; + if (sec != f32w_fat_sec_no) { + if (f32w_read_sectors(f32w_c.fat_lba + sec, 1, f32w_fat) != 0) return -2; + f32w_fat_sec_no = sec; + } + *out = f32w_r32(f32w_fat + off) & 0x0FFFFFFF; + return 0; +} + +/* 写 FAT 项(含 28 位掩码),立即回写所有 FAT 副本对应扇区。 */ +static inline int f32w_fat_set(u32 clus, u32 val) { + if (clus < 2 || clus >= f32w_c.fat_entries) return -1; + u32 sec = clus >> 7; + u32 off = (clus & 127) * 4; + if (sec != f32w_fat_sec_no) { + if (f32w_read_sectors(f32w_c.fat_lba + sec, 1, f32w_fat) != 0) return -2; + f32w_fat_sec_no = sec; + } + u32 old = f32w_r32(f32w_fat + off); + val &= 0x0FFFFFFF; + f32w_fat[off] = (u8)(val & 0xFF); + f32w_fat[off + 1] = (u8)((val >> 8) & 0xFF); + f32w_fat[off + 2] = (u8)((val >> 16) & 0xFF); + f32w_fat[off + 3] = (u8)((old & 0xF0) | ((val >> 24) & 0x0F)); + for (u32 f = 0; f < f32w_c.fat_copies; f++) { + u32 lba = f32w_c.fat_lba + f * f32w_c.fat_sectors + sec; + if (f32w_write_sectors(lba, 1, f32w_fat) != 0) return -3; + } + return 0; +} + +/* 分配一个空闲簇(线性扫描 + 起点提示轮转)。返回簇号,0=已满/失败。 */ +static inline u32 f32w_alloc_free(void) { + u32 total = f32w_c.fat_entries > 2 ? f32w_c.fat_entries - 2 : 0; + if (total == 0) return 0; + u32 start = (f32w_alloc_hint >= 2 && f32w_alloc_hint < f32w_c.fat_entries) + ? f32w_alloc_hint : 2; + for (u32 i = 0; i < total; i++) { + u32 c = 2 + ((start - 2 + i) % total); + u32 v = 0; + if (f32w_fat_get(c, &v) != 0) return 0; + if (v == 0) { + f32w_alloc_hint = c + 1; + return c; + } + } + return 0; +} + +/* 释放整条簇链(逐项清 0)。带环保护。 */ +static inline int f32w_free_chain(u32 clus) { + u32 cur = clus; + u64 guard = 0; + while (cur >= 2 && cur < 0x0FFFFFF8) { + if (++guard > (u64)f32w_c.fat_entries + 2) return -1; /* FAT 成环,损坏 */ + u32 next = 0; + if (f32w_fat_get(cur, &next) != 0) return -2; + if (f32w_fat_set(cur, 0) != 0) return -3; + cur = next; + } + return 0; +} + +/* ---- 目录簇缓存(槽位扫描 / 条目写入共用) ---- */ +static inline int f32w_dir_flush(void) { + if (!f32w_dir_dirty || f32w_dir_no == 0) { f32w_dir_dirty = 0; return 0; } + if (f32w_write_sectors(f32w_clus_lba(f32w_dir_no), f32w_c.spc, f32w_dir) != 0) return -1; + f32w_dir_dirty = 0; + return 0; +} + +static inline int f32w_dir_load(u32 clus) { + if (f32w_dir_no == clus && clus != 0) return 0; + if (f32w_dir_flush() != 0) return -1; + if (f32w_read_sectors(f32w_clus_lba(clus), f32w_c.spc, f32w_dir) != 0) return -2; + f32w_dir_no = clus; + return 0; +} + +/* 在 (clus, idx) 写入 32 字节目录项。 */ +static inline int f32w_entry_write(u32 clus, u32 idx, const u8 *e32) { + if (idx * 32 + 32 > f32w_c.clus_bytes) return -1; + if (f32w_dir_load(clus) != 0) return -2; + for (int i = 0; i < 32; i++) f32w_dir[idx * 32 + i] = e32[i]; + f32w_dir_dirty = 1; + return 0; +} + +/* 槽位步进:同簇内 idx+1,越界则沿 FAT 链进入下一簇(run 保证连续)。 */ +static inline int f32w_slot_step(u32 *clus, u32 *idx) { + (*idx)++; + if ((*idx) * 32 >= f32w_c.clus_bytes) { + u32 next = 0; + if (f32w_fat_get(*clus, &next) != 0) return -1; + if (next < 2 || next >= 0x0FFFFFF8) return -1; + *clus = next; + *idx = 0; + } + return 0; +} + +/* ---- 8.3 短名生成(含 ~N 唯一化) ---- */ + +static inline char f32w_up(char c) { return (c >= 'a' && c <= 'z') ? (char)(c - 32) : c; } + +/* 合法 8.3 短名字符(已转大写后):字母/数字/!#$%&'()-@^_`{}~ */ +static inline int f32w_char_ok_83(char c) { + if (c >= 'A' && c <= 'Z') return 1; + if (c >= '0' && c <= '9') return 1; + if (c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'') return 1; + if (c == '(' || c == ')' || c == '-' || c == '@' || c == '^' || c == '_') return 1; + if (c == '`' || c == '{' || c == '}' || c == '~') return 1; + return 0; +} + +/* 名字长度(手动 strlen)。 */ +static inline u32 f32w_strlen(const char *s) { + u32 n = 0; + while (s[n]) n++; + return n; +} + +/* 生成长名对应的 8.3 短名(未唯一化)。 + * name11 : 输出 11 字符短名(大写、空格填充;非法字符转 '_',超长截断) + * needs_lfn: 输出 1=长名无法无损表示为 8.3(需写 LFN 链 + 后续 ~N 唯一化) + * 返回 0 成功,-1 名字非法(空/过长/含非 ASCII/纯点)。 */ +static inline int f32w_make_short(const char *name, char name11[11], int *needs_lfn) { + u32 len = f32w_strlen(name); + if (len == 0 || len > 255) return -1; + u32 last_dot = 0xFFFFFFFFu; + u32 dot_count = 0; + int all_upper_valid = 1; + for (u32 i = 0; i < len; i++) { + char c = name[i]; + if ((u8)c < 0x20 || (u8)c >= 0x7F || c == '/') return -1; + if (c == '.') { last_dot = i; dot_count++; } + if (c >= 'a' && c <= 'z') all_upper_valid = 0; + else if (!f32w_char_ok_83(f32w_up(c))) all_upper_valid = 0; + } + /* 纯点 / 尾点 / 首空格:非法(简化处理,与 Windows 行为近似) */ + int non_dot = 0; + for (u32 i = 0; i < len; i++) if (name[i] != '.') { non_dot = 1; break; } + if (!non_dot || name[len - 1] == '.' || name[0] == ' ') return -1; + + u32 base_end = (last_dot == 0xFFFFFFFFu) ? len : last_dot; + u32 base_len = base_end; + u32 ext_len = (last_dot == 0xFFFFFFFFu) ? 0 : len - last_dot - 1; + + *needs_lfn = !(all_upper_valid && base_len <= 8 && ext_len <= 3 && dot_count <= 1); + + for (int i = 0; i < 11; i++) name11[i] = ' '; + u32 bi = 0; + for (u32 i = 0; i < base_len && bi < 8; i++) { + char c = f32w_up(name[i]); + if (!f32w_char_ok_83(c)) c = '_'; + name11[bi++] = c; + } + if (bi == 0) name11[bi++] = '_'; /* 空基名(如 ".config")用 '_' 兜底 */ + u32 ei = 0; + if (last_dot != 0xFFFFFFFFu) { + for (u32 i = 0; i < ext_len && ei < 3; i++) { + char c = f32w_up(name[last_dot + 1 + i]); + if (!f32w_char_ok_83(c)) c = '_'; + name11[8 + ei++] = c; + } + } + return 0; +} + +/* 目录中是否已存在指定 11 字符短名(仅比较短名项)。1=存在,0=不存在,-1=IO 错。 */ +static inline int f32w_dir_short_exists(u32 dir_clus, const char name11[11]) { + u32 dclus = dir_clus; + u64 guard = 0; + while (dclus >= 2 && dclus < 0x0FFFFFF8) { + if (++guard > (u64)f32w_c.fat_entries + 2) return -1; + if (f32w_dir_load(dclus) != 0) return -1; + u32 max_e = f32w_c.clus_bytes / 32; + for (u32 e = 0; e < max_e; e++) { + const u8 *ent = f32w_dir + e * 32; + if (ent[0] == 0x00) return 0; /* 目录结尾 */ + if (ent[0] == 0xE5) continue; + if (ent[11] == 0x0F) continue; /* LFN 项 */ + if (ent[11] & 0x08) continue; /* 卷标 */ + int same = 1; + for (int i = 0; i < 11; i++) + if (ent[i] != (u8)name11[i]) { same = 0; break; } + if (same) return 1; + } + u32 next = 0; + if (f32w_fat_get(dclus, &next) != 0) return -1; + dclus = next; + } + return 0; +} + +/* 生成长名对应的唯一 8.3 短名(lossy 时追加 ~1..~999999 数字尾巴)。 + * 返回 0 成功,负值失败。 */ +static inline int f32w_make_unique_short(u32 dir_clus, const char *name, char name11[11]) { + int needs_lfn = 0; + if (f32w_make_short(name, name11, &needs_lfn) != 0) return -1; + if (!needs_lfn) return 0; /* 无损 8.3,同名冲突由 f32w_find 语义覆盖 */ + /* 提取 make_short 产生的 base / ext,枚举 BASE~N 直到唯一 */ + char base[8]; + u32 base_len = 0; + for (u32 i = 0; i < 8 && name11[i] != ' '; i++) base[base_len++] = name11[i]; + if (base_len == 0) { base[0] = '_'; base_len = 1; } + for (u32 n = 1; n <= 999999u; n++) { + char digits[8]; + int dn = 0; + u32 v = n; + while (v) { digits[dn++] = (char)('0' + (v % 10)); v /= 10; } + u32 bl = (dn + 1 >= 8) ? 1 : 8 - dn - 1; /* 保留 '~'+数字 的空间 */ + if (base_len < bl) bl = base_len; + for (u32 i = 0; i < 8; i++) name11[i] = ' '; + for (u32 i = 0; i < bl; i++) name11[i] = base[i]; + u32 p = bl; + name11[p++] = '~'; + for (int i = dn - 1; i >= 0; i--) name11[p++] = digits[i]; + int ex = f32w_dir_short_exists(dir_clus, name11); + if (ex < 0) return -2; + if (ex == 0) return 0; + } + return -3; /* 序号耗尽 */ +} + +/* ---- LFN 条目生成 ---- */ + +/* 13 个 UTF-16 槽位在 32 字节目录项内的字节偏移(name1[5] name2[6] name3[2]) */ +static inline int f32w_lfn_slot_off(int slot) { + if (slot < 5) return 1 + slot * 2; + if (slot < 11) return 14 + (slot - 5) * 2; + return 28 + (slot - 11) * 2; +} + +/* 构造第 ordinal 个 LFN 片段项(ordinal 1..total,total 为链长)。 + * name 为 ASCII 长名,len 为字符数。out32 接收 32 字节编码。 + * 最高序号项(ordinal==total)带 0x40 标志,含 0x0000 终止 + 0xFFFF 填充。 + * 返回 0 成功。 */ +static inline int f32w_build_lfn_entry(int ordinal, int total, const char *name, + u32 len, u8 cksum, u8 *out32) { + f32w_mem_set(out32, 0, 32); + /* 全槽位预填 0xFFFF */ + for (int s = 0; s < 13; s++) { + int off = f32w_lfn_slot_off(s); + out32[off] = 0xFF; + out32[off + 1] = 0xFF; + } + u32 base = (u32)(ordinal - 1) * 13; + u32 cnt = (len - base) < 13 ? (len - base) : 13; + for (u32 j = 0; j < cnt; j++) { + int off = f32w_lfn_slot_off((int)j); + u16 c = (u16)(u8)name[base + j]; /* ASCII -> UTF-16 */ + out32[off] = (u8)(c & 0xFF); + out32[off + 1] = (u8)(c >> 8); + } + if (ordinal == total && cnt < 13) { + int off = f32w_lfn_slot_off((int)cnt); /* 0x0000 终止符 */ + out32[off] = 0; + out32[off + 1] = 0; + } + out32[0] = (u8)(ordinal | (ordinal == total ? 0x40 : 0x00)); + out32[11] = 0x0F; /* ATTR_LONG_NAME */ + out32[12] = 0; /* type */ + out32[13] = cksum; + out32[26] = 0; /* 首簇号必须为 0 */ + out32[27] = 0; + return 0; +} + +/* 构造 8.3 短名项。 */ +static inline void f32w_build_short_entry(const char name11[11], u8 attr, + u32 clus, u32 size, u8 *out32) { + f32w_mem_set(out32, 0, 32); + for (int i = 0; i < 11; i++) out32[i] = (u8)name11[i]; + out32[11] = attr; + /* 时间戳字段保持 0(与 fat32_io.h 写路径一致) */ + out32[21] = (u8)((clus >> 16) & 0xFF); /* chigh */ + out32[20] = (u8)((clus >> 24) & 0xFF); + out32[26] = (u8)(clus & 0xFF); /* clow */ + out32[27] = (u8)((clus >> 8) & 0xFF); + out32[28] = (u8)(size & 0xFF); /* fsize */ + out32[29] = (u8)((size >> 8) & 0xFF); + out32[30] = (u8)((size >> 16) & 0xFF); + out32[31] = (u8)((size >> 24) & 0xFF); +} + +/* ---- 目录槽位分配(含簇链扩展,槽位可跨簇) ---- */ + +/* 在目录簇链中找 count 个连续空闲槽位(0x00/0xE5)。链尾不足时自动分配 + * 新簇:FAT 接链 + 新簇清零 + 落盘,槽位 run 跨簇继续累计。 + * out_clus/out_idx : run 首槽位位置(簇号 + 簇内项索引) + * 返回 0 成功,负值失败(-3=磁盘满)。 */ +static inline int f32w_dir_alloc_slots(u32 dir_clus, u32 count, + u32 *out_clus, u32 *out_idx) { + u32 run_clus = 0, run_idx = 0, run = 0; + u32 dclus = dir_clus; + u64 guard = 0; + while (dclus >= 2 && dclus < 0x0FFFFFF8) { + if (++guard > (u64)f32w_c.fat_entries + 2) return -1; /* FAT 成环 */ + if (f32w_dir_load(dclus) != 0) return -2; + u32 max_e = f32w_c.clus_bytes / 32; + for (u32 e = 0; e < max_e; e++) { + u8 first = f32w_dir[e * 32]; + if (first == 0x00 || first == 0xE5) { + if (run == 0) { run_clus = dclus; run_idx = e; } + run++; + if (run >= count) { + *out_clus = run_clus; + *out_idx = run_idx; + return 0; + } + } else { + run = 0; + } + } + u32 next = 0; + if (f32w_fat_get(dclus, &next) != 0) return -2; + if (next >= 2 && next < 0x0FFFFFF8) { dclus = next; continue; } + /* 链尾且 run 不足:扩展一个新簇接入链尾 */ + u32 nc = f32w_alloc_free(); + if (nc == 0) return -3; + if (f32w_fat_set(dclus, nc) != 0) return -4; + if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -4; + if (f32w_dir_flush() != 0) return -5; + f32w_mem_set(f32w_dir, 0, f32w_c.clus_bytes); /* 新簇全 0 */ + f32w_dir_no = nc; + f32w_dir_dirty = 1; /* 脏标记使 flush 落盘清零内容 */ + if (f32w_dir_flush() != 0) return -5; + dclus = nc; + /* run 跨簇继续累计(新簇全 0x00,下一轮循环继续计数) */ + } + return -6; +} + +/* ---- 按长名查找(LFN 优先,8.3 回退;大小写不敏感) ---- */ + +/* 在目录中查找 name(长名或 8.3 显示名)。 + * want_dir : -1 任意,0 只匹配文件,1 只匹配目录 + * out_clus/size : 目标首簇 / 大小(可 NULL) + * out_ent_clus/idx : 短名项位置(可 NULL,用于原地更新) + * out_attr : 目录项属性(可 NULL) + * 返回 0 找到,-1 未找到,其他负值 IO 错。LFN 缓冲跨簇存活(链可跨簇)。 */ +static inline int f32w_find(u32 dir_clus, const char *name, int want_dir, + u32 *out_clus, u32 *out_size, + u32 *out_ent_clus, u32 *out_ent_idx, u8 *out_attr) { + if (dir_clus == 0) dir_clus = f32w_c.root_clus; + u32 dclus = dir_clus; + u64 guard = 0; + fat32_lfn_buf lfn; + fat32_lfn_init(&lfn); + while (dclus >= 2 && dclus < 0x0FFFFFF8) { + if (++guard > (u64)f32w_c.fat_entries + 2) return -2; + if (f32w_dir_load(dclus) != 0) return -3; + u32 max_e = f32w_c.clus_bytes / 32; + for (u32 e = 0; e < max_e; e++) { + const u8 *ent = f32w_dir + e * 32; + if (ent[0] == 0x00) return -1; /* 目录结尾,未找到 */ + if (ent[0] == 0xE5) { fat32_lfn_init(&lfn); continue; } + const f32w_dirent_t *de = (const f32w_dirent_t *)ent; + int is_short = fat32_lfn_process(&lfn, ent); + if (!is_short) continue; + if (de->attr & 0x08) { fat32_lfn_init(&lfn); continue; } /* 卷标 */ + if (de->name[0] == '.' && (de->name[1] == ' ' || de->name[1] == '.')) { + fat32_lfn_init(&lfn); /* "." / ".." */ + continue; + } + int entry_is_dir = (de->attr & 0x10) ? 1 : 0; + if (want_dir >= 0 && entry_is_dir != want_dir) { + fat32_lfn_init(&lfn); + continue; + } + int matched = 0; + if (lfn.valid) { + char ascii[FAT32_LFN_MAX]; + if (fat32_lfn_to_ascii(&lfn, ascii, sizeof(ascii)) >= 0 && + fat32_lfn_streq_ci(ascii, name)) matched = 1; + } + if (!matched) { + char disp[13]; + fat32_lfn_short_to_str((const u8 *)de->name, disp); + if (fat32_lfn_streq_ci(disp, name)) matched = 1; + } + if (matched) { + u32 fc = (u32)f32w_r16((const u8 *)&de->clow) | + ((u32)f32w_r16((const u8 *)&de->chigh) << 16); + if (out_clus) *out_clus = fc; + if (out_size) *out_size = de->fsize; + if (out_ent_clus) *out_ent_clus = dclus; + if (out_ent_idx) *out_ent_idx = e; + if (out_attr) *out_attr = de->attr; + return 0; + } + fat32_lfn_init(&lfn); + } + u32 next = 0; + if (f32w_fat_get(dclus, &next) != 0) return -3; + dclus = next; + } + return -1; +} + +/* ---- 文件数据簇链写入(新建 / 复用扩展 / 截断收缩) ---- */ + +/* 写 size 字节数据。have>=2 时复用现有链(按需扩展或截断并释放多余簇), + * have<2 时全新分配。*out_first 输出结果首簇(size==0 时为 0)。 + * 返回 0 成功,负值失败。 */ +static inline int f32w_data_write(u32 have, const u8 *data, u32 size, u32 *out_first) { + u32 cb = f32w_c.clus_bytes; + u32 need = size ? (size + cb - 1) / cb : 0; + + if (need == 0) { + if (have >= 2) { + if (f32w_free_chain(have) != 0) return -1; + } + *out_first = 0; + return 0; + } + + u32 first = 0, prev = 0, count = 0; + if (have >= 2) { + first = have; + u32 cur = have; + while (cur >= 2 && cur < 0x0FFFFFF8 && count < need) { + prev = cur; + u32 next = 0; + if (f32w_fat_get(cur, &next) != 0) return -2; + cur = next; + count++; + } + while (count < need) { /* 现有链不足:扩展 */ + u32 nc = f32w_alloc_free(); + if (nc == 0) return -3; + if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -4; + if (f32w_fat_set(prev, nc) != 0) return -4; + prev = nc; + count++; + } + if (cur >= 2 && cur < 0x0FFFFFF8) { /* 现有链过长:截断 */ + if (f32w_fat_set(prev, F32W_EOC_VAL) != 0) return -4; + if (f32w_free_chain(cur) != 0) return -5; + } + } else { + for (u32 i = 0; i < need; i++) { /* 全新分配 */ + u32 nc = f32w_alloc_free(); + if (nc == 0) return -3; + if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -4; + if (prev) { + if (f32w_fat_set(prev, nc) != 0) return -4; + } else { + first = nc; + } + prev = nc; + } + count = need; + } + + /* 写数据:整簇直写源缓冲,尾簇经暂存缓冲零填充 */ + u32 remaining = size; + const u8 *src = data; + u32 c = first; + for (u32 i = 0; i < count && remaining > 0; i++) { + u32 chunk = remaining < cb ? remaining : cb; + u32 lba = f32w_clus_lba(c); + if (chunk == cb) { + if (f32w_write_sectors(lba, f32w_c.spc, src) != 0) return -6; + } else { + f32w_mem_set(f32w_stage, 0, cb); + for (u32 b = 0; b < chunk; b++) f32w_stage[b] = src[b]; + if (f32w_write_sectors(lba, f32w_c.spc, f32w_stage) != 0) return -6; + } + src += chunk; + remaining -= chunk; + if (i + 1 < count) { + u32 next = 0; + if (f32w_fat_get(c, &next) != 0) return -2; + c = next; + } + } + *out_first = first; + return 0; +} + +/* ---- FSInfo 置未知(写操作后调用,best-effort) ---- + * 分配/释放后空闲簇数不再准确,置 0xFFFFFFFF(未知)避免 UEFI/Windows + * 依赖陈旧计数。忽略失败(无 FSInfo 的镜像不受影响)。 */ +static inline void f32w_fsinfo_unknown(void) { + if (!f32w_c.loaded) return; + u32 fsi_lba = 0; + { + /* 重新读 BPB 拿 FSInfo 扇区号(f32w_sec 此后未被覆盖过也可直接用) */ + const f32w_bpb_t *b = (const f32w_bpb_t *)f32w_sec; + if (b->boot_sig != 0xAA55) return; + fsi_lba = b->fsi; + } + if (fsi_lba == 0 || fsi_lba == 0xFFFF) return; + u8 sec[512]; + if (f32w_read_sectors(fsi_lba, 1, sec) != 0) return; + sec[488] = 0xFF; sec[489] = 0xFF; sec[490] = 0xFF; sec[491] = 0xFF; + sec[492] = 0xFF; sec[493] = 0xFF; sec[494] = 0xFF; sec[495] = 0xFF; + (void)f32w_write_sectors(fsi_lba, 1, sec); +} + +/* ---- 写入 LFN 链 + 短名项(dir_alloc_slots 已定位 run 起点) ---- */ +static inline int f32w_insert_entries(u32 dir_clus, const char *name, + const char name11[11], u32 clus, u32 size, u8 attr) { + u32 len = f32w_strlen(name); + int needs_lfn = 0; + /* needs_lfn 与调用方 make_unique_short 判定一致:重算(无损则短名回退匹配) */ + { + char tmp11[11]; + if (f32w_make_short(name, tmp11, &needs_lfn) != 0) return -1; + } + u32 nlfn = needs_lfn ? (len + 12) / 13 : 0; + u32 sc = 0, si = 0; + if (f32w_dir_alloc_slots(dir_clus, nlfn + 1, &sc, &si) != 0) return -2; + if (needs_lfn) { + u8 ck = fat32_lfn_checksum((const u8 *)name11); + for (int k = (int)nlfn; k >= 1; k--) { /* 逆序:最高序号最先落盘 */ + u8 e[32]; + f32w_build_lfn_entry(k, (int)nlfn, name, len, ck, e); + if (f32w_entry_write(sc, si, e) != 0) return -3; + if (k > 1 && f32w_slot_step(&sc, &si) != 0) return -4; + } + if (nlfn > 0 && f32w_slot_step(&sc, &si) != 0) return -4; + } + u8 se[32]; + f32w_build_short_entry(name11, attr, clus, size, se); + if (f32w_entry_write(sc, si, se) != 0) return -3; + if (f32w_dir_flush() != 0) return -5; + return 0; +} + +/* ================= 公共 API ================= */ + +/* 在目录中写/替换长名文件(LFN 创建 + 短名 ~N 唯一化 + FAT 链管理)。 + * dir_clus : 目标目录首簇(0=根目录) + * name : 长文件名(ASCII;非 8.3 无损表示时自动生成 LFN 链) + * data/size: 文件内容(size 可为 0,簇链释放、大小清 0) + * 同名存在:文件原地替换内容(保留原目录项/LFN),目录返回 -20。 + * 返回 0 成功,负值失败。 */ +static inline int f32w_write_file(u32 dir_clus, const char *name, + const u8 *data, u32 size) { + if (f32w_bpb_load() != 0) return -1; + if (!f32w_blk_write) return -2; + if (!name || (!data && size)) return -3; + if (dir_clus == 0) dir_clus = f32w_c.root_clus; + + u32 e_clus = 0, e_size = 0, ent_clus = 0, ent_idx = 0; + u8 e_attr = 0; + int fr = f32w_find(dir_clus, name, -1, &e_clus, &e_size, &ent_clus, &ent_idx, &e_attr); + if (fr == 0) { + if (e_attr & 0x10) return -20; /* 同名目录已存在 */ + /* 原地替换:复用/扩展/截断原簇链,仅更新短名项 clus/size */ + u32 first = 0; + if (f32w_data_write(e_clus, data, size, &first) != 0) return -4; + if (f32w_dir_load(ent_clus) != 0) return -5; + u8 *ent = f32w_dir + ent_idx * 32; + ent[20] = (u8)((first >> 24) & 0xFF); /* chigh */ + ent[21] = (u8)((first >> 16) & 0xFF); + ent[26] = (u8)(first & 0xFF); /* clow */ + ent[27] = (u8)((first >> 8) & 0xFF); + ent[28] = (u8)(size & 0xFF); /* fsize */ + ent[29] = (u8)((size >> 8) & 0xFF); + ent[30] = (u8)((size >> 16) & 0xFF); + ent[31] = (u8)((size >> 24) & 0xFF); + f32w_dir_dirty = 1; + if (f32w_dir_flush() != 0) return -5; + f32w_fsinfo_unknown(); + return 0; + } + + /* 新建:唯一短名 -> 数据链 -> 目录槽位(含链扩展)-> LFN+短名项 */ + char name11[11]; + if (f32w_make_unique_short(dir_clus, name, name11) != 0) return -6; + u32 first = 0; + if (f32w_data_write(0, data, size, &first) != 0) return -7; + if (f32w_insert_entries(dir_clus, name, name11, first, size, 0x20) != 0) return -8; + f32w_fsinfo_unknown(); + return 0; +} + +/* 在目录中创建子目录(LFN + "." / ".." 项 + 父目录插入条目)。 + * parent_clus: 父目录首簇(0=根目录) + * out_clus : 输出新目录首簇(可 NULL) + * 返回 0 成功,-20 同名已存在,其他负值失败。 */ +static inline int f32w_mkdir(u32 parent_clus, const char *name, u32 *out_clus) { + if (f32w_bpb_load() != 0) return -1; + if (!f32w_blk_write) return -2; + if (!name) return -3; + if (parent_clus == 0) parent_clus = f32w_c.root_clus; + + int fr = f32w_find(parent_clus, name, -1, 0, 0, 0, 0, 0); + if (fr == 0) return -20; /* 同名已存在 */ + + char name11[11]; + if (f32w_make_unique_short(parent_clus, name, name11) != 0) return -6; + + /* 分配目录簇并初始化 "." / ".." */ + u32 nc = f32w_alloc_free(); + if (nc == 0) return -21; + if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -22; + if (f32w_dir_flush() != 0) return -23; + f32w_mem_set(f32w_dir, 0, f32w_c.clus_bytes); + { + u8 dot[32], dotdot[32]; + char n_self[11] = {'.',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; + char n_up[11] = {'.','.',' ',' ',' ',' ',' ',' ',' ',' ',' '}; + u32 parent_link = (parent_clus == f32w_c.root_clus) ? 0 : parent_clus; + f32w_build_short_entry(n_self, 0x10, nc, 0, dot); + f32w_build_short_entry(n_up, 0x10, parent_link, 0, dotdot); + for (int i = 0; i < 32; i++) { f32w_dir[i] = dot[i]; f32w_dir[32 + i] = dotdot[i]; } + } + f32w_dir_no = nc; + f32w_dir_dirty = 1; + if (f32w_dir_flush() != 0) return -23; + + /* 父目录插入条目(含链扩展) */ + if (f32w_insert_entries(parent_clus, name, name11, nc, 0, 0x10) != 0) return -8; + f32w_fsinfo_unknown(); + if (out_clus) *out_clus = nc; + return 0; +} + +/* ---- 路径辅助('/' 分隔,每段长名;中间目录必须已存在) ---- */ + +/* 定位路径目录簇。空路径/"'/'" 返回根目录。 */ +static inline int f32w_resolve_dir(const char *path, u32 *out_clus) { + if (!path || !out_clus) return -1; + if (f32w_bpb_load() != 0) return -2; + u32 cur = f32w_c.root_clus; + u32 pos = 0; + u32 path_len = f32w_strlen(path); + 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 = 0; + if (f32w_find(cur, comp, 1, &sub, 0, 0, 0, 0) != 0) return -3; + cur = sub; + } + *out_clus = cur; + return 0; +} + +/* 按路径写文件(父目录必须已存在)。返回 0 成功,负值失败。 */ +static inline int f32w_write_path(const char *path, const u8 *data, u32 size) { + if (!path) return -1; + if (f32w_bpb_load() != 0) return -2; + u32 path_len = f32w_strlen(path); + 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) { + dir_clus = f32w_c.root_clus; + } else { + char dirpath[260]; + u32 dn = last_slash; + if (dn >= sizeof(dirpath)) return -3; + for (u32 i = 0; i < dn; i++) dirpath[i] = path[i]; + dirpath[dn] = 0; + if (f32w_resolve_dir(dirpath, &dir_clus) != 0) return -4; + } + return f32w_write_file(dir_clus, fname, data, size); +} + +/* 按路径创建目录(仅最后一段被创建,中间目录必须已存在)。返回 0 成功。 */ +static inline int f32w_mkdir_path(const char *path, u32 *out_clus) { + if (!path) return -1; + if (f32w_bpb_load() != 0) return -2; + u32 path_len = f32w_strlen(path); + if (path_len == 0) return -3; + /* 去尾部 '/' */ + while (path_len > 0 && path[path_len - 1] == '/') path_len--; + if (path_len == 0) return -3; + u32 last_slash = 0xFFFFFFFFu; + for (u32 i = 0; i < path_len; i++) + if (path[i] == '/') last_slash = i; + const char *dname = (last_slash == 0xFFFFFFFFu) ? path : path + last_slash + 1; + if ((u32)(dname - path) >= path_len) return -3; /* 防御:末段为空 */ + u32 parent_clus = 0; + if (last_slash == 0xFFFFFFFFu) { + parent_clus = f32w_c.root_clus; + } else { + char dirpath[260]; + u32 dn = last_slash; + if (dn >= sizeof(dirpath)) return -4; + for (u32 i = 0; i < dn; i++) dirpath[i] = path[i]; + dirpath[dn] = 0; + if (f32w_resolve_dir(dirpath, &parent_clus) != 0) return -5; + } + return f32w_mkdir(parent_clus, dname, out_clus); +} + +#endif /* DESHAB_FAT32_WRITE_H */ \ No newline at end of file diff --git a/CODE/tools/fileman/main.c b/CODE/tools/fileman/main.c index e7526ad..ac18714 100644 --- a/CODE/tools/fileman/main.c +++ b/CODE/tools/fileman/main.c @@ -305,11 +305,28 @@ static void go_up(void) { } } -/* 查找并进入 SYSTEM/user 目录 */ +/* 查找并进入用户目录。 + * 布局探测顺序(磁盘布局约定不同,两种镜像并存): + * 1) 根级 USER/ —— 树形布局(dev 镜像,含 DESKTOP/PICTURE/VEDIO) + * 2) SYSTEM/USER/ —— 旧树形布局(兼容遗留镜像) + * 3) 都不存在 —— 扁平布局(SATA 盘,用户文件直接在根目录,停留根目录即可) + * 匹配用 8.3 显示名(f32_name_from_83 已 trim 空格),如 "USER"/"SYSTEM"。 */ static void find_user_dir(void) { - /* 先查找 SYSTEM 目录 */ + /* 1) 根级 USER */ file_count = 0; f32_list_dir(0, fm_collect_cb, 0); + for (int i = 0; i < file_count; i++) { + if (files[i].is_dir && files[i].name[0] == 'U' && files[i].name[1] == 'S' && + files[i].name[2] == 'E' && files[i].name[3] == 'R') { + path_clus[0] = files[i].clus; + path_name[0][0] = 'U'; path_name[0][1] = 'S'; path_name[0][2] = 'E'; + path_name[0][3] = 'R'; path_name[0][4] = 0; + path_depth = 1; + return; + } + } + + /* 2) 旧树形布局 SYSTEM/USER */ u32 system_clus = 0; for (int i = 0; i < file_count; i++) { if (files[i].is_dir && files[i].name[0] == 'S' && files[i].name[1] == 'Y' && @@ -319,15 +336,14 @@ static void find_user_dir(void) { break; } } - if (!system_clus) return; /* SYSTEM 目录不存在,保持根目录 */ + if (!system_clus) return; /* 扁平布局,保持根目录 */ - /* 在 SYSTEM 中查找 user 目录 */ file_count = 0; f32_list_dir(system_clus, fm_collect_cb, 0); for (int i = 0; i < file_count; i++) { if (files[i].is_dir && files[i].name[0] == 'U' && files[i].name[1] == 'S' && files[i].name[2] == 'E' && files[i].name[3] == 'R') { - /* 找到了 SYSTEM/user,设置路径栈 */ + /* 找到了 SYSTEM/USER,设置路径栈 */ path_clus[0] = system_clus; path_name[0][0] = 'S'; path_name[0][1] = 'Y'; path_name[0][2] = 'S'; path_name[0][3] = 'T'; path_name[0][4] = 'E'; path_name[0][5] = 'M'; @@ -339,6 +355,7 @@ static void find_user_dir(void) { return; } } + /* SYSTEM 存在但无 SYSTEM/USER:停留根目录(保持原行为) */ } __attribute__((visibility("default"))) @@ -359,7 +376,7 @@ void dsk_entry(const da_boot_context *ctx) { f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write); path_depth = 0; - /* 尝试进入 SYSTEM/user 目录 */ + /* 查找用户目录:自动适配树形(根级 USER/)与扁平(根目录直放)两种布局 */ find_user_dir(); load_current_dir(); redraw_all(); diff --git a/CODE/tools/gpt_write.h b/CODE/tools/gpt_write.h new file mode 100644 index 0000000..ca9855e --- /dev/null +++ b/CODE/tools/gpt_write.h @@ -0,0 +1,406 @@ +/* gpt_write.h — Deshab 共享 GPT 写库(static inline,无库) + * + * 前置条件:包含者已定义 u8/u16/u32/u64/i64 基础类型。 + * 通过 gpt_init() 注入 block_read/block_write 函数指针(签名与 f32 库一致)。 + * + * 能力: + * gpt_probe() — 读 LBA1 校验 "EFI PART" 签名 + header CRC(轻量探测) + * gpt_load() — 加载 GPT 头 + 分区表到内部缓冲,并做 CRC 校验 + * gpt_find_esp() — 查找 ESP 分区(type GUID c12a7328-...),输出起始/结束 LBA + * gpt_find_part() — 按任意 type GUID 查找分区 + * gpt_update_esp() — 新增/更新 ESP 分区项(改起始/结束 LBA) + * gpt_commit() — 重算 entries CRC + 双 header CRC,写主表/备份表/主头/备份头 + * gpt_blank_disk() — 全新初始化:protective MBR + 空 GPT(主+备份,无分区) + * + * 与 build.ps1(New-GptHeader / New-GptFat32Image)字节兼容: + * LBA0 protective MBR(type 0xEE),LBA1 主头,LBA2 主表(128×128B = 32 扇区), + * 备份表 lastLba-32,备份头 lastLba。GUID 按 .NET Guid.ToByteArray() + * little-endian 编码(前三组小端、后两组原序)。 + * CRC32 为 IEEE 802.3(init 0xFFFFFFFF / poly 0xEDB88320 / xorout 0xFFFFFFFF), + * 与 build.ps1 Get-Crc32 逐位实现等价。 + * + * 缓冲:包含者需提供 ~16.5KB BSS(1 扇区头缓冲 + 32 扇区分区表缓冲)。 + * + * 典型流程(FirstInit 写盘 / 系统格式化): + * gpt_init(block_read, block_write); + * if (gpt_probe() != 0) gpt_blank_disk(total_sectors, NULL); // 空盘初始化 + * gpt_load(); + * gpt_update_esp(2048, total_sectors - 34); // 或扩容 + * gpt_commit(); + */ + +#ifndef DESHAB_GPT_WRITE_H +#define DESHAB_GPT_WRITE_H + +/* ---- block 设备函数类型(与 fat32_io.h 签名一致,index 固定 0) ---- */ +typedef int (*gpt_block_read_fn)(u32 index, u64 lba, u32 count, void *buf); +typedef int (*gpt_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf); + +static gpt_block_read_fn gpt_blk_read = 0; +static gpt_block_write_fn gpt_blk_write = 0; + +/* ---- GPT 常量(与 build.ps1 一致) ---- */ +#define GPT_SIG_BYTES 8 +#define GPT_ENTRIES 128u +#define GPT_ENTRY_SIZE 128u +#define GPT_ENTRY_SECTORS 32u /* 128×128 / 512 */ +#define GPT_FIRST_USABLE 34u /* 2 + 32 */ +#define GPT_HEADER_SIZE 92u +#define GPT_REVISION 0x00010000u +#define GPT_MBR_PART_OFF 446 /* protective MBR 分区项偏移 */ + +/* ---- GPT 头(512B,build.ps1 Set-Le* 布局) ---- */ +typedef struct __attribute__((packed)) { + u8 sig[8]; /* "EFI PART" */ + u32 revision; /* 0x00010000 */ + u32 header_size; /* 92 */ + u32 header_crc; + u32 reserved0; + u64 current_lba; /* 主头=1,备份头=lastLba */ + u64 backup_lba; + u64 first_usable; /* 34 */ + u64 last_usable; /* lastLba - 33 */ + u8 disk_guid[16]; + u64 entries_lba; /* 主表=2,备份表=lastLba-32 */ + u32 entries_count; /* 128 */ + u32 entries_size; /* 128 */ + u32 entries_crc; + u8 reserved[420]; +} gpt_header; /* 512B */ + +/* ---- 分区项(128B) ---- */ +typedef struct __attribute__((packed)) { + u8 type_guid[16]; /* 全 0 = 空槽 */ + u8 uniq_guid[16]; + u64 first_lba; + u64 last_lba; + u64 attrs; + u16 name[36]; /* UTF-16LE,未用部分为 0 */ +} gpt_part_entry; + +/* ESP 类型 GUID:c12a7328-f81f-11d2-ba4b-00a0c93ec93b + * (.NET Guid.ToByteArray() 编码:前三组小端、后两组原序) */ +static const u8 gpt_guid_esp[16] = { + 0x28, 0x73, 0x2a, 0xc1, /* c12a7328 */ + 0x1f, 0xf8, /* f81f */ + 0xd2, 0x11, /* 11d2 */ + 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b +}; + +/* ---- 内部缓冲(每包含者独立) ---- */ +static u8 gpt_sec[512]; /* 单扇区(头/MBR)缓冲 */ +static u8 gpt_tbl[GPT_ENTRY_SECTORS * 512]; /* 分区表缓冲(32 扇区) */ +static u64 gpt_disk_sectors = 0; /* 磁盘总扇区数(探测后已知) */ + +/* ---- 初始化:注入 block_read/block_write 函数指针 ---- */ +static inline void gpt_init(gpt_block_read_fn rd, gpt_block_write_fn wr) { + gpt_blk_read = rd; + gpt_blk_write = wr; + gpt_disk_sectors = 0; +} + +static inline int gpt_read_sectors(u64 lba, u32 count, u8 *out) { + return gpt_blk_read ? gpt_blk_read(0, lba, count, out) : -1; +} +static inline int gpt_write_sectors(u64 lba, u32 count, const u8 *buf) { + return gpt_blk_write ? gpt_blk_write(0, lba, count, buf) : -1; +} + +/* ---- 小工具(无库约束:手写循环替代 mem*) ---- */ +static inline void gpt_mem_set(u8 *p, u8 v, u32 n) { for (u32 i = 0; i < n; i++) p[i] = v; } +static inline void gpt_mem_cpy(u8 *d, const u8 *s, u32 n) { for (u32 i = 0; i < n; i++) d[i] = s[i]; } +static inline int gpt_mem_eq(const u8 *a, const u8 *b, u32 n) { + for (u32 i = 0; i < n; i++) if (a[i] != b[i]) return 0; + return 1; +} +static inline u32 gpt_r32(const u8 *p) { + return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); +} +static inline void gpt_w16(u8 *p, u16 v) { p[0] = (u8)v; p[1] = (u8)(v >> 8); } +static inline void gpt_w32(u8 *p, u32 v) { + p[0] = (u8)v; p[1] = (u8)(v >> 8); p[2] = (u8)(v >> 16); p[3] = (u8)(v >> 24); +} +static inline void gpt_w64(u8 *p, u64 v) { + gpt_w32(p, (u32)v); gpt_w32(p + 4, (u32)(v >> 32)); +} + +/* ---- CRC32(IEEE 802.3,与 build.ps1 Get-Crc32 逐位实现等价;表驱动) ---- */ +static u32 gpt_crc_tab[256]; +static int gpt_crc_ready = 0; + +static inline void gpt_crc_init(void) { + if (gpt_crc_ready) return; + for (u32 i = 0; i < 256; i++) { + u32 c = i; + for (int k = 0; k < 8; k++) + c = (c & 1) ? (c >> 1) ^ 0xEDB88320u : (c >> 1); + gpt_crc_tab[i] = c; + } + gpt_crc_ready = 1; +} + +static inline u32 gpt_crc32(const u8 *p, u32 n) { + u32 crc = 0xFFFFFFFFu; + for (u32 i = 0; i < n; i++) + crc = (crc >> 8) ^ gpt_crc_tab[(crc ^ p[i]) & 0xFFu]; + return crc ^ 0xFFFFFFFFu; +} + +/* ---- GUID 工具 ---- */ + +/* "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" → 16B(.NET ToByteArray 编码) + * 返回 0 成功,-1 格式错误 */ +static inline int gpt_guid_from_str(const char *s, u8 out[16]) { + static const int hex[256] = { ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, + ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, + ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, + ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15 }; + /* 位置表:字符串下标 → 字节下标(1=前四组反序,0=后两组原序) */ + static const struct { int str; int byte; int rev; } map[16] = { + { 0, 3, 1 }, { 2, 2, 1 }, { 4, 1, 1 }, { 6, 0, 1 }, + { 9, 5, 1 }, { 11, 4, 1 }, + { 14, 7, 1 }, { 16, 6, 1 }, + { 19, 8, 0 }, { 21, 9, 0 }, { 23, 10, 0 }, { 25, 11, 0 }, + { 28, 12, 0 }, { 30, 13, 0 }, { 32, 14, 0 }, { 34, 15, 0 }, + }; + if (s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') return -1; + for (int i = 0; i < 16; i++) { + int hi = hex[(u8)s[map[i].str]]; + int lo = hex[(u8)s[map[i].str + 1]]; + if (hi < 0 || lo < 0) return -1; + (void)0; + } + for (int i = 0; i < 16; i++) + out[map[i].byte] = (u8)((hex[(u8)s[map[i].str]] << 4) | hex[(u8)s[map[i].str + 1]]); + return 0; +} + +/* 16B → "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"(与 .NET Guid.ToString() 同序) */ +static inline void gpt_guid_to_str(const u8 g[16], char out[37]) { + static const char *hd = "0123456789abcdef"; + static const struct { int byte; int rev; } om[16] = { + { 3, 1 }, { 2, 1 }, { 1, 1 }, { 0, 1 }, + { 5, 1 }, { 4, 1 }, + { 7, 1 }, { 6, 1 }, + { 8, 0 }, { 9, 0 }, { 10, 0 }, { 11, 0 }, + { 12, 0 }, { 13, 0 }, { 14, 0 }, { 15, 0 }, + }; + for (int i = 0; i < 16; i++) { + int b = om[i].byte; + out[i * 2] = hd[g[b] >> 4]; + out[i * 2 + 1] = hd[g[b] & 0xF]; + if (i == 3 || i == 5 || i == 7 || i == 9) { out[i * 2 + 2] = '-'; } + } + out[36] = 0; +} + +/* ---- GPT 头构建 / 校验 ---- */ + +/* 计算头 CRC:前 92 字节、header_crc 字段按 0 参与 */ +static inline u32 gpt_header_crc(const gpt_header *h) { + u8 tmp[92]; + gpt_mem_cpy(tmp, (const u8 *)h, 92); + gpt_w32(tmp + 16, 0); + return gpt_crc32(tmp, 92); +} + +/* 构建头(不落盘):entries_crc 由调用者预先算好 */ +static inline void gpt_header_build(gpt_header *h, u64 current_lba, u64 backup_lba, + u64 first_usable, u64 last_usable, + const u8 disk_guid[16], + u64 entries_lba, u32 entries_crc) { + static const u8 sig[8] = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }; + gpt_mem_set((u8 *)h, 0, sizeof(*h)); + gpt_mem_cpy(h->sig, sig, 8); + h->revision = GPT_REVISION; + h->header_size = GPT_HEADER_SIZE; + h->current_lba = current_lba; + h->backup_lba = backup_lba; + h->first_usable = first_usable; + h->last_usable = last_usable; + if (disk_guid) gpt_mem_cpy(h->disk_guid, disk_guid, 16); + h->entries_lba = entries_lba; + h->entries_count = GPT_ENTRIES; + h->entries_size = GPT_ENTRY_SIZE; + h->entries_crc = entries_crc; + h->header_crc = gpt_header_crc(h); +} + +/* 校验 LBA 处的 GPT 头(签名/大小/current_lba/CRC),返回 0 有效 */ +static inline int gpt_header_validate(const gpt_header *h, u64 expect_lba) { + static const u8 sig[8] = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }; + if (!gpt_mem_eq(h->sig, sig, 8)) return -1; + if (h->revision != GPT_REVISION || h->header_size < GPT_HEADER_SIZE) return -2; + if (h->current_lba != expect_lba) return -3; + if (gpt_header_crc(h) != h->header_crc) return -4; + return 0; +} + +/* ---- 分区表操作(内存缓冲) ---- */ +static inline gpt_part_entry *gpt_part(u32 idx) { + return (gpt_part_entry *)(gpt_tbl + idx * GPT_ENTRY_SIZE); +} + +/* 分区表 CRC(覆盖全部 32 扇区) */ +static inline u32 gpt_entries_crc(void) { + return gpt_crc32(gpt_tbl, sizeof(gpt_tbl)); +} + +static inline int gpt_part_empty(const gpt_part_entry *e) { + return gpt_mem_eq(e->type_guid, (const u8 *)gpt_guid_esp /* 任意非 0 引用 */, 0) && + gpt_mem_eq(e->type_guid, e->type_guid, 0); /* 恒真占位,防 -Wunused */ +} +static inline int gpt_part_is_free(const gpt_part_entry *e) { + /* type_guid 全 0 = 空槽 */ + static const u8 zero16[16] = { 0 }; + return gpt_mem_eq(e->type_guid, zero16, 16); +} + +/* 查找第一个 type_guid 匹配的分区(含全 0 停止),返回索引或 -1 */ +static inline int gpt_find_part_type(const u8 type_guid[16], u64 *out_first, u64 *out_last) { + for (u32 i = 0; i < GPT_ENTRIES; i++) { + const gpt_part_entry *e = gpt_part(i); + if (gpt_part_is_free(e)) break; + if (gpt_mem_eq(e->type_guid, type_guid, 16)) { + if (out_first) *out_first = e->first_lba; + if (out_last) *out_last = e->last_lba; + return (int)i; + } + } + return -1; +} + +/* 查找 ESP 分区,返回索引或 -1 */ +static inline int gpt_find_esp(u64 *out_first, u64 *out_last) { + return gpt_find_part_type(gpt_guid_esp, out_first, out_last); +} + +/* 分区名写入(ASCII → UTF-16LE,最多 36 字符) */ +static inline void gpt_part_set_name(gpt_part_entry *e, const char *ascii) { + u32 i = 0; + for (; i < 36 && ascii[i]; i++) gpt_w16((u8 *)&e->name[i], (u16)ascii[i]); + for (; i < 36; i++) gpt_w16((u8 *)&e->name[i], 0); +} + +/* 填充分区项到指定槽位(返回 0 成功,-1 槽位越界/非空) */ +static inline int gpt_part_fill(u32 idx, const u8 type_guid[16], const u8 uniq_guid[16], + u64 first_lba, u64 last_lba, u64 attrs, const char *name) { + if (idx >= GPT_ENTRIES) return -1; + gpt_part_entry *e = gpt_part(idx); + if (!gpt_part_is_free(e)) return -2; + gpt_mem_cpy(e->type_guid, type_guid, 16); + if (uniq_guid) gpt_mem_cpy(e->uniq_guid, uniq_guid, 16); + else gpt_mem_set(e->uniq_guid, 0, 16); + e->first_lba = first_lba; + e->last_lba = last_lba; + e->attrs = attrs; + gpt_part_set_name(e, name); + return 0; +} + +/* ---- 落盘 / 探测 ---- */ + +/* 探测 LBA1 是否为有效 GPT(轻量,不加载分区表) */ +static inline int gpt_probe(void) { + if (gpt_read_sectors(1, 1, gpt_sec) != 0) return -1; + return gpt_header_validate((const gpt_header *)gpt_sec, 1); +} + +/* 加载主头 + 主分区表到缓冲;校验头/表 CRC。total_sectors 为 0 时跳过盘尾推导 */ +static inline int gpt_load(u64 total_sectors) { + if (gpt_read_sectors(1, 1, gpt_sec) != 0) return -1; + const gpt_header *h = (const gpt_header *)gpt_sec; + if (gpt_header_validate(h, 1) != 0) return -2; + if (h->entries_lba != 2 || h->entries_count != GPT_ENTRIES) return -3; + if (gpt_read_sectors(2, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -4; + if (gpt_entries_crc() != h->entries_crc) return -5; + gpt_disk_sectors = total_sectors; + return 0; +} + +/* 全新初始化:protective MBR + 空 GPT(主+备份)。disk_guid 可 NULL(全 0)。 + * total_sectors 必须 >= 70(34 + 32 表 + 2 头)。 */ +static inline int gpt_blank_disk(u64 total_sectors, const u8 disk_guid[16]) { + if (total_sectors < GPT_FIRST_USABLE + GPT_ENTRY_SECTORS + 2) return -1; + + /* LBA0 protective MBR(build.ps1 布局) */ + gpt_mem_set(gpt_sec, 0, 512); + gpt_sec[GPT_MBR_PART_OFF + 4] = 0xEE; /* type */ + gpt_w32(gpt_sec + GPT_MBR_PART_OFF + 8, 1); /* start LBA */ + u64 prot = total_sectors - 1; + if (prot > 0xFFFFFFFFu) prot = 0xFFFFFFFFu; + gpt_w32(gpt_sec + GPT_MBR_PART_OFF + 12, (u32)prot); /* sectors */ + gpt_sec[510] = 0x55; gpt_sec[511] = 0xAA; + if (gpt_write_sectors(0, 1, gpt_sec) != 0) return -2; + + /* 空分区表 */ + gpt_mem_set(gpt_tbl, 0, sizeof(gpt_tbl)); + u32 entries_crc = gpt_crc32(gpt_tbl, sizeof(gpt_tbl)); + + /* 主头(LBA1)+ 备份头(lastLba) */ + u64 last_lba = total_sectors - 1; + u64 last_usable = last_lba - GPT_ENTRY_SECTORS - 1; + gpt_header ph, bh; + gpt_header_build(&ph, 1, last_lba, GPT_FIRST_USABLE, last_usable, + disk_guid, 2, entries_crc); + gpt_header_build(&bh, last_lba, 1, GPT_FIRST_USABLE, last_usable, + disk_guid, last_lba - GPT_ENTRY_SECTORS, entries_crc); + + if (gpt_write_sectors(1, 1, (const u8 *)&ph) != 0) return -3; + if (gpt_write_sectors(2, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -4; + if (gpt_write_sectors(last_lba - GPT_ENTRY_SECTORS, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -5; + if (gpt_write_sectors(last_lba, 1, (const u8 *)&bh) != 0) return -6; + + gpt_disk_sectors = total_sectors; + return 0; +} + +/* 新增/更新 ESP 分区项(内存缓冲,需 gpt_commit 落盘): + * 已有 ESP 则更新范围,否则填第一个空槽。返回槽位索引或负错误码 */ +static inline int gpt_update_esp(u64 first_lba, u64 last_lba, + const u8 uniq_guid[16], const char *name) { + static const u8 zero16[16] = { 0 }; + int idx = gpt_find_part_type(gpt_guid_esp, 0, 0); + if (idx >= 0) { + gpt_part_entry *e = gpt_part((u32)idx); + e->first_lba = first_lba; + e->last_lba = last_lba; + if (uniq_guid) gpt_mem_cpy(e->uniq_guid, uniq_guid, 16); + if (name) gpt_part_set_name(e, name); + return idx; + } + /* 找第一个空槽(类型全 0) */ + for (u32 i = 0; i < GPT_ENTRIES; i++) { + if (gpt_part_is_free(gpt_part(i))) + return gpt_part_fill(i, gpt_guid_esp, uniq_guid ? uniq_guid : zero16, + first_lba, last_lba, 0, name ? name : "Deshab ESP"); + } + return -3; /* 表满 */ +} + +/* 提交:重算 entries CRC + 双头 CRC,写主表/备份表/主头/备份头(幂等) */ +static inline int gpt_commit(void) { + u64 last_lba = 0; + gpt_header h; + /* 读当前主头,取 last_usable / disk_guid / backup 布局 */ + if (gpt_read_sectors(1, 1, gpt_sec) != 0) return -1; + gpt_mem_cpy((u8 *)&h, gpt_sec, sizeof(h)); + if (gpt_header_validate(&h, 1) != 0) return -2; + last_lba = h.backup_lba; + + u32 entries_crc = gpt_entries_crc(); + + gpt_header_build(&h, 1, last_lba, h.first_usable, h.last_usable, + h.disk_guid, 2, entries_crc); + gpt_header bh; + gpt_header_build(&bh, last_lba, 1, h.first_usable, h.last_usable, + h.disk_guid, last_lba - GPT_ENTRY_SECTORS, entries_crc); + + if (gpt_write_sectors(2, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -3; + if (gpt_write_sectors(1, 1, (const u8 *)&h) != 0) return -4; + if (gpt_write_sectors(last_lba - GPT_ENTRY_SECTORS, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -5; + if (gpt_write_sectors(last_lba, 1, (const u8 *)&bh) != 0) return -6; + return 0; +} + +#endif /* DESHAB_GPT_WRITE_H */ \ No newline at end of file diff --git a/CODE/tools/read_usb_logs.ps1 b/CODE/tools/read_usb_logs.ps1 new file mode 100644 index 0000000..01b57e2 --- /dev/null +++ b/CODE/tools/read_usb_logs.ps1 @@ -0,0 +1,179 @@ +# read_usb_logs.ps1 - Read Deshab USB disk, parse GPT+FAT32, extract test logs +# +# Deshab 实机测试日志回读工具:U 盘(GPT ESP + FAT32)在 Windows 上无法 +# 直接挂载(removable media 不支持盘符分配),此脚本以管理员权限直读 +# 物理磁盘,解析 GPT 分区表 + FAT32 文件系统,把 SYSTEM/DESHAB64/DEV/ 下的 +# 日志文件(BOOTLOG.TXT、BOOTxx.TXT、LOG 等)和根目录 DEVLOG 提取到本地。 +# +# Usage (admin PowerShell): +# powershell -ExecutionPolicy Bypass -File "CODE\tools\read_usb_logs.ps1" +# powershell -ExecutionPolicy Bypass -File "CODE\tools\read_usb_logs.ps1" -DiskNumber 2 +# powershell -ExecutionPolicy Bypass -File "CODE\tools\read_usb_logs.ps1" -OutDir ".\logs" +# +# 输出目录: .build_tmp\usb_logs\ (或 -OutDir 指定) +param( + [int]$DiskNumber = 1, + [string]$OutDir = 'd:\Code\DEAICUP\Deshab\.build_tmp\usb_logs' +) + +$ErrorActionPreference = 'Stop' +$diskPath = '\\.\PhysicalDrive' + $DiskNumber + +# ---- 检查管理员权限(打开物理磁盘需要) ---- +$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + Write-Host "[ERROR] Administrator privileges required to open $diskPath" -ForegroundColor Red + Write-Host " Run this script from an elevated PowerShell (Win+X -> Terminal (Admin))." + exit 1 +} + +New-Item -ItemType Directory -Force -Path $OutDir | Out-Null + +$script:fs = $null +$script:partStart = 0 +$script:dataSec = 0 +$script:rsvd = 0 +$script:spc = 0 +$script:csize = 0 + +function Read-Cluster([uint32]$clus) { + $lba = $dataSec + ($clus - 2) * $spc + $fs.Position = ([int64]$partStart + $lba) * 512 + $b = [byte[]]::new($csize) + [void]$fs.Read($b, 0, $csize) + return ,$b +} + +function Read-FatEntry([uint32]$c) { + $fatSec = [Math]::Floor(($c * 4) / 512) + $fs.Position = ([int64]$partStart + $rsvd + $fatSec) * 512 + $fb = [byte[]]::new(512) + [void]$fs.Read($fb, 0, 512) + return ([BitConverter]::ToUInt32($fb, ($c * 4) % 512) -band 0x0FFFFFFF) +} + +function Decode-Short([byte[]]$n) { + $s=''; for($i=0;$i -lt 8 -and $n[$i] -ne 0x20 -and $n[$i] -ne 0;$i++){$s+=[char]$n[$i]} + $e=''; for($i=8;$i -lt 11 -and $n[$i] -ne 0x20 -and $n[$i] -ne 0;$i++){$e+=[char]$n[$i]} + if($e){return "$s.$e"}else{return $s} +} + +function Get-EntryCluster([byte[]]$buf, [int]$o) { + $clow = [BitConverter]::ToUInt16($buf, $o + 26) + $chigh = [BitConverter]::ToUInt16($buf, $o + 20) + return ($clow -bor ([int]$chigh -shl 16)) +} + +function Read-File([uint32]$firstClus, [uint32]$size) { + if ($size -eq 0) { return ,([byte[]]::new(0)) } + $data = [byte[]]::new($size) + $c = $firstClus; $off = 0; $remaining = $size + while ($c -ge 2 -and $c -lt 0x0FFFFFF8 -and $remaining -gt 0) { + $lba = $dataSec + ($c - 2) * $spc + $fs.Position = ([int64]$partStart + $lba) * 512 + $chunk = [byte[]]::new($csize) + [void]$fs.Read($chunk, 0, $csize) + $n = [Math]::Min($csize, $remaining) + [Array]::Copy($chunk, 0, $data, $off, $n) + $off += $n; $remaining -= $n + $c = Read-FatEntry $c + } + return ,$data +} + +function Scan-Dir([uint32]$dirClus) { + $result = @{} + $c = $dirClus + while ($c -ge 2 -and $c -lt 0x0FFFFFF8) { + $buf = Read-Cluster $c + for ($e = 0; $e -lt ($csize / 32); $e++) { + $o = $e * 32 + if ($buf[$o] -eq 0) { return $result } + if ($buf[$o] -eq 0xE5) { continue } + $at = $buf[$o + 11] + if (($at -band 0x0F) -eq 0x0F) { continue } # LFN entry, skip + if ($at -band 0x08) { continue } # volume label + $name = Decode-Short $buf[$o..($o + 10)] + $result[$name] = @{ Cluster = (Get-EntryCluster $buf $o); Size = [BitConverter]::ToUInt32($buf, $o + 28); Attr = $at } + } + $c = Read-FatEntry $c + } + return $result +} + +function Show-Head([byte[]]$data) { + if (-not $data -or $data.Length -eq 0) { Write-Host " [empty]"; return } + $txt = [System.Text.Encoding]::ASCII.GetString($data) + $len = [Math]::Min(400, $txt.Length) + $clean = '' + for ($i = 0; $i -lt $len; $i++) { + $code = [int]$txt[$i] + if (($code -ge 32 -and $code -le 126) -or $code -eq 10 -or $code -eq 13 -or $code -eq 9) { $clean += $txt[$i] } else { $clean += '.' } + } + Write-Host " [head] $clean" +} + +Write-Host "=== Open $diskPath ===" +$fs = [System.IO.File]::Open($diskPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + +$fs.Position = 512 +$lba1 = [byte[]]::new(512); [void]$fs.Read($lba1, 0, 512) +$sig = [System.Text.Encoding]::ASCII.GetString($lba1, 0, 8) +Write-Host "LBA1 signature: '$sig'" +if ($sig -ne 'EFI PART') { Write-Host "[ERROR] Not a GPT disk"; $fs.Close(); exit 1 } + +$fs.Position = 1024 +$pe = [byte[]]::new(128); [void]$fs.Read($pe, 0, 128) +$partStart = [BitConverter]::ToUInt64($pe, 32) +Write-Host "ESP start LBA: $partStart" + +$fs.Position = $partStart * 512 +$bpb = [byte[]]::new(512); [void]$fs.Read($bpb, 0, 512) +$bps = [BitConverter]::ToUInt16($bpb, 11) +$spc = $bpb[13] +$rsvd = [BitConverter]::ToUInt16($bpb, 14) +$nf = $bpb[16] +$spf = [BitConverter]::ToUInt32($bpb, 36) +$rootClus = [BitConverter]::ToUInt32($bpb, 44) +$dataSec = $rsvd + $nf * $spf +$csize = $bps * $spc +Write-Host "BPB: bps=$bps spc=$spc rsvd=$rsvd nf=$nf spf=$spf root=$rootClus dataSec=$dataSec" + +$root = Scan-Dir $rootClus +Write-Host "=== ROOT ===" +$root.Keys | Sort-Object | ForEach-Object { Write-Host (" {0} clus={1} size={2}" -f $_, $root[$_].Cluster, $root[$_].Size) } + +if (-not $root.ContainsKey('SYSTEM')) { Write-Host "[WARN] no SYSTEM dir"; $fs.Close(); exit 0 } +$sys = Scan-Dir $root['SYSTEM'].Cluster +if (-not $sys.ContainsKey('DESHAB64')) { Write-Host "[WARN] no DESHAB64 dir"; $fs.Close(); exit 0 } +$d64 = Scan-Dir $sys['DESHAB64'].Cluster + +Write-Host "=== DESHAB64 ===" +$d64.Keys | Sort-Object | ForEach-Object { Write-Host (" {0} size={1}" -f $_, $d64[$_].Size) } + +if (-not $d64.ContainsKey('DEV')) { Write-Host "[WARN] no DEV dir"; $fs.Close(); exit 0 } +$dev = Scan-Dir $d64['DEV'].Cluster +Write-Host "=== DESHAB64/DEV ===" +$dev.Keys | Sort-Object | ForEach-Object { Write-Host (" {0} size={1}" -f $_, $dev[$_].Size) } + +Write-Host "=== EXTRACT to $OutDir ===" +foreach ($k in $dev.Keys) { + if (($dev[$k].Attr -band 0x10) -ne 0) { continue } + $data = Read-File $dev[$k].Cluster $dev[$k].Size + $safe = ($k -replace '[^A-Za-z0-9._-]', '_') + $outPath = Join-Path $OutDir $safe + [System.IO.File]::WriteAllBytes($outPath, $data) + Write-Host (" -> {0} ({1} bytes)" -f $safe, $data.Length) + Show-Head $data +} +foreach ($k in @('DEVLOG','TEST.TXT','COPY.TXT','MOVED.TXT','BOOTLOG')) { + if ($root.ContainsKey($k)) { + $data = Read-File $root[$k].Cluster $root[$k].Size + $outPath = Join-Path $OutDir ("ROOT_" + $k) + [System.IO.File]::WriteAllBytes($outPath, $data) + Write-Host (" [root] -> {0} ({1} bytes)" -f $k, $data.Length) + Show-Head $data + } +} +$fs.Close() +Write-Host "=== DONE ===" \ No newline at end of file diff --git a/ISO/run_qemu.bat b/ISO/run_qemu.bat index 7276022..39089e1 100644 --- a/ISO/run_qemu.bat +++ b/ISO/run_qemu.bat @@ -2,16 +2,43 @@ setlocal enabledelayedexpansion set "SCRIPT_DIR=%~dp0" -set "IMG=%SCRIPT_DIR%deshab-dev.img" + +REM ===== Variant selection: dev / release / realtest ===== +REM Priority: argument > QEMU_VARIANT env > interactive menu > default dev +REM Usage: run_qemu.bat [dev|release|realtest] +set "VARIANT=" +if not "%~1"=="" set "VARIANT=%~1" +if not defined VARIANT if defined QEMU_VARIANT set "VARIANT=%QEMU_VARIANT%" +if not defined VARIANT ( + echo [qemu] Select image variant to boot: + echo 1. dev ^(deshab-dev.img, debug all on, auto test^) + echo 2. release ^(deshab-release.img, desktop + login^) + echo 3. realtest ^(deshab-realtest.img, real-machine safe options^) + set /p CHOICE="Choice [1-3, Enter=dev]: " + if "!CHOICE!"=="2" set "VARIANT=release" + if "!CHOICE!"=="3" set "VARIANT=realtest" + if not defined VARIANT set "VARIANT=dev" +) +REM Validate variant name (also accepts passing 1/2/3 as argument) +if "%VARIANT%"=="1" set "VARIANT=dev" +if "%VARIANT%"=="2" set "VARIANT=release" +if "%VARIANT%"=="3" set "VARIANT=realtest" +if not "%VARIANT%"=="dev" if not "%VARIANT%"=="release" if not "%VARIANT%"=="realtest" ( + echo [qemu] Unknown variant: %VARIANT% ^(expected dev / release / realtest^) + exit /b 1 +) + +set "IMG=%SCRIPT_DIR%deshab-%VARIANT%.img" set "SATA_IMG=%SCRIPT_DIR%..\.build_tmp\sata_fat32_dsk.img" set "NVME_IMG=%SCRIPT_DIR%..\.build_tmp\nvme_test.img" if not exist "%IMG%" ( echo [qemu] Image not found: %IMG% echo [qemu] Run ..\build.bat first -- default builds both dev and release. - echo [qemu] Or build dev only: build.bat -Variant dev + echo [qemu] Or build selected only: build.bat -Variant %VARIANT% exit /b 1 ) +echo [qemu] Variant: %VARIANT% set "QEMU=" for %%Q in (qemu-system-x86_64.exe qemu-system-x86_64) do ( diff --git a/SYSTEM/boot/utsm.elf b/SYSTEM/boot/utsm.elf index 0a6b9afd20d9597d3b704cb0d61faa46b2d26a8e..f3b19daf55dc851da6c8fe66a6b677f71a3ff630 100755 GIT binary patch delta 96209 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 p#J@%Ers_EAHDNKmMz$) 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 Yb^g24V>dOu+ao$lZGA2-Or{Iyf0WR{+5i9m diff --git a/SYSTEM/driver/block/ehci.drv b/SYSTEM/driver/block/ehci.drv new file mode 100644 index 0000000000000000000000000000000000000000..46298795fe0039051a60158822c31ca9e5797feb GIT binary patch literal 29208 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 literal 46792 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 diff --git a/SYSTEM/driver/manifest.json b/SYSTEM/driver/manifest.json index beb1aaa..eb16d91 100644 --- a/SYSTEM/driver/manifest.json +++ b/SYSTEM/driver/manifest.json @@ -91,6 +91,14 @@ "depends": ["pci", "irq"], "provides": ["block"] }, + { + "name": "ehci", + "path": "driver/block/ehci.drv", + "class": "storage", + "required": false, + "depends": ["pci"], + "provides": ["block"] + }, { "name": "xhci", "path": "driver/block/xhci.drv", diff --git a/SYSTEM/limine/limine.conf b/SYSTEM/limine/limine.conf index 536543c..6f73b8d 100644 --- a/SYSTEM/limine/limine.conf +++ b/SYSTEM/limine/limine.conf @@ -22,6 +22,8 @@ verbose: yes module_cmdline: dkm:ahci module_path: boot():/driver/block/nvme.drv module_cmdline: dkm:nvme + module_path: boot():/driver/block/ehci.drv + module_cmdline: dkm:ehci module_path: boot():/driver/block/xhci.drv module_cmdline: dkm:xhci module_path: boot():/driver/fs/bootfs.drv diff --git a/SYSTEM/system/deshab64/FUCK b/SYSTEM/system/deshab64/FUCK index 9f8ea6a..88549ce 100644 --- a/SYSTEM/system/deshab64/FUCK +++ b/SYSTEM/system/deshab64/FUCK @@ -1,29 +1,26 @@ -# Deshab Kernel Configuration (DEV - QEMU 测试) -# dev_mode=1 自动跑 shell.elf 测试序列, 调试全开, QEMU 验证选项 +# Deshab Kernel Configuration (REALTEST - 实机开发测试) +# 基于 dev 配置, 启用磁盘日志持久化 +# 启动时把完整日志写入磁盘 LBA 2-1000 扇区, 事后用 WinHex/dd 读取 # 路径: SYSTEM/system/deshab64/FUCK # UTSM 通过 Limine boot module 预加载读取(cmdline="fuck:config") # DSK 通过 FAT32 子目录遍历读取(路径 "SYSTEM /DESHAB64 /FUCK ") # 缺失或空的键使用硬编码默认值,行为与无配置文件时一致 [boot] -vmm=1 -linux_guest=1 -# OpenXJ380 第二 guest(需 SYSTEM/boot/xj380.krl 存在,limine.conf 取消注释) -# 启动后 host 挂起直至 XJ380 guest 终止;无模块时优雅跳过 -xj380_guest=1 +vmm=0 +linux_guest=0 +# OpenXJ380 第二 guest:realtest 默认关闭(vmm=0 时天然不执行) +xj380_guest=0 selftest=1 dev_mode=1 -# QEMU 不需要真机安全检测 +# 真机安全检查全部放宽(和 dev 一样),排除安全检查导致黑屏 la57_guard=0 mcfg_check=0 max_phys_addr_bits=0 -# 实机启动日志持久化:把启动日志写入磁盘原始扇区(开发测试用) -# 启用后启动时把内存日志缓冲区写到指定扇区区间 -# 事后用 WinHex/dd 读取对应扇区即可查看完整启动日志 -# 注意: 写入 LBA 1-33 会覆盖 GPT, 测试盘建议从 34 开始或测试后重新烧录 +# 实机启动日志持久化: 启用, 把启动日志写入 FAT32 文件 +# SYSTEM/DESHAB64/DEV/BOOTLOG.TXT (DEV 缺失时回退根目录 BOOTLOG.TXT) +# 主机直接读取该文件即可, 不覆盖 GPT, 可反复烧录测试 disk_log=1 -disk_log_start=2 -disk_log_end=1000 [drivers] timer=1 @@ -33,6 +30,9 @@ pci=1 console_fb=1 ahci=1 nvme=1 +ehci=1 +# xhci=1: xhci 驱动已修复无控制器时不再 halt,恢复启用 +xhci=1 bootfs=1 vfs=1 fat32=1 @@ -43,18 +43,9 @@ ath9k=0 ps2kbd=1 ioapic_init=1 lapic_enable=1 -# B7: APIC 路由接管开关 (apic.drv 自解析, 缺失=0) -# 0=保持 8259 PIC 路由 (默认, 现状不回归) -# 1=IOAPIC->LAPIC 接管: 注册 LAPIC EOI 钩子 + PIC IMR 全屏蔽 + TPR=0 -# + tick 链路自验 (vector 0x20, 失败自动完整降级回 PIC 并注销钩子) apic_route=1 -# B7 阶段2: 随 apic_route=1 额外放开 legacy IRQ1(键盘)/12(鼠标)/11(e1000) -# QEMU 全链路已验证 (B1 向导按键/B2 启动/e1000 IRQ11) apic_route_legacy=1 -# B7 阶段3: NVMe MSI/MSI-X 中断驱动开关 (nvme.drv 自解析, 缺失=1) nvme_msi=1 -# B7 诊断 (apic.drv): 1=启用 HPET 绝对时基对拍 + IRQ12 鼠标流观察窗 -# 常规构建保持 0 apic_diag=0 [utsm] @@ -75,17 +66,11 @@ mouse_init=1 netman=1 [debug] -# 全局插桩开关 (0=禁用,零开销; 1=启用所有 INSTR_* 宏) enabled=1 -# 日志级别阈值 (0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=SILENT) log_level=1 -# 探测缓冲开关 (0=禁用; 1=启用 INSTR_PROBE 写入环形缓冲) probe_enable=1 -# 探测缓冲容量 (条目数,每条 40 字节,默认 4096 条 ≈ 160KB) probe_capacity=4096 -# 统计摘要间隔 (每 N 次增量输出一次摘要) stat_summary_interval=256 -# 模块级过滤: 1=启用该模块的 TRACE/DEBUG 级别日志 utsm_trace=1 vmm_trace=1 dkm_trace=1 @@ -95,12 +80,7 @@ net_trace=0 input_trace=1 [compat] -# 兼容层动态链接库搜索路径配置 -# lib_path: FAT32 根目录下存放 .dll/.so 的子目录名 lib_path=lib -# linux_lib_guest_path: Linux guest 内库目录路径 linux_lib_guest_path=/usr/lib/deshab -# linux_lib_sync: 启动时推送 .so 到 Linux guest (0=关闭, 1=开启) linux_lib_sync=1 -# pe_dll_search: PE shim miss 后从 SYSTEM/lib 加载真实 DLL (0=关闭, 1=开启) pe_dll_search=1 diff --git a/SYSTEM/system/deshab64/tools/fileman.elf b/SYSTEM/system/deshab64/tools/fileman.elf index b20c1d1cdcb68977e2e8111de3339cf0c252ce5b..175823621c1bc94ee6cac921cc42d12cfcde937e 100755 GIT binary patch delta 5792 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 SweHKk^L4zVZtTAEy8i)LVsKml delta 4489 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 diff --git a/SYSTEM/system/user/use/firstInit.txt b/SYSTEM/system/user/use/firstInit.txt index 0bc999a..cc9b5e3 100644 --- a/SYSTEM/system/user/use/firstInit.txt +++ b/SYSTEM/system/user/use/firstInit.txt @@ -1,2 +1,2 @@ -1 +0 1 \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index 4957dfa..a6bc5c9 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,5 +1,6 @@ param( - [ValidateSet('dev','release','realtest','both')][string]$Variant = 'both' + [ValidateSet('dev','release','realtest','both')][string]$Variant = 'both', + [switch]$Vhd ) $ErrorActionPreference = 'Stop' @@ -211,8 +212,13 @@ function New-ShortEntry([string]$ShortName, [byte]$Attr, [int]$Cluster, [uint32] function Add-DirectoryEntry($Entries, [string]$Name, [byte]$Attr, [int]$Cluster, [uint32]$Size, $UsedNames) { $shortName = New-ShortFatName $Name $UsedNames $shortBytes = Get-ShortNameBytes $shortName - $upper = $Name.ToUpperInvariant() - $needsLfn = -not (Test-ShortFatName $upper) -or (($shortName.Trim() -replace ' +', '') -ne ($upper -replace '\.', '')) + # 用原始名(含大小写)判断是否生成 LFN:Test-ShortFatName 内部用 + # -cmatch '[a-z]' 检测小写字母,含小写的名字(如 limine)必须生成 + # LFN 才能在 Windows 上保留原始大小写显示。FAT32 查找仍不区分大小写。 + # 注意:拆成两步赋值——PowerShell 5.1 对 `-not (函数调用) -or (...)` 内联 + # 表达式解析异常返回 $null,分开赋值保证布尔短路正常。 + $isUpperShort = Test-ShortFatName $Name + $needsLfn = (-not $isUpperShort) -or (($shortName.Trim() -replace ' +', '') -ne ($Name.ToUpperInvariant() -replace '\.', '')) if ($needsLfn) { $checksum = Get-LfnChecksum $shortBytes @@ -501,32 +507,94 @@ function New-GptFat32Image([string]$SourceDir, [string]$ImagePath, [int]$SizeMB Write-Host "[build] GPT + FAT32 ESP IMG: $ImagePath" } +# Convert a raw GPT disk image to a fixed VHD by appending the 512-byte VHD +# footer. Fixed VHD = raw data + footer, so this is a cheap copy + footer. +# Windows can mount the result natively (double-click / Mount-DiskImage). +function ConvertTo-FixedVhd([string]$RawImage, [string]$VhdPath) { + if (-not (Test-Path $RawImage)) { throw "Raw image not found: $RawImage" } + Copy-Item -LiteralPath $RawImage -Destination $VhdPath -Force + $fs = [System.IO.File]::Open($VhdPath, [System.IO.FileMode]::Open, + [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) + try { + $size = $fs.Length + $totalSectors = [UInt64]($size / 512) + + $footer = [byte[]]::new(512) + [Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('conectix'), 0, $footer, 0, 8) + Set-Le32 $footer 8 0x00000002 # features + Set-Le32 $footer 12 0x00010000 # file format version + Set-Le64 $footer 16 ([UInt64]::MaxValue) # data offset: fixed disk + $epoch = [DateTime]::new(2000, 1, 1, 0, 0, 0, [DateTimeKind]::Utc) + Set-Le32 $footer 24 ([UInt64][Math]::Max(0, [int]([DateTime]::UtcNow - $epoch).TotalSeconds)) + [Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('dshb'), 0, $footer, 28, 4) + Set-Le32 $footer 32 0x00010000 # creator version + [Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('Wi2k'), 0, $footer, 36, 4) + Set-Le64 $footer 40 ([UInt64]$size) # original size + Set-Le64 $footer 48 ([UInt64]$size) # current size + + # Disk geometry (VHD layout is big-endian here: 2-byte cylinders + 1 head + 1 spt) + $spt = 0; $h = 0 + if ($totalSectors -ge 65535 * 16 * 255) { $spt = 255; $h = 16 } + elseif ($totalSectors -ge 65535 * 16 * 17) { $spt = 63; $h = 16 } + else { $spt = 17; $h = 16 } + $c = [UInt64][Math]::Floor([double]$totalSectors / ($h * $spt)) + if ($c -gt 65535) { $c = 65535 } + $footer[56] = [byte](($c -shr 8) -band 0xFF) + $footer[57] = [byte]($c -band 0xFF) + $footer[58] = [byte]$h + $footer[59] = [byte]$spt + + Set-Le32 $footer 60 2 # disk type: fixed + [Array]::Copy(([Guid]::NewGuid()).ToByteArray(), 0, $footer, 68, 16) + $footer[84] = 0 # saved state: normal + + # Checksum: one's complement of the uint32 sum over the whole footer + $sum = [UInt64]0 + for ($i = 0; $i -lt 512; $i += 4) { + $sum = ($sum + [BitConverter]::ToUInt32($footer, $i)) -band 0xFFFFFFFF + } + Set-Le32 $footer 64 ((0xFFFFFFFF -bxor $sum) -band 0xFFFFFFFF) + + [void]$fs.Seek(0, [System.IO.SeekOrigin]::End) + $fs.Write($footer, 0, 512) + } finally { + $fs.Close() + } + Write-Host "[build] VHD (Windows mountable): $VhdPath" +} + # Build a single image variant (dev/release): inject FUCK + firstInit.txt from # build/configs// into SYSTEM/, then package via New-GptFat32Image. # If $ImagePath is $null, only injects config without packaging (used to # restore SYSTEM dir to dev config after dual-image build). +# NOTE: configs/ dir may be absent; then SYSTEM is packaged as-is. function Build-ImageVariant([string]$VariantName, [string]$ImagePath) { $cfgDir = Join-Path $ConfigsDir $VariantName - if (-not (Test-Path $cfgDir)) { - throw "Config variant directory not found: $cfgDir" + + if (Test-Path $cfgDir) { + $fuckSrc = Join-Path $cfgDir 'FUCK' + $fuckDst = Join-Path $SystemDir 'system\deshab64\FUCK' + $fiSrc = Join-Path $cfgDir 'firstInit.txt' + $fiDst = Join-Path $SystemDir 'system\user\use\firstInit.txt' + + if (-not (Test-Path $fuckSrc)) { throw "Missing FUCK template: $fuckSrc" } + if (-not (Test-Path $fiSrc)) { throw "Missing firstInit.txt template: $fiSrc" } + + Copy-Item -LiteralPath $fuckSrc -Destination $fuckDst -Force + Copy-Item -LiteralPath $fiSrc -Destination $fiDst -Force + Write-Host "[build] Injected config variant=$VariantName (FUCK + firstInit.txt)" + } else { + Write-Host "[build] WARN: configs/$VariantName absent, packaging SYSTEM as-is" } - $fuckSrc = Join-Path $cfgDir 'FUCK' - $fuckDst = Join-Path $SystemDir 'system\deshab64\FUCK' - $fiSrc = Join-Path $cfgDir 'firstInit.txt' - $fiDst = Join-Path $SystemDir 'system\user\use\firstInit.txt' - - if (-not (Test-Path $fuckSrc)) { throw "Missing FUCK template: $fuckSrc" } - if (-not (Test-Path $fiSrc)) { throw "Missing firstInit.txt template: $fiSrc" } - - Copy-Item -LiteralPath $fuckSrc -Destination $fuckDst -Force - Copy-Item -LiteralPath $fiSrc -Destination $fiDst -Force - Write-Host "[build] Injected config variant=$VariantName (FUCK + firstInit.txt)" - if ($ImagePath) { # Phase 0: ESP enlarged from 768 to $EspSizeMB (default 2048) to fit # VSCode + Electron + GUI stack libs (VSCode integration roadmap). New-GptFat32Image $SystemDir $ImagePath $EspSizeMB + if ($Vhd) { + $vhdPath = [System.IO.Path]::ChangeExtension($ImagePath, '.vhd') + ConvertTo-FixedVhd $ImagePath $vhdPath + } } } @@ -785,6 +853,7 @@ Build-DkmDriver (Join-Path $Root 'CODE\DKM\apic\apic.c') (Join-Path $SystemDir ' Write-Host '[build] Building DKM storage drivers...' Build-DkmDriver (Join-Path $Root 'CODE\DKM\nvme\nvme.c') (Join-Path $SystemDir 'driver\block\nvme.drv') +Build-DkmDriver (Join-Path $Root 'CODE\DKM\ehci\ehci.c') (Join-Path $SystemDir 'driver\block\ehci.drv') Build-DkmDriver (Join-Path $Root 'CODE\DKM\xhci\xhci.c') (Join-Path $SystemDir 'driver\block\xhci.drv') Write-Host '[build] Building DKM network drivers...' diff --git a/build/configs/dev/FUCK b/build/configs/dev/FUCK deleted file mode 100644 index 9f8ea6a..0000000 --- a/build/configs/dev/FUCK +++ /dev/null @@ -1,106 +0,0 @@ -# Deshab Kernel Configuration (DEV - QEMU 测试) -# dev_mode=1 自动跑 shell.elf 测试序列, 调试全开, QEMU 验证选项 -# 路径: SYSTEM/system/deshab64/FUCK -# UTSM 通过 Limine boot module 预加载读取(cmdline="fuck:config") -# DSK 通过 FAT32 子目录遍历读取(路径 "SYSTEM /DESHAB64 /FUCK ") -# 缺失或空的键使用硬编码默认值,行为与无配置文件时一致 - -[boot] -vmm=1 -linux_guest=1 -# OpenXJ380 第二 guest(需 SYSTEM/boot/xj380.krl 存在,limine.conf 取消注释) -# 启动后 host 挂起直至 XJ380 guest 终止;无模块时优雅跳过 -xj380_guest=1 -selftest=1 -dev_mode=1 -# QEMU 不需要真机安全检测 -la57_guard=0 -mcfg_check=0 -max_phys_addr_bits=0 -# 实机启动日志持久化:把启动日志写入磁盘原始扇区(开发测试用) -# 启用后启动时把内存日志缓冲区写到指定扇区区间 -# 事后用 WinHex/dd 读取对应扇区即可查看完整启动日志 -# 注意: 写入 LBA 1-33 会覆盖 GPT, 测试盘建议从 34 开始或测试后重新烧录 -disk_log=1 -disk_log_start=2 -disk_log_end=1000 - -[drivers] -timer=1 -apic=1 -acpi=1 -pci=1 -console_fb=1 -ahci=1 -nvme=1 -bootfs=1 -vfs=1 -fat32=1 -devfs=1 -e1000=1 -virtio_net=1 -ath9k=0 -ps2kbd=1 -ioapic_init=1 -lapic_enable=1 -# B7: APIC 路由接管开关 (apic.drv 自解析, 缺失=0) -# 0=保持 8259 PIC 路由 (默认, 现状不回归) -# 1=IOAPIC->LAPIC 接管: 注册 LAPIC EOI 钩子 + PIC IMR 全屏蔽 + TPR=0 -# + tick 链路自验 (vector 0x20, 失败自动完整降级回 PIC 并注销钩子) -apic_route=1 -# B7 阶段2: 随 apic_route=1 额外放开 legacy IRQ1(键盘)/12(鼠标)/11(e1000) -# QEMU 全链路已验证 (B1 向导按键/B2 启动/e1000 IRQ11) -apic_route_legacy=1 -# B7 阶段3: NVMe MSI/MSI-X 中断驱动开关 (nvme.drv 自解析, 缺失=1) -nvme_msi=1 -# B7 诊断 (apic.drv): 1=启用 HPET 绝对时基对拍 + IRQ12 鼠标流观察窗 -# 常规构建保持 0 -apic_diag=0 - -[utsm] -max_segments=1024 -max_capabilities=256 -max_pckc_keys=8 -dirty_shard_pages=1024 -arena_size_mb=64 -drr_emergency_pool_kb=64 -drr_recovery_log_size=4096 -drr_heartbeat_interval=0 - -[dsk] -show_logo=1 -skip_login=1 -default_shell=cmd -mouse_init=1 -netman=1 - -[debug] -# 全局插桩开关 (0=禁用,零开销; 1=启用所有 INSTR_* 宏) -enabled=1 -# 日志级别阈值 (0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=SILENT) -log_level=1 -# 探测缓冲开关 (0=禁用; 1=启用 INSTR_PROBE 写入环形缓冲) -probe_enable=1 -# 探测缓冲容量 (条目数,每条 40 字节,默认 4096 条 ≈ 160KB) -probe_capacity=4096 -# 统计摘要间隔 (每 N 次增量输出一次摘要) -stat_summary_interval=256 -# 模块级过滤: 1=启用该模块的 TRACE/DEBUG 级别日志 -utsm_trace=1 -vmm_trace=1 -dkm_trace=1 -dsk_trace=1 -block_trace=1 -net_trace=0 -input_trace=1 - -[compat] -# 兼容层动态链接库搜索路径配置 -# lib_path: FAT32 根目录下存放 .dll/.so 的子目录名 -lib_path=lib -# linux_lib_guest_path: Linux guest 内库目录路径 -linux_lib_guest_path=/usr/lib/deshab -# linux_lib_sync: 启动时推送 .so 到 Linux guest (0=关闭, 1=开启) -linux_lib_sync=1 -# pe_dll_search: PE shim miss 后从 SYSTEM/lib 加载真实 DLL (0=关闭, 1=开启) -pe_dll_search=1 diff --git a/build/configs/dev/firstInit.txt b/build/configs/dev/firstInit.txt deleted file mode 100644 index 0bc999a..0000000 --- a/build/configs/dev/firstInit.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -1 \ No newline at end of file diff --git a/build/configs/realtest/FUCK b/build/configs/realtest/FUCK deleted file mode 100644 index e7c0d08..0000000 --- a/build/configs/realtest/FUCK +++ /dev/null @@ -1,84 +0,0 @@ -# Deshab Kernel Configuration (REALTEST - 实机开发测试) -# 基于 dev 配置, 启用磁盘日志持久化 -# 启动时把完整日志写入磁盘 LBA 2-1000 扇区, 事后用 WinHex/dd 读取 -# 路径: SYSTEM/system/deshab64/FUCK -# UTSM 通过 Limine boot module 预加载读取(cmdline="fuck:config") -# DSK 通过 FAT32 子目录遍历读取(路径 "SYSTEM /DESHAB64 /FUCK ") -# 缺失或空的键使用硬编码默认值,行为与无配置文件时一致 - -[boot] -vmm=0 -linux_guest=0 -# OpenXJ380 第二 guest:realtest 默认关闭(vmm=0 时天然不执行) -xj380_guest=0 -selftest=1 -dev_mode=1 -# 真机安全检查全部放宽(和 dev 一样),排除安全检查导致黑屏 -la57_guard=0 -mcfg_check=0 -max_phys_addr_bits=0 -# 实机启动日志持久化: 启用, 把启动日志写入磁盘原始扇区 -disk_log=1 -disk_log_start=2 -disk_log_end=1000 - -[drivers] -timer=1 -apic=1 -acpi=1 -pci=1 -console_fb=1 -ahci=1 -nvme=1 -xhci=0 -bootfs=1 -vfs=1 -fat32=1 -devfs=1 -e1000=1 -virtio_net=1 -ath9k=0 -ps2kbd=1 -ioapic_init=1 -lapic_enable=1 -apic_route=1 -apic_route_legacy=1 -nvme_msi=1 -apic_diag=0 - -[utsm] -max_segments=1024 -max_capabilities=256 -max_pckc_keys=8 -dirty_shard_pages=1024 -arena_size_mb=64 -drr_emergency_pool_kb=64 -drr_recovery_log_size=4096 -drr_heartbeat_interval=0 - -[dsk] -show_logo=1 -skip_login=1 -default_shell=cmd -mouse_init=1 -netman=1 - -[debug] -enabled=1 -log_level=1 -probe_enable=1 -probe_capacity=4096 -stat_summary_interval=256 -utsm_trace=1 -vmm_trace=1 -dkm_trace=1 -dsk_trace=1 -block_trace=1 -net_trace=0 -input_trace=1 - -[compat] -lib_path=lib -linux_lib_guest_path=/usr/lib/deshab -linux_lib_sync=1 -pe_dll_search=1 diff --git a/build/configs/realtest/firstInit.txt b/build/configs/realtest/firstInit.txt deleted file mode 100644 index d00491f..0000000 --- a/build/configs/realtest/firstInit.txt +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/build/configs/release/FUCK b/build/configs/release/FUCK deleted file mode 100644 index 1d05690..0000000 --- a/build/configs/release/FUCK +++ /dev/null @@ -1,112 +0,0 @@ -# Deshab Kernel Configuration (RELEASE - 实机测试) -# dev_mode=0 直接进桌面, 要求登录, 调试零开销, 真机安全选项全开 -# 路径: SYSTEM/system/deshab64/FUCK -# UTSM 通过 Limine boot module 预加载读取(cmdline="fuck:config") -# DSK 通过 FAT32 子目录遍历读取(路径 "SYSTEM /DESHAB64 /FUCK ") -# 缺失或空的键使用硬编码默认值,行为与无配置文件时一致 - -[boot] -vmm=1 -linux_guest=1 -# OpenXJ380 第二 guest(需 SYSTEM/boot/xj380.krl 存在,limine.conf 取消注释) -xj380_guest=1 -selftest=1 -dev_mode=0 -# 真机安全: LA57 检测 (0=不检测, 1=检测并警告, 2=检测并拒绝启动) -# 真机若 BIOS 启用 5 级分页, 设 2 可阻止启动避免数据损坏 -la57_guard=2 -# 真机安全: PCIe MCFG 检测 (0=不检测, 1=检测并记录) -mcfg_check=1 -# 真机安全: 允许的最大物理地址宽度 (超过则拒绝启动, 0=不限制) -# 典型值: 46 (QEMU), 48 (多数实机), 52 (服务器) -max_phys_addr_bits=48 -# 实机启动日志持久化(开发测试用, release 默认关闭) -# 启用: disk_log=1, 事后用 WinHex/dd 读取对应扇区查看启动日志 -# 注意: 写入 LBA 1-33 会覆盖 GPT, 建议从 34 开始 -disk_log=0 -disk_log_start=2 -disk_log_end=1000 - -[drivers] -timer=1 -apic=1 -acpi=1 -pci=1 -console_fb=1 -ahci=1 -nvme=1 -bootfs=1 -vfs=1 -fat32=1 -devfs=1 -e1000=1 -virtio_net=1 -ath9k=0 -ps2kbd=1 -# 真机安全: 禁用不稳定的真机特性 -# ioapic_init=0 可禁用 IOAPIC 重定向表编程 (留 PIC 模式) -ioapic_init=1 -# lapic_enable=0 可禁用 LAPIC software enable (留 firmware 状态) -lapic_enable=1 -# B7: APIC 路由接管开关 (apic.drv 自解析, 缺失=0) -# 0=保持 8259 PIC 路由 (默认, 现状不回归) -# 1=IOAPIC->LAPIC 接管: 注册 LAPIC EOI 钩子 + PIC IMR 全屏蔽 + TPR=0 -# + tick 链路自验 (vector 0x20, 失败自动完整降级回 PIC 并注销钩子) -apic_route=1 -# B7 阶段2: 随 apic_route=1 额外放开 legacy IRQ1(键盘)/12(鼠标)/11(e1000) -# 真机冒烟前回 0, 避免 legacy IRQ 持续投递在非常规 IOAPIC 拓扑下出问题 -apic_route_legacy=0 -# B7 阶段3: NVMe MSI/MSI-X 中断驱动开关 (nvme.drv 自解析, 缺失=1) -nvme_msi=1 -# B7 诊断 (apic.drv): 1=启用 HPET 绝对时基对拍 + IRQ12 鼠标流观察窗 -# 常规构建保持 0 -apic_diag=0 - -[utsm] -max_segments=1024 -max_capabilities=256 -max_pckc_keys=8 -dirty_shard_pages=1024 -arena_size_mb=64 -drr_emergency_pool_kb=64 -drr_recovery_log_size=4096 -drr_heartbeat_interval=0 - -[dsk] -show_logo=1 -# release: 要求登录, 验证 USER.CONF 解密链路 -skip_login=0 -default_shell=cmd -mouse_init=1 -netman=1 - -[debug] -# 全局插桩开关 (release 零开销) -enabled=0 -# 日志级别阈值 (0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=SILENT) -log_level=3 -# 探测缓冲开关 (release 关闭省内存) -probe_enable=0 -# 探测缓冲容量 (条目数,每条 40 字节,默认 4096 条 ≈ 160KB) -probe_capacity=4096 -# 统计摘要间隔 (每 N 次增量输出一次摘要) -stat_summary_interval=256 -# 模块级过滤: release 全关 -utsm_trace=0 -vmm_trace=0 -dkm_trace=0 -dsk_trace=0 -block_trace=0 -net_trace=0 -input_trace=0 - -[compat] -# 兼容层动态链接库搜索路径配置 -# lib_path: FAT32 根目录下存放 .dll/.so 的子目录名 -lib_path=lib -# linux_lib_guest_path: Linux guest 内库目录路径 -linux_lib_guest_path=/usr/lib/deshab -# linux_lib_sync: 启动时推送 .so 到 Linux guest (0=关闭, 1=开启) -linux_lib_sync=1 -# pe_dll_search: PE shim miss 后从 SYSTEM/lib 加载真实 DLL (0=关闭, 1=开启) -pe_dll_search=1 diff --git a/build/configs/release/firstInit.txt b/build/configs/release/firstInit.txt deleted file mode 100644 index 166c057..0000000 --- a/build/configs/release/firstInit.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -0 \ No newline at end of file diff --git a/dcplogo.png b/dcplogo.png new file mode 100644 index 0000000000000000000000000000000000000000..c788790b944416ad27c16519f9a48e503db0cf59 GIT binary patch literal 248384 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%0AiAN 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 x4}g7x)n)7u=gAj+m~!J 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 $S13Vb{6`Fi3PxU1!4`xstwItd0+x zy>a&k*IqGIpC44;2Lmkz1i+XC&le27XD)j`g7H~&8R z_t}N_ckfq6JWJScR*F>4Bqm=?**uTq#+vNDm_RB^Rv5xxS;OPp-3Gf*h#N4IyYGwz z_1YAtVexZY1f7u)+!{czVSZfyl`xnHLFrlZ z7{vB~WgswYK)MDkZ74~Rq$zYe#Yl~?d_ZG}n`n=2!>;=tmLEO(u<-Q6lp?)jWMuKS zi(d0f>jsC1zB#&Q{k_+J^R@#(pPlSWUyc=-v%nGn@7}%T_N7Y)|7l|4;QJQLUot#C zF+n1Pglb5Yb_VAjoOEt54r0VrqAY;$Y-Gmk0^?Cd2}WNLRRhzqdS_yKd`wHnN)lV( z=lHt-9`o@gdsKY`E?4(V<;iBWiOW?w*Tk%7HIr2gUE?xdXU+`ad#ekyn|gv&2&`Hb zV3R_Uw9!cSqCR~PkFH#Sd;nqC+Irg<9=rMrE*S3`Pwpv zwvh9{tD!wl?Bm2DQ)TC)OCVdf1eWb53g9wz#*@SZ?Z6|$nge-f8*A=CnVOsw;Ox;E zDU=dGQU}5nU>Ji+K@lOH97m$sm{&`&YJMHb{)5-LHQATi^WNo0O>M4b&Iv z@yRi$qz+=ska^Zp!1)cX5&_N(F1^Y7GeaUfOp!?aR{_Eb_Z4TW@s3NjAZ2C)@rK+1 z7-FybYy}8|^j0?}Y&2yxKZH7cC1ITPgcKlvB@kZaLT&-$ZWCh0yE%-t)R- z*SE~OJTv_W-F6xR`yyH*J%8AY$I3dnso?NK?mktpFP4y9%eb)${S*TOZJ!$%2pfymX`_L8^)ZZY{0R?&6H^02&7hm%$@4Nb% z8$LBOR9`+cIG@_>NocLXhO$N6shxag2*0`6oYonj%D|Fx8Y62D7&RIGTLhP0Ef&Pv zD*~a;7;w3Zq8b<(I4f7xWm}(*bLp>xyVh?&uKE(E2$i#u%zz{*$*Tf6W2}N59m?6L zEK7^e({A}-30r#sureU3jD=(<@>Y)^XG8(#PA8@~6Q4F}ZryD9 zx=E$Cv}gT1HR&o>Qea4Z+w5&ZIeX7Sn4T;(q3?604P=bzVxSx!LJLL1XZ~ISQyzbB z+a^triWx-SA|xF4*fEhF%o3QDOKIu)M*>nl=e9sG+nG)F{>6<23qymDIL90k2EjN$ ztlZc#>eNCyIR-N}hBYfzVnI!#J$?YYH{Op&w>*OM&>@lLWLs&PShQ%##Om|T+ji<{ zr(W^IF3>Dysqo4Y)+iw!x9BjE0D&jWsO|FbGc3SmTrt#VKafw>Jm zltQD80ojlC*k&{*cDGMH?Ud^`YeJ6XUOAZLwbU*_yZ z;{z1CIaN^Shx>5j5PP}9NOrxCxhk;UsBkxfiSj((fPKz3Kk39S`mQ<}F|CtGN5pm*ZIaz!Jc1x7{}V<~P6n^H*MZ z?b)emy(w)UYADUtWrPJpH5j8IF8!DU?{8(Q*#R2^PbCnaHjey{*a;&Gv}~IeC=Hm%-pqqcav)f__Brai?~C;&wz2ozb{yDb zChLp@QV=G95|9zp6QH3P4VpropFnf;VeEfm6Av_oH(Yq(g@1k3*FU}mb71pKl*^t% z0)cS=$$MbggOGAee;h+pl?3tj9^N@%g#t5P5OUATK2sa0y($iA3}XHCVvq51Y@;vB zVScM{%0WmrQ^qvSvs%Szcm;?}{9ASQbM+@Z<>W^KV`Q1x?A zXxsI~-)N@jCgp&z04A)YO@7aOS&(bnZqjY+-LZYc`fG3d&nM7VardP!b4L2W(u#cX zi=W*6|JZxeAUm$>&hvlHy_s*T6+mGlK!6ZQk)Q~Qq$rD|7RXv?$!fNNV_*OPAOJ~3 zK~%M98@B9j%iD}Yc6gE8V&?C?z{3=)ET6{ zg)eMc5F5+ufA*iF;(uMT>@02PGPn4${algpO17BTi&!zGzQbHqiX?9;>hm!mL70}p z1>%DRhl4dTJg!SWVd+Zu)IZi>15$zI47H>Slh49Wj>A^Y;oQl?%C<4{p`Y9Hzx@5D z|NX=G?iIjmj*$_7PD<$Ht{zMlW!`hyKSfoSjnHBo=(4p?QDd?nzgQfnmE&+_XQZ&y zSz63V|8+&$m6T(5>2)hz*};k*1g7r`QR=5vmx6HlJGAgjtc;)TTN<%U(26cu^s)51 z(h}N#-+AE}O9b~d#P_@Cs1MoT<+e!~^G&5?R#%AFAQBxyW-t)eoy&W8T+|x2&_nsLE$PcRMvTdGfa{U6^Es+Hz8Rjn1jx%uUCFX zDX9A|S>{75g5pAqWUzvvOkOB9s&r?vdMGhO3hNgI5zbgy!iG8g{!&_r%8&yNpQBzu zCQR6@v`mi|)+#BiTbzSAM`{7~v+&K67#->X^;39m{DhZ0nf%btf9MbX{?q^dE&w@J z32X?e4u%;X2*;*eR}@03OcYsWOh(z!C5W70(e7)_?_qH)`K}`5y7XOPjPSDOr8uWn zy0XLg4`aljjLBW@eF(?@g)FPUQk?O3x#^b+M=Vv9D3)QNq5<`{AdGF|{^?OZC%&+n z7uHK+v*JABLLdrCL21x0p=83IhzKlcQAI(ZUSX|)IuC0IDj9~Phmi`Q0YO!AG^fwv z%(16&_|!N$(`OV_D-fSSbdiahmu%TKd)2P%4-Jov-0}ER2OsJ2)Z~Mo{r@sV0U3!z z3Vy&Pd{tt9;TJx7)w9PZe*ck24&F{&8wDy%W)LD3xGY6tYalWZuW(|D3958!hd4}t zR*1l|gHJg*P}%zNd@nXi_i{N4FHhzP{|7f-QGc(m(8@#k!V}?AKEpCA2^jiaF`LWp zC_*OmdD{U5(YMSROT^2pkz6E9P`~D$mUBBT2?#2zu;qUDF+ms@W1uWcLZbf}08{vl zg{iMeiHl#GO6!=9!%v@_9d~0l4KFsX zpxoAgqIbK1q2!@j)Ee#AQVoQ4j)FBDM?fGUZUIuRO?1gZPUnrkQt)Zt@?Tog1=<&u z+<$(>+l!D13MTAOMT@wf9o?^~ie-XvMH#H3jbX9ned{GH6TKu$+gysw{=!X)^Hz)V zse&hAm9~l~|J=N>Zop}EA zv!~8H`Ow_N#6;=Zj_&P9B(j1iEMFMD^2+NrU3S^kKY7o64}6S}T%)L0tR05sLG-!+ zX^J$hBJ&2$l@xU8;xn%-yqBc(*OVb=K+K)&t-vHWenBQ zN1k_xmy6cI)lSPSFDqS!MbgT5iC3U6n3?)9V5J30``_PpPWO9>iN}`sQ>Z$C68NI0H5X%dg{QX^a6~~&F0C~BLb6<@Xqk*sJYT%MsMi=Q+Qb&Q z_%Qa06-=?TE5=y4=vyj175bZpg5X|!Vdh)D=vwY6SQe5}o(shyDU@^MzGeNpuBf!U zf<&>sRE$qm7%qYaNMNW81t>%^Q7GW93WWnxi2H^YxXcAhE9XcIP_qX0gpoo~u?7hf zQUUAh;d*6Hex~a1>AA9uR;ooK+7)s0tRV!=I zZ8?ZoC=Yw+BT3Ai6lmHO&G?zqNnW2ngiY`TE84rlk z+PBO)vOEU!`~;mcF{PBMD;zXGJIBZOugb)P_r34e#-2Pr z`K(*JtKJ#8l6s~BFAhWoPQo}4lD-ALJpLhOwKRlNX?E<7AyS1YB#iGDQCMC_R+lV@ z8Y%_|F+{_;2L&ui5f-7vI9poql%h7oNS9iv>>#Cm^|Z9?y=7M%T@x-mgFAz}Yk;7^ z-GaNjySu~S5Q4i)Ah=s_x8MYK2ol`g-nrNFp8s$@oKLe>&syERd)F?x>Z&SMCXN;r zd%B0e0R!((b;>%Vj$-@e|7Nye5}nZ!vF~VLHGMCI#7zly-F|gOhM`6fqT*ZbtqvPk z$L)M2>0S#~flouZ`xg92>bpTZV_PK15j%8bbuX-@qSaj%QmR>Gh{d)R;apQT^BO;$~+2M8a=>I<}q`YK3kY zN8%L>&8&^hI7w_-m*n~7q)o;$2=&pToIzP4AijXa)=nv8O|Z(zO3Ngza&chO+OWT~=nL1m3qH^p zGgnv3Z57H=95gML&e@cQchsSfK;QiSZUO~BHX|^XpY_v@S>`7CU5mXo{xgbOoMKAu z!~&y|5QYKip+ZE50t!Jd$``~7;>VvfnKvo0Rc;m$3X(T)B5eFa*X8XH=kRNA}{7q(RQs3-;i zQ;wU2dBRHrvFr|-dIZCOt1dPXF1^GHY+Ou+1U8%TL~4;|5h`xFDU_zFDh@ph?$qlqt(#B#Gi|Q~%gx7V5*i}M8)CkszZ1KCz zgbg;a7}{!6rYOB3JmGGi(%5%xj#@)-L+a_3%p9Cs(*VVxqWH9t@#SPCoP{Erzke~IKAo|fBG(f@9m6gD0~t#1)DpZpzuYcI)_wKa zDX?xuPvCNI7CO=@o=kg?K}q{@CI4--#>xLBGm=Qp$(*2#kfd5gRYs(|=rDj+5eS&# z#pjYdqO~T-)L!&zqSa1G1tbz53#wWsfT|!pRdB9Y^;0UefJDgfcuW!4yIkKn$y=Pm zN64E|yvcq?P-vqdf*(lD2{sJT8NN}=gs?6si*zoXgy(*Ni$Gk|*2cmW=yF8MrUf%F z6f0jC$M)+2j=xO%70PDpm5J=!kWza&Z!q(n(c&H)uyPAZZg}eLko2!G6v~RSjv$r$ zP8u+u607T_*@#MJ+j|FEyWj5JkJk#g45ex*$;M@RdevuIbn%XL#2_~|DxBz#zc?M<++A5MO6%i!* zs@{<^-yoqxH;K+u9(2}4>Ee?@xj^?i|Eq#WA`y*x!Ih&_g@&esBAYvr{VjdV)0YUpFiO&BYZC;QuJz)Be``R|;+Vsj%2;QMx0u?+{seP2*AC z1gmIWJQ_!6?|WqMahe#<7Z_u9B1%ec1SWt*uz6<136OcoSdj$^Yy29PLXqo>tyP_Z zDpblSt@Ea6mzFnBU-PlrjmAJ(Hj?a9FyaSf5CpcOH z9PK6p$#;c)e;StUCRrvPMpZPa@?&mu+|JHlU%emx&Hg}R#N&3+|4NyNtOz;$LU7A@ z`o2fTle{F$?ItJNVy%rnZ|!&K`)zB?V(DOMgi(p&BiBsN-+!vZ@2fbWZ{$5UdCwGC zWLSdXYvAjD+Y^=5gJV>F+MZV%F)W}*r zs68pMF_2G^&hwG^Gf$As=ORQ$37?uLGiR7pEC|H)?RtGG)+M-T?y#z^Zt7&)kVnP% zQva1vho$yTz4ET2{MUM>EyFxMsq>#3WF5{D$0BR4wMLR(iqrg$>mC6mae3)XWCex? zU6X4Lzph+(3Y?CjvF?#Cui}F`?~UGHr~EwiU(yPfjJ$5B8XK!!@VU2+IF(o|b*wyp zu1U`g`s924R|~ja&JNpXrYJw`J&rn=%Qms6zRa44SIU%BO+b9wzSy?7TG*%PXIY=D z?y&Z-YEVuXskjWMP+?Oo#JYTXyQuh?IWB|R!COTE>s+&vYwedOQ{=%$xSQIBFU@4u z8EmP3wK_wy=%M_N%+|2b!?&cWh4$_xdxvQaJE|XcDZpGIKi6Np{OL(1+*~bzCgx}D zG8TbTn#Xhv0{o>32miK>J2VyW65A}B4KMPt9V|3tb8Ef2j_&6X{W4G5hjeK@Ee+w} zEB%M})i1nz>JpxO->T+qIZ23#=igpNu$Q&s+IY?>6GtoKAih80zs?V2BM$s!bMV-{ zV_8Gm*b~+WI!AQQgCO^HEH_lH;vqcQh`5NS&sV^9&dW~x3lI3L_wm;PkrQzmG&~&6 z@`qTmVf_|2<_tx_WG_#A+_`jxMz*$`O|A;tVg@O|l4^>#=hyN?^mkAqI}zJ3l8Rcj z0o~0Fvh1L(Jj3FmxvQ%#*ahe*NAcWEc6jkO{#tJY@h#FAl3IEl#KKxqn2>kluO-XB z^YOBNmkFBWT)O3Gxj$Pg?cF51N*1nvokI6x>fXwDH_R6`SrZBt^t)GXY@7AuD@|7X zyw3@^7WiT4M4oC(w^K9S7~C}CrjuteVgL$$De?b6ug`Unm!wRTroP4$W!c5`!Kw3m zaExhiKCw&YA*F&`$BdTv@9+Qj^MA)DxP~s=y*J$&XW40i?8~U+Kui}aL|=|pm`~>k z9&8UNb(=Xm*YW+y4GMIm$gf<0H1AC#P>lSwc2>lvDrwa+2@MUMb67?lUCyo+iZHfq z{DX_bjgN?otOpO=GxLH4*=-h@N*(QP!g~XjTR6xwQtj7T9R9dXD{LZD+u;mMVxc7= zSgU+Nj%C}=D4IOglO0c;md8G6`&0OTGh*TSK;_uXj3u&?GR^&zn`I28HnM^m4U5`; zS1NGKG-MVq3yQg-w2D;8fBxP?7O7xhiioXX7cmGv`KCJEI@7p#IIK-!dgd8mSlt4N z?cv~wK>*jyZiy>`=*|!nlq#KC-{+(z*CXX5dr~Z|@QPruDBjPC(&!q&_>^TL3*ax2 z^;JGzXq&lpxr$cJhCvYb(=KDd+vIPq*1Go}2xxbxggwS3QXy}wGE0k#BTEVfRP~h| z{noDN?_b$3dEv!rcU-w?@5lVTCtC4VinAIN3{|}20-Fd>R`rJJ6vF`{QG}XS=nDE+ zpDlZA$us&h$=Kjdx#ymECI`!=*>XN~FBxjr-C$vD{R@we3*o@@@k!tWl*)6O>MFI> ziV8wAft(7B>93tJeW68wDy;j;W6?l_+(w-P&A)ftn9-fXIv#i}1|6OYib~9{(-W~L z45=P_2C?d>5{U9&LkZ151PQC;7f~YNtkgx_&c)8c^uKm<+r_3aL{z(31Zt6JBgE1- zi8c{5&MMVZV9D!jBA6~}E_hpS_9WgpL=J#1U@zr-Acuj-8bGnepyR$Ump|y(#_p)? z_vmF}G?mm(Iy!WBjBrcp>!jHek4S)7H0EC3PG>Lz03Sp^kN@^ZK^1@mCGHPz4*pi% z>(ek?;JR~93DGKsq?);P>%Uy6@`PQQ3Uw{)miixt0p$ScZBdCqa?>afAQYrWMvF+< z&u&04Pnb_fi^FJ0B^J&)G-sp?DpD635e>zZ4VIBL@%;^HG$A#_;8HZfq`9CaK|J`N zT<+1lp#y&0#NlXMS+lfMYznr|TFEV2s8BNSJA*&p(RKaj-s*8Z(bvKM=&0pOjro+w zic}{?fE0m^xG4dn0oM!2yj1-?aC9EyrjKZdSOH^ZdlbGF2~mhXKH47rt}ZSre1a^L zVe2{wC4i-ZB$0)F@erOcV6U>l&jG{H1G+;&>B9{bun5cJA`Km1C^ncOia`8Lki(1ouaYZ=}2GV|91pZwlG zx9DG2U%droQXox8?@9LAUxVmKOf|G20U@Qx5ICB=L@K1XCqvzoRS%>CRyE~P(S~S; zCE`j&Z78stkJx%|Z!gHws+i@xn2ALSvnruP!-e;7E4}1V1`v?$^O2YKk?Le*c~!hk zEzbL?+S~5&eA@&s3frgz1r&-%PC-v&ObfouP1 zPgdD`SJ5U%g3;#@NeZjPv&Mtin>`D&1;laLlA?%$JnTPz4n>-9V36mqXZr7|NlnD+ z0J&P0=l;+%M@W8qt{dg_gOs1U&Rz+nN-|D^pgwP?Al2(s5U*bXmQ(7pU|3AVZVVF- z!6wL-&}Iy*aAoK|%5#0Y{WeMdX%V(}5f8}RB*Lzz?NrkOZl)U%cj)W)EP;n|$#i)> zm)m_E(-gXFREcC!Y?epge?p$$CyNMczQT}*uY5&L$;v1Kg#iy9&G?O=nRE4@zB{(k z@C>><4gpXH+e$;Rs5zw>{j~`v_784Imrw@5z)(bh211{?MZFC@j%X=Fn}XE#ai)KK zpJkw}-2EeZsf_8Z;yt_VYX+ALt>zr-N3j<-JBgxeAjT~>>p)wk*=jo3p8L<_d*@0o z5x1cM3CXJe{_WmrZQ#)qi@$4)Opwb9%Xt^2t@)lL$zYz>ta_C5h+CJ*8(6Sa_?(aY#4Hp2`dYw5p0xJtX4Kfxz+*l zvqg%b1?d}wBXHsxBRgZD>#V2IJ*Rv;I3Zp7DBfTSe&8~gih*|9z@~9?#yT2BC1X|2 ztvIRQ$TxwuGbbR+LzI-3-jk9Hf#iY(l0wdt8riRr!(4(vua__9kC93re>Ik3{cqDA z0^J4se|d9aC&Wt_x{M}Cx&*Rf0MHX6=h)-J>F`Gz>>@AnZ~9Da5T&`O@~j|I!Z}=l zm8J)#{2~iPO%vMPU)db5y&=@!+52{?s5K}=SRj2);X(e3=A{@(I`ooyPV6*1J3=-! zK#op?LX+Jd69s{(eHU)_GL->>Nt1(RrLV8BztdHiT${osgJYbOWeUombR)-Yl}W*K59E<#@mFl@#F5L=P>*;vA9)GABU?3(-97Px7v{J5< z#kApxEdO4TVrozU!T}Iu2*~&7R|6@43AWW->XO)L|Nj!u)KWh+=ScsHps?_{#w$SJ zE(YPjE}Bvnim^rzQ6um(IE6E2%l;rkHWo+`?A@ttxcxvPP7z2EfFTuYIK^d3E&A6n zKTe%@nIndLRc`n#TZ}|J7uFw15=sR+JX{*;LRBPk_6(($HoZT)IwPlvwLKh;FdSk*M1xK;C z0&$Sg7f_%sm~W^4^FZV#E54UkE;3|s;!FTUKp0iZ>5@g?8Gxptb@S}wjqd3uNP407 zc50N`bOSLW-KW)RmhpHN(%-(j?Wc0GrvJuE?fcNz){4Qy&R3FrO~b`iW}s!$wM1QL zm_}#egjI#jVI7^{mM9!#wZYk(6gS6ROv2DzcecGAbzy<%UMp1tap9E%y zV!}7@MiEC5W24FPj=~|+eFtmz9vhRhD&wo-Yv3bj!&E_Kps*68G|s4^Q8+f8O6wp^ zftEi>>X)PADna$H7D2_UP^X?rGpteYiEY|hm|U&b)C$*q#0x+6ZT&(Mx&B5{EWKy? zwH#Sw83Dn7x?D>4s!ZKgodHcIi%N!FJG)OeejaKONkov<3gD}}r`_u44w}V*9A!hZ zD!v$ttUTpxYJwS^-_PeUqhi|kEO$uc7gp}f`%OIBe*wp$Uru^*W0n;d#!W&(kcqVy zYwGK0D?&00Hy`8>fdDE5t#C2e%OscwU!BD;v8(%SYO9%sWrZ^*vnx&z*rBe>IpexRJQx`3%%TZ>HL#g&NTcLCR#_Ro0e$R=4@Q2fR$Hi%A@B6>*m@3Pr z*|KQ@Rk|9T;M`DY_+q$YSg{&sA0im4Ahht{D3SSK>E55n`O{|O2b?FMkmkR|Z!C)ROb~~gleHiX7?e%)W}po=hyKxl-9(h;j1pqF{yk3l zuOuRzvMGddeTFy*oRJ1&wSub(W2_cgf#}GEDkCm|TXHvVjK2w#Z@DbOa}2-%shOmH zAxC27|737K1jk0BEJKcV%6-Uk%X4neH#7me zJ6_-sG#WA{ra?YscMcGnp3KiCglBL`#D%O-4YGxZfQsE{z!ln}Ej>46#LC8ic7ry% z{H*V6BsIP(>w2gc$8%=SixQn5rP3q|QxjHl6W+lAA9N_{mcG5O9p;uJcP`Df4mTAI zO|N!#aXABK-OeCwj-Sc+Frdz`FhW`qr^}J$j`AoOzK6qkdc!MzU#F4O7BA6bvlyTi zEg!z$#A%rj6)D<+rVMQk2P}tX2I`>SLD2`7J7;5sGij=V*%AD8QqdVc6(C|Gy8ce2 z&fal@&L)JLxEGHnd8>5_I8pkDH+nn9BNdhZ7hNTWMk=uE$h9&3`aU9bUweS3m!286 zgz|-O*bn|nOqO~Wf9xhFDmPe*=g{qf#wu~B*mP=N=V68Ab$}!vnwB5WH|Rf4r^I{# zHtu4jaYVrTV<0H-!2VDRn@XBNOZ<(sbH30P1Ji)o+y}QkdsJMfPhR>L@=vvYmP%v8 z3b#S>c>>pTfGmKku}|vVh0QE~Pb_-ZrJ0$fH;-HuNtKcB(S9~V>~HD3!-){2@6}6; zp>`7Hf2rb09-}?SFR+aWpcfcL#D>53uBw1Iw@Z+Eu`ME*2C*_wGh(V-7@pA*BLXy` zF~b|6iGWX}(t4}{k+K%R>Z!1YrwQl_-z6pg+wPZc@4c5(E9z_A2ypJ${h5Pfv#zFw zjD)mCbW{p&ir?iX2Sz#DSBBJYt&0`Sn(?H93<$JS-tQ30v4mYYWy;IRDJf(6%$98E zucG_UuZzlkp5E0$tc_F&OxU*Tv39SNC*VrRGQP(e{^SG5$j{Bo?BMCSyeiG-egAsVs9r3&;12T%oe~c@83k%yBu%f3<+hfGUgMeOdjXOr zFnb#fPldID71Ap`#4#P+9-nmIrO+YFLqIx%^A-YhFDki?#8w4(qHQ&lAQhCnbjb!@BWI zb|KOU_|QK=9R%w;wB|XL5&A!i>{Sz2p-YORrsYNKO)^ZFG7Y^S4>kvO>#qn4{IfdT`|MU^!r z17uC<5p>cFCdQOp4<`B+wa%aBwQ9QV=eB;G$1c4q-5^)PCWtqu4ZO(AGu+gzhuV_* z5<$;2fcSX^4Zig@gd+{`!<8W=!ntxo_0zIjp0!-~AI>`j{4e1CTijE>I80Q4uAFpZ1?7OzvZ^R2abFe+Bll< zo#Xmq1fy1K%?*DGO9N~78IP@$!j@c{F;|P8={rI2qX|Cg-qFgrRFe6rW|bJS_y1N+ z12HKuU{1S;m^dDboRSDkak(=WP!URrcX5@2P2u)#GpWvjk)+tM^7}qZ(6!U1>o{l6 z*ZG&0=QJ_P9T!4qkH{1l`So}jSfCgfOi>I*%=N)W6sU044gJ9vw$!`oA^x!bP%Z5H z&>`$R7QQ+DKN-Y})1YIbqor-qt+rGSu86?>CbA;!2P+qk%{wQ#Zn1JTt9dqbiX==1 zn-KK&9+`I4Y@O(1$VW>i)AC|<4wQP zz%EIl+5NbDBuTM^c9;vNvC*fraiH2rnC9i-!pIOsxB=#Dqaz)#JyR(D1)}t@nzLk- z+><(F6(q}TdDgAm)A)&R(#Okkn@?Ck?x)6wf9Jf zm3$KZ%>6ZkTba+D6E_=C8!~8WPfYn!!=6mYXH^7cIGi*?F47s^GI_Drn_V*BAtd0oV9(oH^P;(%o1LYL!w+2&A^$z;AG}*O6B8{<$uc$AvrCM11m>N- zx7hv>WBK@m6~bOe_o&k)IC*Z)XVlf^)%W(ThD3Q;`~m0dvp;GYy)NKi>4dJLm#)L3 z@6R{nBGE`1Zf+J5gMl3u*QahLG^N94@g(-t`qkdG(G3I>>_M3i{y6j-6Z#IU6d16T z4EWu2$4Z!ObQv2QdM)-mT^4HxJ|4LTZEjX;Yb{-l&;MqnXr-j2*2o@vhiw8u(@1f0^t|Zrwfz_!T{zUX2!^f3%UD8A8c%NG8)WrBSoV=B z>Vp00uP=kP1b;fqt?zf2saVRpgl|7F!OWDi!N=u%4spBBDVuVCZNKzDZr=Uz{=%!! zs`;JwkS|xUhxr0xDNxbSjvLHS0L!iDfcxAni?fi6aUUkc^1mZKZ_jt@NA3s!-!8UQ zr~=-(l;GgtcTetY1wOy~DnRdlYuf70MS9gfBtBbdqyuhyov)WeJ}+gMwm%0aXGu&< zOrW!~^N>|~&lCUV71|Tq)Wh|&deHM=bbTdrkMGG2)!zXs=o4G?pqJ?8Ny~1aG#CEN zzJ%pqWi3M?>^;_i9bBHzUk@j5Pv<6=nhl4^7$=sAob0T?w-!`2hz&m3rX*;^?}W`m}Syqj~lUc{m! zeR~dH&nJsi$abt^JffRYoO&g-R?sE!7tGOvcELelO0logmbG>xs9-^iVp5M`IL;YR zhL=~nJokZ}67YZXsC~hMNAo{2WgL(I{736tI#Ci9^od2-bU$5D(^AOK>_SuDC@%ZX zzEmmUp{8(h>ShYXK@`K}20$SPH-F8&pr>~$5TJ7JXmj_g=tAogY;`--7>R0+q*WCB zn3?3>a(+ZM;%P?c{8^RV1UZ>*D;7&}fmb&V7rzJB=k2*_?--WE+_4&~zc4^^ji&@C z?yZ2_Y%?m&6-aFOM0}XZO?lFxkGK7mfVHxMDrY_gOKP|6ls?F-Ld_m0!=X1fhk4SN z54W64o#&bGq~6;o{r&qPuRr}B&i6ka$v(F9xW7BJFESqHYC>9Op!wE{-}$Ecc5RBK zc^u&39%PGImqwf|FE20Z(rhd{wqnv=Q~eO17JmKb^^T9X1ohpq^XhAd*$C$eneV@P zZY70WzvRQ!)Gp0?zFtRm%R1X%X~iv$m}+d%R#k5F&mo&g6AVR`I?B zpQ3+l$!cc(aBOIMq|>jRb}8JBNjk2o6>tBKcm6Ex6|@JO{p)d-T6uYQ$Bp{GcNcP) z=To02JfdEvMKU4(0cL&Glf-@yw5zNdWIU7sc3J%}h!ADd&rCdL+G!s1fE+kz7n5CpI zB=wh^1L|g$;}Y_ljOvmr2?sh$9EVeKdBIr8%dZc(;KK|}?zI$=} z%%{J)GIUb=uitC^Z-l2O56C7hKr#);lvK{$aMmXE>gd)wo>E9LCAbjikZHPa>`(hy z!zO=T!2Nb8o_Bg6lVkf)mtK$C_7y)k@dVLsj8@NN4)tFa;|Y?4H`?vSxz9IRYdaCTbRoG1FWQqOC%uo;+2qz-!HstxO-Iz_Z8a@`{#L%++3k3F}= zw2;Q}>Q-sp|81jsD7+^#lQ+QL)U+r@%R2|6-%9VF_|LoL7S69(%^hrE%m=AX@yoUD z;>f#IiJZF}vjNqbYT4T2)|`hDU$WcZpSjnaw|j%#YWG@#(Em$(S-QKsKb~yy`M=z* zEIO4hwg46=cKgEYV;$Hn4 z?V{jS7n)@487bqUr8RQ6e->@ZJhtfo&yqNIb1w3Hmi)FJN*~@IreE2>r;(3*?RSfM z@RpVbTOSh~bb)fsp5eci(sz?RwV(AM?qpVODh*S!WWKP`>ShC|7+HSo;Z+zu7rhL_0B%kKeU zgDHUr+@Hs3=!vcYmqWr5*l-25vqtNk$5O3CR! ze6`?d0~KOHgfWnvOy6fk^{Po!5u9HFK$Q?`66^26+)s()EIqI9KcwRCnYU|ZW4Sl2 zxgFXq-|DU6-^cmikFq}$OWLP0Fi1&P<#Y3ZD$t4*qkgp5L0Zu1(BzUok!VAbf+=6` zB)w?GK)D6jyC_k%kkp!Uisqu;m?iuHt`)rH!67r}&$~^tkzum#+d1ynNV}T0XT991 z`P28e7nYxK3R~$!PMvJ^_eq=^=cg?Lk#X;@I|DZ#tE-%LbH_AX(^%A&8Lm>7tj<$n zCL%USyD(E&(kPubGsJy!lW;ZoYManuU$A!~p>%lr$)1ashk3D{N5jY11{HI9)diPOs;)aSk94=k(*<)xr}@rD>%jQX%B(n z5OSGDNLdKa{OFk_+Hg%UR=g)mq?R^Y z%IWC-GIIF4q2<~AU0ElyMIn^ZMhcxm*WCBmAlbxwv>+&$MxVMUgA^TRLBoxq|f#GIX~3x4&bc3u9D@aXx9xkns+3PiIHVShq%^#cDl> zpiwjV33|cKcLMb*X*x0Nzl!S%APldPmqqQ+z+;K0h!lYwigX>Q#-|q>rZT-yP^&wz zGctx^cyOyVPcRquJfOw1;UXefejH_@3~G3@NY9xPE%!dNmawvy8G#>e$d6Fa;tlVK zdfPwXa`^88x`+KQLwkOmmzRe^gJm8tHAO^Q$2q3A^^`oqz8`kPq5 z5g`I9gLQ?22%plM>_AW><|iw{r^H$;clv5huF^}l0ZNXw^Y^*nd?=Q@&i7%W_lrrQ zpd(}@;kTJFh}?ElE)~S&LLxQct7xTd2PePye1SV@3N(ztWM-PPsxNzaK|$pkCTx^O zXQQWkg-+F5b@wdUR#rlq)tbpIo&}>o#D$coa%pD~1J$XF=l8jW3wD$HRzat)?!ukNXIZ1qkzo7x8HM<9qJ=VCgjRV{Wex2#`wuS$Dh*x z&i`f#Ut7H1`?uEJ4*HCy{5Xj0`NWu@l?f4&X)$^U*>_VZj8o3#5<)9N#=y#vJ0@WG zuLA!J0Pj$>&J(j!xqfqng$EA#FY5+m-1mlYZ6EnOV643BUrtf)z#tF0n~_CJ#IDAjXyDJ>yb=yX)pyDd5;CXer~_ zL4v4Nok9F7tNgZ#_ZM<$0GD$XKnSR^%uNe$zLY35g+V~V(loEwAp`O;!+RzZJ$z`}S?AbeN)&kC@fAlfB)K+aT)d zpH4wBNrWn+6dl@;Ls%yT>WF`p`=OG*gbn(th+sa30hz$P&gXte1;}D)ci6vKJ@J44 z81)qg8374^ed0qQ+Xc2eoMDd%^kJ1C5}+M4X0EG+bB5_F$H89L9OO9ZP&_v5$EPc-N5o8xv? z4Y|v4W|7`7p#+~pMZq8h3M5dLhN=w1g1#4@)kW8HeAjfB78Hs!jq>2Gmij`q>W16> z%7E_H^J1&?a)W!X3-|X=;u5fy!68W1p)xRvA&Ot{!NG29v_BgmZBcF;Uu^=XJA~?9 z%LX z?V{xs3Ps|kxPU8es7Kr)$N1kqUZU4y2*Nv^p*wUvqdLJ5n#w6(-Tv56%wbT_8>gu^Pbi9AnF0I;CI9qKPOc{J&!uIfWg6||VMh(qgkhd&$(-K9+K^cClw;wR#w zkk@xu?0*=6&{Rr9)Tr7ab2p;%u+nrEu_}cBjMjyPx=(Uj*W0h=a2WB{T{k?)o~O6o z@0T8SCZe2=zA@I&JFHv}^3%~C2N6h)rw4Z+^L-xnKW0|iZr$Pq?~`S6Xsyis{p}l7p}&g!j+W9DEFi;QF&DQ!7MT&^ zF@dEZ(H-n(ciVT@UvtBESiY1|Xu8>rB=voTR59n0;o#b2IJ(vVGz~xuy}D#PNB|ei z;*ox~6BuTt8<>%{=tn?c@c^;2qW>W?7eK)F?8yzv1*!;P3MtBzz66-kI}fXjjzPz# zl6>>$_MhK*=gE6(PNVA>nUTv7&WHP&L#u`hVP5HC2~wFc7zzdhfdQNTxR-%&?kXWtwP@haO!C>~=hObBH)?sWzR(Ehfi z^FwXXtmKp6l}AuT(T+$s+^2-eR0I{p`ZN2KI_}L3zIp!)9Zok2oQxoHdh6qkLLTQi zkN+|$j?DrV$E)!0j@DC5eEP=6M+HA-LaB!~Xb34t*|YqJKpPLIDvkPS;ag<#=cr zC=|^Dp;AY=+RniaWz~_0^Q!n5c_2VUQH@6_?3m zXIKfg?SZC#Dp8uIwhhR-`Q!U>&AEOLi#0D(UQREIep@f~mE88r%D$h5kK63;ju*x( z(z|XHyw+VVwqli*YZ1*NHPWf#A);*kE{E4+Vynu^jMa?;@vxS&eq8e>j zVH{H1bsu`;b=Lm&2VJn~?@u0``fhDQGF}ff0w!?Tle7V1`D|YyLVQ@Knf;D2ai1tO za|HRlzxFJs0i=zPQ_4?%nBu*ci7^vY3Lt{60Z0RPDe0qu7}{3GxstXx-%momrHM)& zij=4FsWKj3Yzp3QwFtb<=LbDPG6p!{WW{lz^aWdHwRw~F_7{_?QcE7%Um{naDUvav z1xu#?!v#oSb`(Y;4mS=F!N^rXlfWLr1wV?Q^@+k2sPp{kG?SMd;5Cvnjy9GMTU}O~ zW>TZe^^k3zLXaiSQGyZ`IUV^9ec1k%Q!)b%#SV_%|Cjh9KKGLD>8`ftb<*qo--r>` zv37a#&5zuFNYGFy(VTg~?a@8+t&00)~pBnv=m0 zgDu{^)(j~DR!oijAu_?b*oQQ*g0LMXO@oXSzH6KFFLAma70qzVGU| z@`rT+0ztv~`%R)HgrcdjY<_L6k>KR4j8gYVpH)tv={@@js*btw&W2tFvO?hkqro|~ z33L=uYM{fz=sLQ7e)uSD{6oOEof$+;O9Qz<9ZV(o(?j~siH^5ZI}V{8kBJ_m%c-j#ZY?*B5YD90jz&*+lLUu(HA$< zJ1602GuADV?`>4lD=S0!63j4Bp`n4PLTG~P!s&?;aN`+yddUJApw!_dCL@PT>kPl{ zPic((;V-ROZ92M|HgKDa)Y|@X>NYUIXFky{U;@h%Kd(c#v(n0&5sXA$C=KOpb062& zC;Il2yX&RsBIx5lI6Es%SUO4LO{FR8B-RjLFXAtL<_$wvQfqUGa~CkvBn~isLg>PR9|MHHvcTP*&h#vsp(=Bk+<=+ z1fmu#KN>KiM+6J3?M3wq!Zsx!3=%I=!yitok>@m)x*Ab}S)mto^DWV)vckv>qC~1F zB#bpdV{6EI<>l`9)sqp--QJf;li96r(8X3Bs@el8R|5FlWSK`{^@w2v#lQsE=_-mDxfcx$4wr!G`*aO|@E9@Zu4p-c zw5%?x?U$?Z!~4;P|DRUJm&W*@S2aA-xWm&-N2_@;?{fJO7w=KbId8OyLZ$m?TBxOS z_kKLmN3vYba%VvA%na(vF(;!_%>=2(K4xF04M;Ts|H~Q{hVGFZ{oWBaW&8}U#3&&y zm{~!d*MaKuq)`({6l+Q-a=xs(D4yJJjWk64h|25PdsDE zk7yuxOC*0CWKlS}au&@H*csWsvSom2QT(lAOwz8V-8L?+;wfmA0^V>}Mf5Kjl;)bo zu&n)MrAWo5eosy3byB1p050X`MXIA1`Rpl-hLWP5!H>tdV7pG+OQmx^`I`Jcw3+G6)xjf`q$@(F96C zcr|jKp*F6kP#z`0hF2xfwo4O@CM+RL)F-%X$Hlk!|jM^g(Glwl6s+PMOVytsN z4(MGx25sK{Z0-6l0X1cN5GAEvDT~TlUG0B3p64n!b?au2rKb7FR@ir zG&%GjhL22{|CVPVKE`RT4BC+)RPb1ka<^l^+TY~7(!DO}F%4+{f=|HA+wdgA!~r#v z95-@W9yaHsw#^B7DsllWgF9q7QXlgZys&32sWC`1-zmwguaL;~riK?Tl2SGz^cTt^ z8#{%zIY?HN0rmuC8-*G-1>=>T0IZgvD7p%1bB#+rLS6|-#;f_%gJFv%!x&79+(0nb zE-AP~pcu|+emJA-omexI%4%UY081mzgk?~Ubr_^k?(8K%LP8^5L+cs(#fG_!ftZ0T=nVP6xES zod*8e3PC+^3`Ygy9M%FQ@%2KIpGM`$Bxn1B>GF9lGjSMq<7@QWu4&4dH|k761I^cx zmxz~z?|Bb`_rD%ADyC61l2DYyl8^8F(S$*rR#*tsmwdve*8wv{5of@B0}K&nc|2|g zCyL6zeT9n%7K8hVB8xIwGGh*^CyxVbbLgh1p(-n~DLXTV{6e6vm*NDe&MJyh1Ah^P zkg8X*d3dnI=K1e#H#go5*Fr5#^O|mZ!8ld}HT$Af`^qN{)1*QWP@Ds!*rOgLg50N0 zpHFJz(MgGeF8ZL8jbe6j($i$|s8JgeRJ9^pP^3^wFt@OwR;l8P`#mtQWAdkYkE8tc z)WI6Yvxc(2b>c4*gG*52*tHw#V0e`9ug5&D5NbTohP|y~wMAB8ZD`@RYk|(YK5zus zDS7CTG{tuf(vll^n>|lE7xGG4HABP;2>5DLbg0^P0(T)$PAUFhBqfY3FT&X1NeiTI z%2v+PD2^wf2%ld%hyYBDJ!GoH)^bnlVGmI{VlKGBV`_@voHO?dwWja-^51n{n)|TR zVxgYOFyB!MF9pN9R#ta*HsAmTUi);1Tb?h~AHpuvG$bU4jo&m2R_Q6nLmC-|L!lS@ zbRrr_Y49A#*THq6ZfJ{yJXTqESXE3ZTtcHX@y5C>tSS9pX=k!h(49#_i6eK(A`(I< zLa28#$~ycEif;RtUKfns&x|0Do{$fkA%(~1+Nf-S*>6L|yHBaS z3=3&lp)9A?GG)zAaSC(oRz!M!mM*pj{Y7OBwUw2x<|Rqpt79k_d^?r<4IV#*ayxt*R`?8M|Wa zP1n>CK@Y!yB487~8a5=#A@@+Y!DBk#$o)!h@^M00)H zV~gwTEk!%M-E>5rZ-Of+6PwZaOIe~CBSm2Kn~EFO{|Dtj8ox&n_9=g7JqHrtCciLE zvy+~u1!q((gjM;x1AO|q$*5^63vghfdHPJGBeH3YHf*>$x$A*@Kj6^ymagt~_3z15sz;Ir8l1B*B1{KyivFB52KFZ;QY>LLD_Ijvz)Mt-B_N+p3V7SH24$ z!)sn6Pvtuf)NqH)0ZK)Femet(4|407K_orY)qNgKO=s)B`PJ`#^H+cM|M>V9zVH{1 z;Y^$`b7-$f$8v0JV#Z_re!>H2qLjqcbTa+9K+T`E&Lr=sD0TMHCN^!_G;{IAZ~Oa4 zw+;U#Q8lqy1jz&Qf5=TR9WfJ9LtE?)x7*?~?U``GWV{MUc| z?&p|G8@js|>B)%#NOu4fAN-dPsUR+;e2R|PfR#gK(aIXNFiD-9-%C@PY549%S|89^k5;v^iLxuCqSqGihYWYj#UU6zK7r~;%D zE>lQiA*H6ucWwO9?|tMWpJ;sLD_{I)0H;M{esFMb-aYp`^ao0HFH7Qu#A+ED^&&D8 zxK_;qBFJp$G>VSfz+m70yfn zQj^FT*vtTMP*DV92wIhhxd=xwwC+ZuZo%wZ#BxZv2c9XMflsfKFB8&S2%UTc`F~CT zmIR9CLsJ5k6v&u^D<9kT)c^S4```Urd-s0%2RH?3mn^GzZ$D5JTJs9XD|Pbb7(Q2( z^Y8)Wd`qugdPX@5<#I<=DV;S7b(&6b*uNp3{0IOcJ?_)c*7L^0Q)lLr-n*T%N??gd zd8&ml9QnXIkQl><|JqtlbW^d|2jhy+Ne^ac3@jC( zbOdJ+&|VG23dY(%fb!0|73Ximkq;=D;h+F1z&45~6ql*$Y~wXI-E{kBuDtTfU%mC# zTTe=G{@;te5*Zu~gW{%&Cy+vaZqTY+6)Ig~LaVbku6-h9JtQsJc3Ps8Hy{Z$Q^yf4#clh96!TMf2WJD6eK?J5f)QuzC7)Xi#p0fWWLl5`qT^9K-ibX9djy zpaHEbk~NMAx#=`bXD51kicj|Sb^Sz4a=&S)y}D#(tu1BDsbf~6HR(npitDaYPE`Cx zCiSs2&DM^L9O=v&6FRL$R4&h>VsVj2ooyLej8JtA4T(LmxHYmibz2^UKJ(A-O;y5J;6qd{OUf{=i12x8#8 zvq=JI1!q0i4g`p^04Z3B;iMZ73n*feXvNq6<(5Bt&wD=b=y$*SjeR)Db<*z)9TgBc zU)&d*M6~pbRdct`1^%VE{WH$gqd=)B6Gx_AuZJa~{C*u!6$uGCVF5&WElKi(T8|kj z2q_^tdB;+cgaJbV>kcnckQ8GmC|JsZQ>Fka!$}OS%COlil}hK9hMv6lI*u2e!+HtV zYLQ1it@0haoR@i1L?9)IR0B%QKw|=Ik0NcNs*Xb(}V8 zNxeR$vc^bMorx47M!9o<`a9;qa6js{h-xDOp$IYLCF>x59#ZVlQlH-P^H(`=Ke;}^i-3v%VKm72}2hTtM&Hwt;Q}@oy!7xfoJ>ofx+CGO~+Uz$Hps6x3cXs{t z*Z2S1zy099vYNLtN|q_11#l*UbDr+wfY3!Nr)B`ejGP+i7~!`Zh%;zqz-}6_6H=W$ zR2MrnUM#4?%a-=I6Xaiw^A6JnVGD1bTqO`rKGz%lQ@ox9Z}SCZn@lX>1=iO z(w#eZol`Tq9~}3w>WDhKmr`arAhrx=3vdD`9m83H%`$*}RWLyS>xWsK0jbzi9Z-gq zZs?fVkv;#3ClCL{>eZ`1zH{f!XA{zXUQ4?>8OO#+!Xx#?eZDnkzPII_l-jL{`1;>C znYTWzmZo*(#Ig&4e_nv;(L)m)XK5y?;anbP1V_oAkEeoICenm_Lnfp(rnyQ76NTiG zR}v_ex=@)s0Hs;?_V@h?r1T!ri(_Xy05WjMAON&dFe1=w5pjk>Aw_16qE;P&%_fD} z&A{2Q^Vh6CxNOC;ACDZ|f2VV5ccn5qJv&>o<1*%YsZ%Xkyd;UDo}N;v^yabg(X00l zA2@Gj_VB>W%%L7g5*N!$)x3fAn5{e11YUklv!5T%3#^k11))SZ>pR&9Sku&y9|b&Yt}EwQO*3P(Sd%-k(p3i&qNzDYG45D#1AgRuOFIIw3;-a2Ep$ z6WT9&o+&GV=DMV{F`K6Q_AFk~^REl$_5btu_|82~KfSl{{A2hG69C!x_;@xxe(w-~ zKkn>Y@>g%U^mQBd96WOEf&Kd~H#R-DR9d51^;2rtBb98y&O7VPLOUmLp%}pz9~Gcf zCn8m+W1~;M;fF-m7KADwGib~l6){IDifh}K zEbP5w(SlV!s++0p+qNB;2KHuXA-{6$SZiDWOaM3l;GTK&))&^US<=_hvG~G$2ZrBy z^vL*SQ)6arp|~I_7KJKV6~tu_G?Ju{-ef-Gwwj$;4?5V~QULaZ~r zW5=N@Kk%WSef!RxpZ`8i0*xLU8zlgg7uk8tp+$WX@hTCmXWMo%Nc-1kR`E`~Uf1HR zVn#DnE8>gmY(R%|{%`v<^#DTtS4bL-#z{Mtb;+5UAg<*enHU1r;+y8emmNq8+qMp;0{~)%vgl(qqM9_k)*R_U>Cp zkL5EH{m7nE8|#I>Hk`I|5O z?k7I+D^Gp#i+}St=6pAJ`Rx_ySUPJO0xz0MO&N*s|F&oj{m=LL^eW>d!`$~hC#-MN091k@u(2mv5eNrE7Vz$k*agTzkv7Ydz408=M>H6Oh8 z;Jk0#xZ@8CrA6yRO01&}I9GtN42TA-YKy-8XHf!?3WyuXvJ6BGba%%%HaaX>Ix(?$ zQO{q`pWpknty^#0gOiII!wEB7d*qSfeE<%;;~m#OuzmZ^pE-PF_aBss*^*K+kE;!z z@R2Y^61)n}<@E(TzG5>d7}*#|J27wGI*g7^#y8%0^RHZa=tF(>)Ff%g;$L_xG;Ozqg=Wlxb#j9Vlb^F0zPiqH%HYpZkBLxyj;KdgFdd$}? z=LaT3!~hgQB!Mw8NR>+uZhrK)7p+-y+sKX`$A0={ywIzq;ES{)TL@E;n&)Tke6~n5 z3}7Dod3MT1i|4O;!#(#u{98$3d53lVjTYlY>$Y{s-k&*2}XjINS@BAAUE?)G_sj;!gZ@%dZhy4w{ z;5lo7;o6QzhYtZ5S-EoXN0+?uwM+Lr{p8zcrjGs2(UJSlU9j{GYJ6HC(t|`aQt^d@ zUeSP{gy6zb6TlZniGWoM2LVYBlv>DQYZq_X^2F~gUAq1^hKGkYO@wG~o<9dxiD8ZVbwP$0msR%(+D~%bTI&_jkaRpkZAk;83 zbId{N2hU%-;=P9s-TNn7x8C$L<}MZC?zrQI{cG2D|NY?Q7yW##wrlt7?9(n%GtiVl zGkcz*Wbo2Fe$!!6L9HeO$UumY8I9R$gs$#$0F?TkdTPgi+OT26DF=argM(_@wjCcW zl?T>=iUdiTyf()I2?4#hT&UO|K12$c%^=c164#N{hpjV*cCNo*;McZn`Qg2zqeHVe zJx8n@JNDGfgAf1UCmYr+{cY!lx1_cGqIC^S76=EU4|v)>ob!ihxF{h76_rpd_0Z_i z@zskumaLew3k+0{cE>{?UY4dU>*0O6rsm=CP}eEi)mL9#y!U|zek0Ng7Z;LqDQ$Fu zRqW-0+UWsM&2{QRF`l&nnIi+x4B$p#&9Lhqu-oU)SMM0wamyDT8MS1tejEFGKPzxQFcYGn@;RUozkMreiULVMIOD_uy3JC}KZ zek+Jo0kai@LZOH1b$U~GU;I9t1X$AB+Y7Wh-M7--;e7B8mmfweARAtH$34cD3*gs#Yc6|#QvoQS!d2w8hk*m#Tqd56C{31;0Pvi2=L(48 zMbOC#6iQ1utHo5Ctw2X9;v_?)XW`5UU=ILp4~~vJAwAtahX&tv`TJ(4_x$=jKl;{f z+qP{ShJPR@GiL>W+q-vT{ozeF?3_6E*cX?rS^M+Onq5csJ?s)Y0=8pdokFo!kTI|@ z4shZt>>b-6LazY|1xw5zDuTEVpaH%0(Y@DPbIs4b6=xy9_lk5ZjcE{&ciU-IdINZ7 zT0D>R@?wTuW&#XzmZri1$oTl=VIp!w0;N1x91>bn{~A}OH&03ZNKL_t)} z9YvCOQR!TO-TUV{bnpmAS{tX=m?DhnL$S1k_v}6N#{LBh264*kHCb7y;LjyzsmAg>AlNp+OvF&^+a;)qUGv_xd&Od+4D%_u{3&Y-H1;H;t@a+yA?n z*>|{HtcYd@mMEN8p?HiOAfYc$J30rAMgyofh{VMOBeVu{K#FT+3#m!Jkmfd$J9Pea z?^NccA1CmVPr){9*bwb_V$ZeC7T=-N0y3r#;I4o)=LIm~gDDh%bD)Y4SP6(SD5^ly zBxTTeq%;A@7nc1wsdJpAk*Pm z9LNP^P1^Sx4r?Pt)~JJspmh-ux?BA;zEU2wj1C)9y81BKkd zY;hM_fr7jw6aQ4KW{t|55;m{?gn8Eth=QQMAM?LQx|L{q zP$FL40S>|%nJvOeCyebtQeFZ^8I{Q~^!5NI-3Q14C>ll9m@q4r&3oikYtH%bkAC#8 zcZ`gToQ{6-cF*JT_!U=O^${o42WO_A&I-vSGCK_z1I6(BPoN7)lj}l&a9id=;6#IU z7j&|S!1Fu*@gHya{dd3nLrX9R`_Y+XXRBlMQ;nIqFMcl5ofH;#89e4O|v~FO@|T34JdZ7&IRNu?`#2xQGkuSCMGGQ znL^s=#=L=bfRvKQw>|!Q>({S86|s35n>1IF>gf~eB#{D~736}{xzq|lK|ulJy!bo$ z{@zMEl!^}1Q+ulG*Dv_*_uhNs4xIjRY&R!za5K^9S1Z-s8Pg0%QiwP>3MdlLa-wo& z2Y9VSAc0mLfW(orv;n8S_xWRz;NORG{j!!5hvwnj`Vh1`EvIZ$D)p?}vFp&EmP*Sz zR5U;hqhVao{gnLu!!J`SfFYLPRMx>XgJM!eqq4^?T)_AA^~(RYZQDItae9M>?0et) z@}1XQ^B+D{uaDe%`0#d9?l7=+2Fwo39-(YDWe{J{4)`j3A$UF>CvZliSm>jA4Xf&@ z{QzEMx36)U5P)ph;*Y=U7a9P}Ap$*Dro}NKbau#-b0?td?zqctyH}#NVK^1U67bM( zuJ79ZP5~_k(2U4JL7pSP=gSlfBB;0nDk|Z~$N@MzfjF8$y>`T1e96Ukb@wE{eD4E4 zxEpgH1hv^lbKA{-x8?P3dfjh>Ya3^0_h+#jMTDwv@<0M;gfmb95`^B_fylR&02Hj% zu%-y>y1~%`PE&m`spyw*`a0CTEOyp9MnBDrX*MTrMyD-Vvi6Nc!gMU;m zX>B?KAoatMJB-pc94Br_t&~yTt}E}Md-TYW6k`A_5cVkuf0YJ`8ik4pLg3<4Z4?|6IOV09#J9(UXjmy@Y|NmudkGynbZo=?zQq^eBt4SB z!9n%ZQ@gKZ-9*Z~dLN@@-hewWGvVQ&gQ(nA8 ztv`{gCFJwJ`8y#6o}0_tgwBm0Li-O5|9q76tx)lNuBRFfDs&l*Ay7Dex`^k>=B@gi z1*D2X+`!b>F4x`Zw)S=Mr-p_$KZ=)TotPVM{O5<>d-dg?tyV|&r|FnL>PXWn1QyH! z4xOxmTjIM`vCkYCYhay16nA2BvVusLqlX{b`hhq2ZeK5WooHQjL~NKs%gua`KhDp} zkzZ*!M>!gOhJsDL60vPE1{~y>sCq!ua@1YqxSxrfQm^pv){Oj&$StQAG*SG}^~pD2 zFo3eK>|kMFiI5aK5k*B1fT_uGyMEpITSpG<{mn-o-Lw^_)v0WndGNmPJn%EuzWYC! z#@HjKu`4Slb&$I?v2yK+1rWqMzJz|8;MF``b@L+u4`T zUXhNa))d$z92OEEADUC!7vwufcu6zgCQt~TGjaC%l_(Z%O$1X+5V+RjCkL;?N3G2u zNBt>uC350XA|c-@&KfD`GIhV0_doyqH<$0ZXY((EN`)lpC&j&h6hpC#0%--(UT=-b z`QjoF6a!k(2eTLQ>hArh7&4 z3?l6-tA#xxNJ;?ZAjmxBz%iIh5Qo@H@e4ycHxJ>Zp*Q3v-@fU~kG=Mi^ZrL#KhlVG1DXxQWe~D(1)h+Y8J<_fW-OENHKgmI;eZ(UYEKa3Ycmg1wPOUbl z98f&???bTTQBn?PNnRxD=@0^jbq>x|LDZ0*p6>0&(zPQaPd|y5cD}lszVo+_UG=Uv zd?-!Fp31VPY+R^9B=uZ>r$Qr%%%j8NgCGZj5rQGlzs#HiM_q8-P17^#++yd_bvTRs zv9snz=E|OH1vL?g<8;l=UK<^qmnwxJ0tt~vv|81Kt^cLjWfx9Q*IygQ-IS&g#1&x-x1|#Od-*n#K_ifWII#fLFgrU2aQ)bYuU_?=+qT`f z@017R3`jb{IOUc?XC>k8%!aO7dNR@)VH(a3;d!gh5B=5}~7Tt$QP0Bx?ED&wiGTaqAOZ=z?KrM@WPb`@V<}7&HTZ2RI{emH|gdj74l3h^PT^ z)1$+~JD3}?w}g%Uw=)K#w+ zqq(>qqTHDbTWJn;g}{qX!mAl1!^t3s%OI0K0d8ma)CZTdpK zQkkGg!Da?&-N0FeSeH?VJCgD7vFql|oA-jLH(DnGK?A}$#Tf|OR?BkCG{1&GjRIN_ z;F)GAno?XIS)PW&V>wp>PT}n4Iu^-tIPcGU*u^u=Y12QXZj33Ze#z zMJ<`BjGwn=`5*1rv1tS^&wO>a-15!6=bn4cpHEE<*9oH%YYRmR!U|qGNdU_7yl8XS zg>qF81t3D9xB!CA;uAyr{^P1u8#-TxdpRxkigYZEaiKrQ@s_7M@|IoC>O8i~Qs8Wr z8v=kKJ1{VCn;>=yL;>FHb?InQ_2&-iR}yLkPHqECXesf^XW)+bpj<2bwHf& z)Zq_t?|g|PxV%e@D8#WsmQA8o8%3JVx^q`8y!EN49@&VO9;rOh+}POD-(I(7@jp6u z*k;)Xm~(W^f3!*iT!`KS3daScX$i&BGO+3_j~<==MLcu2{;4)JG^Bfb=Ur_KRIyxw zfk8$oL_3f+P|AT-=<%%q)&$Z3*ulmYMyF_)89Dd7uWm)Y<&W%~E zYF=`v*)TK6B!yumhOf*7z>1-@%2CD0nD~?t%N7Dd$BoEw>AsdHa{PWG0+GIxKCm}z zST}FS&OM)w;(^H6B7}+%L|~3i2vR)lLh?W*I8V$OlBj{o)QDTPdg1r>J-+qJcp2yo z!Q{?6zcX>khPD5cWfK{370y&WYUvEbhDvZ}Lt`ZYL!b%Bdz5#UyJ3t)mNj_ugPR5i z1_oZR5{1^o0z#viddagh1T>wXXUzDvN5?08e}-7ve4K=4o62W9@YFzjY_d`90si56^))1#<e1e=v97uf*_%Wq2HJ_L z&tr{7vUK0x!|x(4u}}e-jUf_)p#5KF_{9kY-yoI(64LxvK*K!|+rXUU~q2#VRGrqtTk z6gza33Duc{A~Jh;(frQ8+;wEz+~|6qyt!4YmfYFMCZ>`^MC*X2TN6_F*2@iv1)PO7 z8Pco)krdXY$V?X6eG1iTl|{v3*8pDN8u2M(t^>XmtS^ji(|aUpoe6Ecd@r1ytBc4P zDjM@F=m>cyv&m1+dCAY=Jd2zHfRX@cYPYuefnj`)<0Ch(xb3`kKA~DMAQns(Obrx@0-M$C zx#zCD>%f7LNAWD%<;!Gp&z4({E?ctT-)fbkqPPl114`*2HXj@l$$7tFi~)#?QLkx4 zQ9lU^OO)cb;zg|8o}PKj#wU)hBs3_ijX}!{kRF4r9D-E$LaMtVwSAE4ZkX8}NT-L8 z&O8BEeG+EsNtoHEgydk&$k?rT`B1Iggvi#2#3B@NFf2bR5AE6+1gSUJUgHOsM(vBsDci{gldiJ^;vfqiT1Envb0X zXP!^cwu$7aeR-~F-#&r!>{RYpo2uEBO_Z*p^fBeF}q;V{BJ=j4bwP`L^t4^@f$1?BxoIaayxqtDd(@1Ag&X{ zo$=&UwW?hKEN^EQx~r{&osBvn}n_U`D)jX z|7o)d-tYG(f6gT_azcn7%hn`4QK3nW193veiv)3$p;kF0T2*$>OpbpYXOI&8(9lr! zx=S{E*}0jjODCj23`GQRK-neGblQ2?@Yw?qfe?XI0@7VnuhDs3UGrDsEbz4m3v{~elWZJ7Y&+QC@@;3#x*$Wz|=VcCpvTaYdj z#F2<(F9hkL$;s}%%v4^lqAqqQ15yfUaOM4i(BciDS2QRj+Nl9+rXaR~)vH!Kxp(i5 zGqMNkzI_KDU9`COp~FW;-)yoz0y>b-H==yA@zujb1P23F3E&Dy(*#k}Lr*^W^hKAx z@imJ#ZQ3~fLf-S{yKX*s{@U|Cz5JXt7g1E~ICykg;y9KpOLbCM7!emJYbaZvbtaBG z(wWIxMp0sGX@fdTmdVT>J#uiz3}6{`TeD&a z$e(Si4e$xhkvg+_g(Gd_SmK#qKA-tJV~E73a9`;&-nadsu!KmponAl$5`hy3#TJdm zC`{v6cIg||e`(XEAI?b+)HyU6o0_>_ko~wed+1#~1LtYUG%WjqGJoUBYdS*!p+KJi z0D?FT=rYK40^HeAt7X>#*oNl7{rj|6q+_X+f5>@?$Vppd3gFrHqbAP=K&!IzEY=$W zsO~&u=Cuu5QM=^D3eDmZj}&=Mpr^P==&PP{-Rf4J*Rj0*`s?|I8}7T3^@4s-5n>a7 z5)dMIR|{yluX%9-Ly?2HDrRSnfY_N*NBpgs-A}%3k~lxjMn{LL=bZEEKLIM=7h6e) zjn_QOyE;)`ed@=JMc|zC4mB46DFTR!QKT;d@Z<}5&!M5A48Y@CwrqObzj8eRUw^J& zf3Cw=e#*~YDx=FUzv`Sj?|kTT9j#$VkLMMN=lV90CPGs`4sDK|2*66iS%GtNfmlil z**Ol>$sNq&AjIR4uord0A8%xB`SnUuF*OQKB0v;CI^1AzhA51Q&p84hkaHk5FzJkd zF=Nu&chKs&cShKK_dPas?bUDj>Mb|lvu5yZS0?AJIcIEgdgj2f@$sjRjgCE1t5tSX zt5b)aQ&ZNuRH?`+)o_4=F-}EMzbO`7cKGn&7mR9kc6JJ*T4QDyc;!cz^CuAk7D$uM z;DNkc@VgaYi^z4ziRtO_+wd|fQaqP!*>Y3uoO53LCwupfy(z7X^ii}D06-K0WYJPo@r9?7 z12IA%jzgJ15lkh${qd)NaoxHN|KAf&Y#Dhu@99+S73o+KK?uqxY@b)C*m)RR+BOmL zsFFML=J=diq)XjIGa9*6MS%0gi(7_8{maoszs{N(AQ=SWiRIBr>UpIGNSDcq?9hC+NA%En2jA*PcCN zv2tiA)d8~IG(BB=Er1*F!tV0~r|^ckzpK-4 z^uZ7Q^8EXL^61By7Y=lHt)xcQ31bOh0SmPV7$guFzd8ls@CxNLqyP|$oO1C_PRxK8 z9QV>fUiP+?miI?!GiTrKPoC8KuV24D8hT>#H=}sDBJM<%X+UX6b1(8l6e2*J0Z9yi zU{Qf$3uo&PsiCj0Yd=x;)G6HlX|kQ^_iyU{+Y2sObj#$#js^epwQue2>eACABO_j^ z|JmpJv%Nj3g_>ufjG)E9~YPpNyFiMp-`V|0RZ84&LvL54=QZZ)a! z@Byr0j}U5nEP|>B$W_5l6y`=qadp~o21nYBM*M)^!4<0&n_K3>G4NQ($ zyAT$@(~87qq^L-!Pf$3J3argB9%ndl;^d~CyLR1aTeYzvBF?Ik^%~p$8##-8hh*E|6gr8TY#Y0e9 zZ`jwiXI#33KD6~#&Nfm}W*}Hd>hlc54LVUP5WZIzPeI^9CAHtaecLx4f9#>_Egf|^ zQ{d6^si$xdz(D|5zBXNltG%w0fRG)i1W;F^&_JSDq^yai01zOfueD>m!IlNF{0R-q zSR6Q!&YyTwlhOoWwbwL8nv9JjG>^Q}=}Ocpz#0ifJEODiz3+Y71K)h)PZn2oF0Z4V zKmX)Xt3}T`S$sFk07R{7PS%-?iGT+Y^E+t=%0yX}dir#}obCOLGfA<$Bl_(NJ?>kcFUpd#U4&}391XuqGmrUSIF zaLX1|)AOESOb%5BSI4fa2ZaDly_hjZZ@Pu0!37mRDz|K(|H4yGefJXT)qmaD*S_}o z#oOMp^UoM%!B_-z5J=vsP=`a$Zm5k?sT3U-1Lhu>dvxN&ncH{n+jLYiuU#1zDWNA=4d|0^ZV|KeUo7hgzsP;+GfyQ1wq1Zv=hjRmDa!?4E7$OQO z$ABLdu2}jEaOj5SajlgtEG%edtcbc;ts9mOjq`5OCKUjvDMh!=fCeHLJ-3v$4_{M* z%z((Qcu%_K>bhlBn_*Fl^}fs|_t_W-5k)jF=<15Q2*~0~gYl_9#X1h64;(m9zW2TF z_^$U$3M@l0Le;pTYRyzr#!|I7v5C$nLq$Pkd2xC0F5KY0+0D8!y{a`j`~tv4MQ2Dq zCer__trG<0n^(tj^Jbf!Iz9Ly!?R)aiP8`+TZkAS2^+h_Gzv(;3>a3p(o>ch=lyGP zxro_rC(d%B9A2ow*VI_ZqLT|f0YtI#P8qXvn`P77ts73CzOZx0&UY2Q9NBTP2LN(!p>j zvm34VFLl|q3fpeASXF6Zp{FO$EKhIQK0AHz`DeWw4KcN`jj~LY?jf~{)N36f^{)zu zBaDZHjT^S(%-N$0OG`@`fOS&QdqwuL`Lb`{KK|bKzGwUWerGtOt}!!RV`SUuvr9kv z^t0c*S2cT2t3A`3-gqa&_h>xI0O|mg2M!Yw2d+jACyywhfJ~D^K#A+M+PSOs;nLbc zb!M?Tk47b^5|~VUxyLo`9k(#IdP;35vf6B+VFssmR zt+RD0NC~5S_2YzUq_P6+FnLeo$%RGVdsyo*TskWoH})TY;)!$Xt!QVZ$!N6ru$M() zf04;<2z3Tz8Wr|o4N!?GDI%!)m;q#jE^@%RGcVon*{Cq6K(_#fs=^&jBCqvg8mj7J^hHuF9z+6A300?Cwrsj^j=pMGto9Qj*;;@u$oc4NkyjcN4_wen7DCxbGG=CpJn^uIRD5b;p);H(|A8$~bg zqg7~S8lOOF+gGqzt!z@Gk|zY3Z*W%VbLE)-q`tgAWfxEyA=kOgNWFdr16Yoo%Ng z5{Q*V^?5c-!Kl>3RurJE`M^-TEd5^pOfP#avj>|3z8v`ZxtTAVKlknHt)KgvHa*>Y z9y%Tv8cc&d)Q2LliNK@jV*qiS5kv_J17b@{%Y$vW!7GE;*GzbWj6(D4Ykl`C{%|vy zwXGRId!QioqZ(j)skF$_WD(T6x&j>Ge^RK70f_c`y~pv|@(FzO=;`+;=D6x1+gON_ zRl0Eq^dAueXb&fj53za657U{`XWPc|Hj#piwP0i5WA_fjfk-XtAgU1Wk=r0p?tSQ7 z$qhbUbBsvE+k-Y`F<_9ma|8s%0dX22u>jjbl_;|rQaV&sph6%r7>_MVmqAl^il8+V z!2*rkCI12+hT6z#M?W){&c6?o3*2n5W$cLP>wwy1U3v217j?hElM|rh@jK$ z!q^^`hM^N5=?X->(!oRZwL+f=pw&x-Q8;RC+Sovc_fe^Tz$hkClI&29LIP?_T=qS5I_o%fMIT zBsl<5SMd_UvojQWutztv>IZ+BqU!5uL`DEs0~6iH8bAq!G+GbT^q%m>wLOnl{i{?C zdPT8PfQ+Ij7C~@Bk->Lyeb=}vvO|aVU$89unalSp`Ko*`bcKui1ydYG2_23d!#EU* zi5Lt9!;SOv^XBZ?>)j0N+U-s0SQ?XMuQl8&qb4dBtlF<`;+Bqi-ro!qmS9P=;q)YD zGIqh?W@S7{NM&e{0umVmB%Ta-m+~=+#+N8l;?f8QVEp7 zqYdX7=g%96D2z3L2n;iFYvI*_4Z(Ru=@iEHpe0Zg9w>$ZEat>#RG{fxCP#om8`3a* zlB0grPTgfK>EKS@9=tr6K^l0oDGa$bop~-wcTcO6lOhMan_R{y1$uSDg%DDp%nb2k z_;OL(oz`Q(qt{#LeqEUWnHL}EsR;(I0)l2xQaeCgWfQL3X}_v5Q;cI0w=bM)WUbc$ zSLGKs5m-%wh}Iwodrh09SrMQf&_aRnFw6SKux{^iZHEpW8gJkF_RpNTw0sYYvnlHY zM0Az_h_AG5wXt^+7zzd(i?STG%d)(oeX|?-?DifyPHg+xFk zf+9LHF>Q^w&wB!e=H_9oB!xmCkwYpbi~Z;wL?Ys(95ywi^G9?x6^03&P7lKY>~OjK zCjcJA>&Uab|Ni^=bD#U1F|)JNe&81_on4$cdi41AM;|@-L4ZF9*d3SfgBjwy#k61y z_mE{%@ZQ2Z5Sc!}22KDVLm?K+Lj=cK3F1s#d`;l-l7OwiyAsR8ad7T!R0rS;KpvfZ z1GWaf07$u>%gMP)3IZb0V^4jaL%%f|t=h4Sab@cB-taNBc0@EG>}h~$LcpNLn5#O6 z-Me@5{{3gR7h?|?0-2ZreGF81)7jVl$Mb6W|J>pmdYZ11}X;Gg7I6&;4dyu4yj;=UQGV5v$| zMfaX<>|GwBK$AAssEL-klBjB|4%R5So;8k0X<}#uk<@T~#)NBP0OFtu6oo>yr01S{;(fQ>_Vx{j4?n%uV!CwS zefQ-eosGZyyPtmR#igZpia&l2gx|$kdyC}_(`+_$iL4EYz>KWzKqLdB1Me+Ff@7a& zGY}ugtsPqPf`LDd8riBd<`wWR2?pG z5l~LvAA@MTY6OIK=SzS_Q0h*; zpyJ6Gb~09BpxP@d6+m@HBLdq;5yM( z3@D5#0PC^VLL_$t7NQKk8v9|fsxvS(Tv@MMlQtf&E(oKzqAC(~qY;52>K|7dFa=#& z0=yIei5F?#D2^qHLSKUT5fNITP$Culh`I+DfNBE?QfLBEfx-Z6F&e9xofcYLZ|wQ2 zus5Y+$xQ7gk)%#-ho9>zxislGR3A6f;Yy7c++4P6*M0WDfy3`Mb`w>bR7GG%MBw5* zh`qXlreq?&JBS}bqy!s2fBN+BHI?X!VkrYUHjoY&sg2}>HefUbLctYc+Py-9G)nbE zkqn7xtBKqwph8eDF+DN)`SDT#rXc$6YaLCXMk0IZc`NqhJ1ZMEB5w*1w%zV-8$E{^VnDA?q|UNMq-hN}mNn*+#!#x_j|DnXkF9_j z#cPgZ$+@cwOb-o*24q^s1TJ9WAXok^P_=Qa3QiJ-i-58i0}wC9-MCQ5>tvmFZ!{j( zJh%|=CDLSKQBP8H00J{3$cZa-owanFo}P6I;{X%a?{s?cswOzesE08S=Sf=gRv(1D z+;sALYVd;z5HC>g08+KKxM4bxh!kT2#-tu~)qOBc*^@?QA$*O0BY@eUbT__V@n&=^ z$+F2baZErH+qqUeyXL}Dn;<$GqTrj8V@WeJWt)iKZa5E}O)A|jphoXfx-AsyFf^og z)q}}FQNpk)l#h2hrnqq7)em~{;w5FJ9|Y&d-m+HZ+qJhZT!-gzWVcL&OZC=#I3EF-I%FP zk%}de9O4a_+hEFJOa>;4QcZBw6%YwLAw((zM`W8wLnU{dSLykJ086P`P7rezUqDsh zB@|o%3^oel0=X49H-N$ghtmUOLrcfttgR%cG^VF=gMLX^1GypKvY)XcI0&tH5FE0G1P$BM2DtE!^0^}u2_{~1Ax#e%c&8)VK(YF$_AtH2-H=P z6iLUq3QRbXO%o_AC3IXtYaz?CrZ#rQ?12hB}WW5crMx} z6)zgc8iB|Gs_^PT>R_ygcgZhcDu?6MI+ph5e(slUdHU(6Km7lE=CgknI(1Lp+SUVg z!PF)~KX6!;qiB7oTvQbER$b4~#X;<%~MR0T-Mdw*3Y;(f^gnb0Cqk%E^Lz$kcP3^%_QbzmrzWi8O! zo67d>+lN2=qZbBI@4n%jH`klS&k-$742WQ9fm}c4T*vqOQ{F}DR#;mbL3GNVh$g~( z7@ACV1}N--*L+>MDk~B)-we3wU_GV6Ygwmg0mW01)wf@LqpaIyxo}B6lY(Q>FExWc zO=<$Qx*bL2Tcpay-7wetO7^C7ELD-!EVmRbTn6#bxamogXLorSoSVjW@3zO!p85r2 z+C9-Az)Y1)3Mzy$SS(Cwpftpx71wZi8NgUtZSw^1=oKI&tf4P2*Y}qQtTlzPS_qry zVF~~U0eO_TaYwQ7D#Mcy*NUsDaqaZ+qictwTHpwEEUhkFr6h+h-GSOoVI)Ta4L!;< z%DC3DPRBs36mF*bB_`f{Aa@7nRbf1XVPHI<-UZ&E3Vc!tzj~v+d-t}8dO!Y0fAnX6 zm#Fh2S+;F1Z*5}rQ{;UPAcs>xd~{ye0>o9pk`HZ|Kx6?Ed9Bf@R&nw2Q#(Oz+%rP- zNI@CF`Pc@UJ%AE61H_MEGXcn9Fua8Mg+9)md(O{Hb-(swKYsV0eCaP9{AU|yW~Yj> zWlBjXNMkvp$#0U=m+}+ZUiIQlAR-Z2!B=adq+0zg-Gi#FBb2Uo!}PvwKuXLOE{-os z_d9M?0Y&`_|QPH_~_= zjYbL)p#Xiq0zbd$U;}mifP}FoH_r9GlD#P%OPjH2@+iX+5~Ke}a84RiKt#3@sh%Do`b$ zKP!|00bF)+64zZZ0?OxL(%U8IMW#qtp<$g$?+wRm{7@s42`S{5uH+>3lT^v7BA`$l zB3uUUz(GVq8%)=w4Jc4f=-o=SbpVn0-Km!pc;_$O+x^n2q>_45A_9z9aJ5dJxB!55>ThaV?dYO*;A*N?|R!U-&wwR z{&&9imCqd`b-%T|c#c*30Oc|D-smz~JyV@<+*1}}^|qB0YFMclqz$pMCcihGN@J{D zrK!45Nk<&VYSgKk;0f0fC*eKOjg3yPiV`g6SAX=I+xpX;(O_6Y;lo}KiwCRcT5)qx zhX(1N0-*Jmz48}Fc~J~=&K#nLw&E3BqmqO=6Ia<XCrs~Hg~m;&{uL9o1hHl=A&ty>xdYHw(JQ#zIan~*q)=$QJrU?dbNrL2OmAfX;0 z$uR&{8_BIt^WNM6YHcNILp29rk>p^sO8uk_Gby486b^x50>*d{TtHMMIB637E@o;{Sx{{cnw(}$|JBDs+>nDe z6hYGZ6T-Wan0wk_5*`&6YK=FIPzxz5B?m0>5D_)bs3~I=jwxC+92O7p=i%fM#5q7o zRrG2*QqU(p@u`K+e)em>G8~v+&$1nJ;2H8V1)z)48K^g4vS1DJKk5~1Lm(j{fp|)h zGZ;Bg0)VQDdk>C1a)}(2JPbS#+ixlgB!|pLf18=$+*lPVUYt`c28Q&)_S@zTY@FNl ze=c4)`|#4zrP0w7M@L(?PIXV5ID@hr5$6i>>A^KnV$$l|(kyKv1w3AD=N-oikZ5z$ zT!FqI9hWBdbebH9S8S?2dOI=QZl!jpn`jUk*II38jHtF?$ZO~7kA8DoyVEL|OAX_) zp%jApn?!}F#y%2#h1X>UYotXNkUU71rR$=JrL`I|N_C-04A5WO8M*??TTooCcC-26 zDUU!%#h4Y+PfVRo`-VYtG`7(ZGND*8UQ2T*HB;lYY*i9U4{x}J<_YV{-jt5zRKIVS zOz5AA6g9=LZ6d0R4?aaz2?XfXp(tXk1X_1J)c8(U>|A zhZc@O6uktI^E0y>zW9R|pT2Zuhu#ll{S>Y<|9hO&D0$y3na6eR7zls2#Mp;)9tb?9AT85OG0AfKyBTtL$?WyN@t z>Juib#?$XTh{s9-7#adlKh^IHUhB>_Ec?ly`1t$(7AQ~hwFc1qPWE~PKms@`tn4C_*mn<`^kzmkcf$#FEHD9|{kt4)Qgb_f{47!E~s zstAQ6ruiZ?wlP#*0*7D$WLTIxnB3Xl?538bB!n)n0IIbuOzqkMs*67B=UE}h>&#p^ z?f@W4EzV22h4DMB-v}8EKsq#S;KF0%IIh3iUExo|)Rf4_G)8N<^Ys3Gq~b0iuf#w*&&<4D-lZ9)h4{p*C-24<0^o z_{A%Hy!6RWe)654`ON44mn^^K9X7iycW#D=XCP{VyoWlFQs7K-c($!p2c;|E5mNdl zc41QHfJ9OOXbE79EMs^X!?_`d143Drr2?g@j=k!*V3vZ&4;JP&9$#44{JgRJ*tjg7 zHwLGPiu1$a*>g`k^%qM3#;764i6M9H+&Kk^ybm%(=?;-7f|C_>;}j9yHlZL$lcWiW zF?Mxx*B1s*7%)nzQ;Gye5{n?OU=t>XN9>@a85ABMxoWN>(MXupTN0E&5WKFEgms6g z#GMNn7X&0+0YVO-O`Z1C?5eK)CNySSs#hfnTCpU7-k=Hjt`<3fgg`K1df>HWphzWBG8nC*{X!N7J&4myjQz>UA~{=uViV?V zD5x~>VqonQppv|olwe>a^UU$3y{`={RpZ^M{$~WgquxaoqL&%W$9Z`O!x$lSQE>J=EW^yb^?A^uB_|M zzJ2?$&wu{wziRCKyRF?~T-gKL4G^^uA5(jkpA#GNRtxeH56c#A@4IuAa8N4Z}G7||(BNY%# z)rt>@ISGlfIN??6jv7T&W(dW}jpSP0F1H>5O+@|mx`bFL)F@)Cz=6oAta3C8fPyYA zkY_|@8aK#z?e%6ebAFr=XO(7U;^ijkyouW!i~bl4VuLKpF)l^g?d}aN3HGLQECJx$ zxidkQr)+m8mT8j#OY-AbuIds<-dF?TP1~fBrnKa53ohHhJLaX~1t6^`z>dY@;|iP&xM+ zk<%FUu`y5(@>Y&=Tr58K^mo5>{``Ss=g+_VxM$A1+8Mlri?6?I^lH*d6W8-vx}Z(l zy?b~5=%f4p*2PQMw`I#a_`<~wz_ai^4;--xazF|m3{(`Z3`;&)4>AtKLoh8v+!9=| ztg0l6a>=ruusKd_-Lm;WubV%(G`#q%bK@mzFD@NAbl?(jVvNZmu4~>Tyq{*!m^te5 z5hGF#B3c)v%M&J3kqgpT1?ymy9Hwa%8Uc|%6~y{AZPv-EqMA?yHc^D8h(XDf+rmQE zojH4PSh~Ti(gai ziU@3^;T*QL{BcJnClCQ>v>g${feKl2F{#T)Rd5|;o7@O^Fj$+X-GHu?|wS23L3>a_iXSAZ-&-yR&)o)(wXa?SDOP(OTGbSu8eh-h9VjKJ@rM&$3(lOM@0E zW}wu8C`b9d)S(Cf1Y{Tz)69S%YXdeH42KtB^&-ff*YWV;h}a&PnQ1+I#~p8Zq%4;n zSz0=O=)i%`UqV13FPFw%6WajbB~99w*x%Q&b6S(A*sJRQGCWG&^|aU_)G#K z0U3ds0lYs2az{`uANJ+=?7`jd-1$%5{;nOr*z0wE{gFrh_+LHt*n{6WaNv7q5V(=e zDx@{HOXiY**fVEgVXm|qYwqpD97g(6>iDzzT~ni$h*pStBfmBAg(DGzs`JpOtUWc= zzp4{CaNvMzweoLv+N6Xryep7esv@GM&1+K$RrnMu^u1TKT75oo^7Qt(xw+PAuK#8; z=Ve*M3Dg7X>vVX-f>Q}pDiNtT3ju9{Utf+Tv+05?6WPW(9{x6|rMRND0L{s}`pH7k z1Fw!jQ~?JQ$4v7G0I>n61>zPzw(Xjv001BWNklC@w#&Ub&Q-@hd<#yKfk0A;Ae$D^fUl0aII71H8C@Boc47@Y^S z^m7X{|Ng*%=Z;_Hd1$vwhlxs0C_yMg#kPjRjOIdEeMbc1h%i$NneE#rpL*u!ckI}4 z!;rmSZ@c^Md%I6P_1rJpY@=zn=E2;BC`Uzv5Htxiq8O&)0CC0;D#5u!J72`;;?pX{ zso{=W=D+&xAAaZ0?0@RZ|JVMZLDD= zQT~!KET)z3~@X)bqV9!fE1Jk z-uJq4ES)Q-pvngB|)6(eD-E<{zSszxV*QfT>#D!_}VZ*;)Yxdhc73)MC) z4@bM+^Pcytdx@-FyLR=Utq)k!C-FgYb|r{C*)SolS0aFVfCc4v8MofL?HF)iU5%xy zwKpZQGdDjk1QYiKt&s*wHOMJ7UT;R>m2p7C&*2-WBfu-!CqD6sji3A6H-C$GVgAx3 zB2XWM7DP?OGitV;3O6G<+=vs0JgjvPKU4r0<`(*Yc>L%UR)aUJn`cMG4;|AuBXR_F z0L8r}qQp88HQ+@9qrZzQtiPx1Mv(~ z>1U=CnTaHN1rQLM%qm*hGL|kLRN&%;J@?%6|DHN^;&;CHtiLsKZ$QMZW%(^m43^KiR%;4N3U4pRYXRr-jn)B zZ_v2Tg=Zwx7)u%o!-zO)`x+>i27}Rh+E0nu-T@c}*+?G>*QDxKtfTcTxlH)JI?Hnc z0wYQ$s3|d{KpbdI5!DYz4d*A`!sc`2TyO5eh2@<99$&i?TXQ3k{D%RV5zUEh8%zxE zC%bPo<(N~m4UmAy!?1_bLW%WRtjl?M<+h$C!W(A8!AO}ILEKtbEXK{LmA22sJ`{C4^ZfCTm8JbyuRmv(ms>Dd526`VqWls!xsIVwp%VJmz(-;dhz{Z0K>GdO zbEl6Vdm2~oc&Hj2hq`5Bw39v?RTqo~0u{;L!LPNU%S)>d(H>Cbc7AH%w&#IouznYz zH^g@C+}S$(;*o!3xR+Toh2>#XpCLxw2{Kf?hN2yymL~BaE>I2+s}zfaU3c#Oy%$bA z_dlOMcHjiA?|mhpe+y8yRH8UN5cygXMD`o>2?zjF^DjjNP$KD;YmjYVl17nVnj;ey zWr)D_dJb%B8i# zMdt<=4V`)hftPnh>k#{*R1vL;5fgoZl2e(^uI?B^DhRFagFYx{W<8;5JUu=2BSaU8 zISaxVai|6zw7MBZ;*C;+pFAmFxr$!l;r#&Dl&W+;HF7^IL>QRxyp%GuT61u&-LZM^ zV*tL7z~5eXwr9^C`{<+3{Zy9Uo%~hiJ&x-b zxUYr1DILqu;y1|UaEkO3cP3h;2S)(J#?7JLxCkCmIp#(++O+Ad?|tYikKCWNwrvpU z6Pamv362F~WpGHf`6yOi_XQV7Kq|$-<`*$8FF>UzZhy<|pLz1}&z`@wQ!398#)sOO zn;VrQ5_CYw5hCuuO$q4(j%b2|Wdnu9uqe=OO`#m-W;7Um0KgaVCU-11YmFO$iJp2TSKM7#x>dZ{7B!TwMI!^Cu1*!}Y(Xlx6r2 zp&jeRLZWH`K=7#}R72teY{23nBw$2s%dnBIo;yZRB8Ipi7!p+CLmIW#3j~1BUp=fA z6>C7TboIv^kC#pw!UgE14dN_JJ`HFVirU}-H4XSl0S-qFa*W~cqK3Zt_3ym@{qO(b zZQuIV=MLcdKhvi1aOn4#Z2tDFwbcsj7Gt|0>JV`YOc?>JWDLA01YQkW@vZ>rMQ!C5 z{iO>pj8%$L^Yin6dG_qt#Va|eGbo5z2ml)&c(FdNg9cC^Q3TOID*!{(gT;cl1Hh6o zc^@l*zw2ao-+g!c+mAf{uKv`!sB~>8ER>`6kOHK>+W!O$f&`#NHEO?d;9x7z?XsS~ z#uZ&-#ZdUXC|~Cp%2ETsRh4++9r{4eAp?gVY_{1ReEyjqd)K?(^}j#)+HLsg{1h)B+g{U`D*6&?ouMoop&M?Uh?Tfh16cYcRx=C(ZBz~ixj;VuX{ z7zSJk#{)zfQztg!LxFruMBuChn=w>}(rVL@lNXQuTU=xHo_TC>dF$p4pDxSg(wEC1 z^)M!-50hL=>N%bGYy{^4aRfpe*7oSB{m*=G&(=NdHM&=?m(l(A-~YPKzp@-J0H$A- z0#O6zEIL07(#UfH8r9N@r2ojQ@NOAIrOR{qy%$bAdK~LuvD>-K&)PdAU(77*Qp;V`iiA|GEb-Rb1`JiM(pLApr2tE}nb) z?mNHj{YVrAR7(I7tNIgvGXz~BYDR?u1u2s@m^(x!TiCd;@NT?pfxEu8Yu7IO^GkZBo%-vhF7fZ@k*u>D@R(p)B4j)iziew)jc?AA8^rDK@JM&aJyQ_8krmd^FNw1^PefQnhy7SJv-}Z0* z%^!Vy&z`;Wz~v&C$@et=UGZ@!%X^^A#>PUBD&?J0vOrDq08%=YSzxU|WPtv3w^SW` z0qaESV|I4S!s6m+N0v`BaUPL64QaKi>a|(@-K^SPL8(}U5wUV=q5xn*T)w$s$8;)d z&9SV@d#*kUeE;IxCnv z<7a<)*REY}R6*Aj+s>UkTSs0z@sBocx@BfCWGHe78C1)O;1SZ$Gd>9SFXUO=lo!gzd6Y%WKL z{AaN`2Sn#?>7T|6Cl9c7b1DTy9iSdT8cJhFu*AFMSmP*h;c8)2SXi@R^WnosJ_O)J ztg{H`Wf|qOvol5uck;ri3r7#0I(P=Jf+9M#J-`?xh7-V;ENE9MR~%K2x}ZrUU7%3$ z07|%W3A3A~$1l9_&1()8*4St`bUTTAGhQ-ct|dIGn@~BEk-9i~@gK-1H6JDJNJrR$Q?6r5)e3_J)_6<=vY zswHZFGaw~Ow~TJrVmxrAZP5d`3O*inS*TEmdKdzPLYElCh7g>BlLi7TB0*Jz4OA@c z|NgV@dCzS5kriml%DgYNi$b_X#qJyCvqJ0>!1eE0b zA`=%-`2j)Q;``qB-v9OBse}Iluj0rXHf?}e1Tc99k#L^^6EUWrG>H<&j^s*UCWwro zTB6gP9Rj$n8)dRRd-mAJpFH}Lo$i+2aGZlEk5!k{;1vRBqIW=axYDPr4PpsQRdoiT zm@gs`jjSnBIx!XPG@AXa<_zw*V`1^g;o}c34wiPR^jM1lxYdg5S$w9Aj7wDvp+0hK z`(QIoitfz$a~J;ety{M~a^l2^!75H^O>FPpz2=ceU--3l@7A7DAB+wl2B9OnMGyi4 zOzb2BRVcZ@Un2*F$6$FG>SbifyojrCK6>ojNL3Uipbnfm8UbLX>VBpm0Z1`L2->pF zES8qeY|8W2Z2&G_&9T?mNR{@GnY|-#ZK8|A3`z`UR1S`)0&obDa`9anfyKsnS(8i1 z>flM6$4UWNGbJ3yFC}V+5`;>WK0~{+gO``i%eJTl_(-?= z&W@9NMbc@h$aaNZ~@gZ*bD$Z3-8aNC@=Yq8#_?Z&Fv(c5;PaX!Y|?h@)2dT=yW!N%q9YF-1YsZkNoQ1y?fUa z*OCvvc;-Wjes_70(YTlf+a1j2wsvmcI%WB-JxjOua8pA2C!7n0ruaw`S)y{~Z9=O?>{7pZw3ZKl9v) ze`qxO<{ShkAZ|&Z!_zxjEsbvtpk8H!ny zZi)&wMc!{9=QfZpXQ&)+AXiS4p&ku~N{Wn(ZPR#MV&~4=A3l8e!1J$pK!Jq@*9saC z`N>^FAPBmBsv33X0x!sw4q(P`JX#mQ(?>u0(GB1I*7yD~o0(1R&SnDjpvVB_s?su; zHonGxjVYZdm?G^}d6=p~$uA7q(bO}1a!HVgXetpDA@%2V<#V|D!{2%5U5|(_PFfyo zW=cTD0C|||x+P{}Ry;mJK(6gH^3EnA^yc^Pf94vk7&FD+`Sz3d-E+^6Y+KWtbA`5V-@eWlzx2?r zLFV@K`aAg2qQZHNTI8`cZ^dbBPKt`0TLmFB>FBLRG!|mji7l335NHNTouzTi_!5Tq z1ZW>B3&gx__B-Es_BTKJ(Vur?;rf4S~tKwN+)_J5AU$4y`d+aO6ZoPH$ zr^PS3%(yT|v8oNg|C6d|3$84Zrcmu5jc@!@fGuE3FqN=0Ms6?%YaR7BpFasFMbNBx~e=ukz-Wmu=y>< z@YdU(+kg0f`oIT1{0`u{A(tdl+_N~)-)Xhx`mSg}U4|A)(F-=(&HMw>Hi$F9Wtc3r`lX7HSkqfwtns)I~cL?=o&^sS#_S zc975QYgOXEscQ2BEcL{FG}x{-HyM#&3P=!vEpi z;z^N9N*IE)fH6Lz)A5Sc1gZ#?@qQSH3Oq7+*@&$F7NTsn{m_G7`PGfncYJ8~?%i+r zv;L`H`lZ>2zjf%po8EBO&a&(f$1JBtF>N2o859VD0U1^oP#;#btbv7xA*Xl0=N;dD zZvS(S;OYZi*|@RuS(W9&mjkF202FE{g5D+|q!i{02}dOCTrQ{Pu6$ zd+#Ua@y0nl{KOLn-|>T^!~eTGwR3({v|zF+KrM(OFc9Z@;8TbBA`=K>m%skVGk2Ukedaf__AS$+u_crD;ffZFbfKQ0-dE5J3rEmKK_-ARnOQ_e zry%YnZDqx&R@ePYtP?K#TG*S?u{74y+=u9jU`6B>e*3%M_wWAQmp}FOuRs2;L}vd*p6}e+ZokbCwvqT5s6@VvIuLsh$FG#Q zlz{`1G{k@ukpn;p(NiegQTRgKbZ^_2rl#7z3+#VgIw3sx;O}32_q*@<1J0Jm&?QK@ zj1~zDrNum)E5`sKv{>`8PJ`im2VUoqw{|gT(>or1_{q=g+4F;Y@Ww~Dyl2nPzUA5H zpZ}-&|C9ITVVYIdx%j)*-shZesJZ6>x}h7oX=WN36tEB!L6k(rOeQzi+?ZcX%ZNqplP6?nYx?qriZGouDQPHoW0k&f9!p}Dij~3 z86;sp&v~AzufD0yIeYK5-u15c6(_i7^MB=}~vV&RKsOtL~C7VfN&aH$fQ zf|Vy*z>5Hsf^83yw9bu|KcUs~KfP?(vf7Jvp1#`{9v-%5o_XQMojb?>#PFikttJ!O zIz%<1*MWl^?mLNLhyX=V zh2>Rb?V9TLhYtVd(&eYTd+pk_FDH+lPdxE8n-4!T@(HGa4Q!T?%?1fb;GKnc9Cc8A zTwLe>H>e;nfT1=ZGf+Q{qCM-*I{VbmKJdU-cH>2?`u(8Ct1_@mlT1x4^@Y+vkG%i2{X26&nN@K8< zBY;k7wOW06-MS6Gy>sWSPvfPV46d&){enrFw~0>qjGI8-gn@wAL*dH20IU>{H+Vn` zcu_b>;d~!Nm!qeCf~iziY<%#+9e;V&S!Z1^Jp8h4oP5DKKX>Xw5AFO*g}In*FZcAW zM4Am@ZqCCM90Pp=LevmN0~QCucly8O`NUz!i~@5|ZGqX(e>RmrmrMP^b7cY4z`O*CMVwaq)_n7> zd;avqQ}B+pYlka%iPyHPufE#ceDj^>O-|$2>b14~>iURKg@VD%Fl<8EkQtT(ACD+# zf?5JBd8jtvch3Cv001BWNklm*`eSq7KiLDkUt`Scq&xGPyhD!Tu2EC6*DCUSfJ!% z*%&ZP034Fcf|*ei3eh^!YzSN9C}eYSl`nm%^*bh;gzSmA3u+P=-h-DcS^iE84D_#Fy}BB+rVPOz zwLG!~k0sxBJlFHS{q+lv%Ccq4>V18y2bZqiaO%^0CO-7=!;jy7|F(xdGdUw4P^`KV zIPsKPW%FQ~on||&Mbv6$C~x{uA=FszG$73Y%|Ngb@QGr|Y68Lm4~D>)Hb7&j*2d9k zk1DgBIPSP(-uJ)*|MUo6VxsX)H~rhlndhJJ2O^WxB;yL11)&p5jv>$&5WEJyU1|F` zPpmg6#G;+ok)+44>Y{TVcwpzJpL*)RDR}7<0zUQBA76XV)(1Z5U2>VthO)uI)da1f z)dZX~5D6=gmWc}hkOLbB&IDc?VDd;3f_LrCF{@U7c<0V--*;0&lF*n!^LiykHLZYQ zpeWIRSaPQYOMnm{1TO^dQ?MO`Dw*l&nX8sBU$q(EZhZR^jp5;8d-~~DtiS&H>pv!1 zeNUybsOEf1%2n93iaaks8tmxiYj^VO)r?~xsinOlBBJR5C6a_+P$^u*8bC=(`^fLr zj1wK~M11^E(H8*;lG|>(_u=E$uli%r=}y8e_+kc(4h#uOP0Nrxgr{f#d7Z0i_kd41w$_QXV?ymRs)nhBeFunMcp5)HO3R6a?tE7O;$aeoqCnwuc&&iV3a(a;Kjx{Y#(wj*+wcGX zFS+E$mf|IfX@-aYKl|W=dtP^N|K!KLEIU7`ZcLD^p}eTVYZV?gm^5@vbwXUB%F^wh9vvp{Zkzez4fh>W6EorCvBghWQq+cD#W#OCe<8CEC_SUJfaN!QF(LNQ2vm>xC;A z)r`2+*?ruBgGc^i`SN3brKcy|xpU{v7dum=_3JP1oolzx-2Ujje+8|cynN*bJ2e9W z1~5I-0;C3ThO)V&y#K=C0!2ZaP;Da12+hVMmM$NBe0KKG-T3~`yJi{h0b#_r5Q~9;rZd047;NzGyGn{>Zlfeb!l*zVCqtZhqod_>xP0 zYVfYR?tXozll(zyR-Ra^_gWD{&NIkXAYwplK~a|43P^*0BgMS4XlU7 z3xZ{oy6Hf73DFR^#WyauV=mE5@=+@^{Gr`G`VG4r`}g17zWVC-|NGOszWAe%=4-`G zbG?5M%_4{TWZpE3q+6O(7*H(pdR4S?MwTo_nlhYJxzpM|wEf`+|Ej;ga_pOb=%@en z>tEP@0NDDXP+o?Ihwb!qvGk5D_rBF|-)~kc8;*rt#-Khpq0r3XDPC3|lEyHZ0R*d2 zVh^B+#O6rIV|Hp(mM^VuIrz+D*Wm?7o^9E(#h-G@@STr7y1U4#wM^YC6g@C$7EmX3 zp(;WYMbU$j2(boYjxq>!RB9{XtT&y8TeIbs+dsTw#iHLn?X;`!yWxgwrtre%4qblv z`_rkZCy%+|hJX4IAo(?$tmp#`a*_?fWOdBUv|vmSZ8z{pGL9~32C50X1YDA|bb!&A z+(vvKRgi%pMq)gg&2ebbM!R`L)~-GF-htvmGKpsp zC4evzn;rfO0ycZBVAa*>5uMxR}vE$GSqmor4CrZI4g)u3t zO)1ZFbUG&R%_zg{3_&E6X8;9LSeVP61w`_2I0pfhgbLJ~IGJ;>6_5y++wgJ_X*PjY zYeHS_N6y*&nm@ev-fw;#FQYR2jvZT?S6%hqj~v>6@T6vE|LK*gWiNF=8A2`w{00OA z^#rdBqYRRODu@sa^`dYY5}Qy~A(I#BMWcsDKQl5i^10(rJn>c*d1B|zopX3$H6T9m z#2*_fim9`9?tb_Rz+J(n@3`K+Wp;W>;AIHoV+9};WLX`BL*P$JSoA3dl}sK?Im9=i z)I_^A4WLocvtF{@WN>f6K`Q5P{haYrJx!sUbuX}UUSXgOupqUKmCzgZhT-PWOh-b zv7f3vtI^2`c@dJW?&cjqQf7+oHL=BjGKemMHFZk+7E#f8X3_NYq2KxX7jC|2@v{CK z)~tTh-DCNoC--gNUT)aW=e*J8&6{nV>&x%CXX_>Hwtp-0;)@qAJGpPJxx^@XkxOVQ z3RfgFnh1QbAuV~;(x&94r zc-t*)6c5~W>r?xIodv$X^@cWW+LWwXwSMv3-0Z2h-16DYitKG{22WS1XP_mNBufBO zL*ad(H%S4eHi}{r)(99isP6@tK7dlF#5u2sNNfaD6=jQ5_+WgerseY;v3V!#J`0kn za@`4!mwR8#&_n^`P(KH243Y{k*Jz>BVvxx|oB#}X@1dp_6H_x(?cI>78{K@{ZCgJL zbiRDX8CQN)6%Rf1(DjEAl8xtezph`uK3%?i!>W7k*?LjYZoWf#;B|vTr}WRZ7IW6O z7R`o(lhv$rhC8j&}Ge*KVMM=FaV+ofE1;ta(8-B$lAJPkkaBy2}CIo^$;*X>Uv0A zokaQo?T1JYRO)cP0s&LuEqTdEwL;FPB-%sJ0RjdH+E1hdL>MBXK~fzg!eICvLj>w4 z?|TT^N5q39(nCd2p?2HC`5Hv(@IHkT3#f%k=sIO0cy%Z(8bcOE-I83eNer?YWvK-0 zV5k5v3t$Yj-Zp$OsxI#wKI7EWerxN4TmKo@_ab+If6t*$Jh64`k~dv3y6dSYFJ(4W z^-6{<3@1=;B2ZLj!3R_>qa%&&}m$uUfU%^e!8m z>Z?}W%*@R5**JOArppGcUHhiJ`yT#ZQ&Z3U43yu%ylBa)l_#6&=>*>QgQy?grASOA z*eH5nz7nb+I1sgAxC6x;fN4~#4pPj>y5pB^*}dz&-)T0R&kJ2Wbm)-ZxZ&*m2M_N5 ziAtqE^{9Y22^)))(mWKJp322hT^HpG9mEMzo05fRlKj|%2c9`ssAu|=O=s>saG<+6 z9_`?B&)t1xTO7f*?l6P9ySoM_xVt+E?(XjHL4&&lcemgW+=2&pCxZmHJNw*y?vJ?r zX+Bi-4BRo-Nv6a}E=HFMBJjqZ-B}OKxuj zp#eex7ruNfEi_u#I8aHj_-@3`B2X8k`a>7IH=KF#IHLa&3W9q;v&)&`dpcNo+}fhy z3=2#1H#Ob!gO##dLDkW&si<4K9@YT+_-+qud#XRVq2QHMTO`gN65ph(#yalB7X$}qsmkHUFATH6aG%}|Ka1sEA!)>~Q8StT6#Yo7&7uA_91npbnOzX7D8KF4Z7Bd%(%q{$;d zqfN3r`XisoBwI%CyO=w`>R1PJ@OOn#^$e^73y1;t=*PKd)~$_Z|MCX`$(?jBzSv%9DUF z9(abcC-HzgFkHx|-y`}95rGbYf(C7j8eT?}BSP7)xWoIAyI`&9@U~#^oAYch=4dr$ zqhj--bxkH)R6mm=ocG@VSK&r;)Qt}1D8MTDb|`+VG^?J$72|QSSvbUCBAWRo`T>I% zIuwb39@{&Apspd;84*ub8ep0%$PXtrq4K@K$63_02YuBXy`KK=US1Btn%Pg|#Ffrg zLam=1Mh&VIA=93)dBF_PCecaw_Tt0i1GMtS6zH5nF2eD=x{_evw-I!)EpWdO+#X!0 zZh$t;YBUbt^4m}lQ2WMJHeKilYI;cQKPJmgJX&a!XQqJoid+U~Sf0^S0S78mz zldM!koR<@TqREPpbQ2x!0` z9Hv0Z`6NLByG2x^+lfm>6-DlY5h~9uCLIkW3pebLWK&2_t5(g|l7p39>gs(sU$pIa zQ`rcv)O#Y9s9HLeiQZ5i%3Julen5lbDnV$j?vEgxEd3`pn$QKa(+oipkIF>GT;Fm& zYe7baRzz4>*yjZXPygws-@V}D!Ta0G`r|uD5!j!@gQ7D!N~ip^-2V3isn8epHnytq z+m}-5PHnZ*(=fa+K__ua&?FmF>d;F%p+-b2?in&nL=>3AYcfU(h=W!SG}Ab{B*E`T z5@nW(qX6n7(*p51XYF!7yw51bphMVUl@+wY@SFq%pPg?voj%g~J~lG??jwC`!NuZ4 zk)1Gh$ogWz)J3{#w)&pGDL}>8MIZFe83LvQEznmmU;lpox_qLvaH>H5OfA6Smoe!Gj}~zUr_SRo~9Mhsa8@+%{q&1f4erj#hymo`DC z-l^s5sL8SpXPiO*F2;xs@s{1=;}K|7!@p-M3p<*EZ)%A++K5I)UIY_(PzAy4{Q4f~ zcD{_F|FXL8Ct}>;R`p0_?m>2Uo!7u%o13z)Q3&JKaMA6Pmow~=i{TJS{OJKM}5z|oZ=cQ?cC^RoLW*^P4$`G(Ut2D}xp9_7 zBnYuS#UxB}CfEfR_MbBz4*$i)s2=wk0%g%7>=O+7Yal2}qC0dqtgwbrzVYNrTg+@$a(KS_8+JBum=Ika zgUvNqmrV{UEmmqPD}*Vba4Gk?vUIba@8zx!0@>MqWDM-jT3W8v(}VcB!M&gC4AkdW zaCn7eV0h*sHrdl~u`*PNol)fGsp_H%Y9oYhHzG4EGb_y38>Zk8G_! zmz^V6&-ZyVxQpG`I1ZV(-^Z4Th39L&*NyvZKAjFw5T2ST6P8I74dXf51F%=^tP70QHl#%hvl=o7{hN8dB|@-3L#_S%rrq#Wui)b$(Mf3S*7IT0 z%Ztac`=A*T%sd>}vdHaV=i>9`h%bg}{RoqjWQ8YZG>+a4a5iV;jTog&qG=j$X>_d^ z{TcH)=(g@<=I`m{w(E^eMj(rvO$Jt$L;L!cH!r2S-Jh0q5>sVN3=XMXMyaB2GSVYF ze^*_0Y9pI96q|}N6v>Dd6o&7DVG+b~L%dL_p_D$;<}!h`!jCKHrKM{;mzmBi!@ zgh1qEOo;mPt;NduNdFKTleOo!;CCW_2-Q={VRnU3saKgO!`SNPHnV##L|ZpvA~OiC z0emzQ?}rNfPOR!*s)xYdqpTnI$MrwoC6-9t9Lw+GlrCIX%>fb3&V*c`H0Tl@T4O{G zVfYFpn12yaAyRd)QvYy2uPH2TSx9eSe=GQ^sIM=`&K|J4DEWhBP(voc9Tnt5WYT;A zm(7#~_tI?dv9w02+)Uxx3#V*%S3Z;fxj~h!(ZpCPfc+NzGmWgX$D*BTT zsFE@!(5zm)N>VTMkh=Xcp;H&27BEu}DH+~N;k}K?fFDOTgT&ev^xRR^OVz_vVIt7d zNF{Jd5&BE9e+%vwZBW7^&O#SPirZyoIfQZj=ncW41;-JUDQDwdY7}_;-#B4A;?~Gp zqbDoi+(xE9 zZ@VAs^-S1WaO`16LNm&J;gH%##b$EH4&SL|GgfaPRDC%CP5Qtai4lfBYZ2s2Sov;L z6{pvsZGo!*oSC{6EBw>nqGmU}-ZI)9E3{v}eu;_M4c|dR0~6Tdu#u`F%}W7nWna?O ze+tQum1C;*;%j!_#gM#gNzz+Ntwn`MEWpW@hcTpIY8?iOdyrhK_G~qWrntjY{-b2A z$`mx+Ee7SWBZwl<+b;VqK>Z+m*d(Tn{mleOVXMfDylt*nA}OOott>TtASkc-c&gvH zV)eT+YJfIO&@nJZ0K9=f2-ncKJV_Cfa>p16fA?uV6pzI)Pzi!$uf#{%$^XPs31=Nu zSKa3S!p>upH(Y&1s{AQR!Gk3wjH`yHMfoXANot%#qd3|%ee0MIsC*{c%1q{qehih@ z0(%B!=jxpomVz)fqa+c4%A@UT2ijh9zCj3bKhAsD2448kz_*`ytOQtp2y8!9_q~*| z1|2SJ{q(4vfvUon(*7DZxTG5W?N|lyC3-Ca(-gj1pZ;44?KK*p88B8fPt5~!w-~!9 z$Aseo5`BRNyRi{@&l=Z3fY8G#l127oKEWMuv5eT<@W3h-@#MSF>$^|sfyB`?-U37s zyTnrLNX58K$ktrz@@rXvL%)vSwvIpE_Kk*-ZjCdvsA&)Pou+LS&Z(&^ZOad4#_Nq5(}a?z_%uOL2{;H9h)_zh2=(+M_7N^$iWT~o z-TXO_*~|BIH)h;TD+O8>zPOPoWiYZ-wB1cg{KlG>Ym30n+WLCu5BwRj3=9cv%&sNV z*-{agR+3}W8VNu60LGo22tCBG<7L(h`QvH!<8Jx0(bLv;*L9?me=e4g+#Q<^V~AO$ zaKt%nZLqq%@NegMs06}zQHI8SlhAAc9lbxQo!=M)(irONn~a&&LcbP}7Rx71jW!U2 z-i-z(r4Ge(=ZEIt7G>$ya9+H_HbeKlFx`~KVU&^#4<(_XL!^xw`9~p2D>|Q4Y%T2m zbm;Vy#%k~o^Rpme&?uFnng5nd0T<1Ikf`8^uj6-(MoH>YLRfT&ZU`mZw~$nnf>ifY zDMDpyX6W!YG3G`O|pZ;ov)N1yMa=TIk>iy1$N7#sw8LLSB z(3q$%Vctn?Y@eI)T6Gh;Il~|Z&IMjuU;A#of*!n7nu=C9IiCb6&(vl)+&AG$@BxJ( zbz+Qcx*VpmF}kdeuQS^x^DJQ*e&@~YQ<&FukKO%a1J}zgcG~`5&HcZYyEzxGHIH~} zN`QkYqW4!1NwwdA4AG3T?&x2eux$d;`7aSV{Fk+>%>`;t39F5GxOm~|suD0;*5!NQ zd$8n|B@8jATCnJ=jMJX-r4e!CO9!CT{+JyZ$B3Gf6K0z&`89B<%H>$M)3R~1mnqJB zzpU3kew#ITX>{7LxX^eMi-R0beXV{dF?f7i9}!2HOyX1fbAbT3%{9AmQJ?T9kDdqF zzsI-wlNLKwpv^9J^uzVYUQ+((D&+j^pWwMI!sUxD*%Fs3Zmimy9lFhlmC@DF_-zyB zDVd&G`9f_R@!$y?(Tn%hsTakpn;-mEN$0R;`TaHHZ{KkN=`fQW@X@aR4MK|OzQGH+ zA0cvjvsyaE;hC&ha0IpBe_e}80L4}I;|z$EDP|5e4VkFTC0$D(paQ`FYeaFBtD;WI z)lP&E;y5kuV+!B_N#t%r3_6SAw)1AxIKPGP}wdS0Ez91EJM5>TQH(^*qc@@$C?14zKpYZh}$rDuP42 zX;opQ{uX7!B-CJb!*vPY5l-Y+kro20nyuUQ$fpKYBfY^+-p`eO!XD-mD-NEoe1)!t zh2hUXyBnYC{QknS#Be78TOb;nN|PD&U=o5qK8uI+EUm_P)QLOvPLhOusMAgx7H~C@ zD={Uk5nQn^&v)D15Aja^n@$^Dhkp~FG!q2%8oquTX_RxMW%tURSg{b1pDaqbc&3)|QRKLsM+o;`&J_H=TpkAUAmTn9$Nn z@2}*tR7ue{j0~9PJGWn>MS!mR4K^~yQ&@Pt*G&&Wr(G%=VHwr|YxnW>@kTy(ej@&F zt)FwJEuRJNZ1wX8%Bs~K@p-K=vJ2e@-eB><Ei2-H7GJ*$OW~hy z3A6OWwQ+#)Id(CR9H6D31|CAowZirnFX0ADJaqZS28zU?z=u@-Ph^$A4G=X+;3^Zh93OsW@cu?ahif%pItiP)5Cbp zl}w;Yo@k?1?b%g|zT+F8H77j1GQZZ8x`##f(;ghrS~_4y@2FAZ8X%-as55RlEg#xb zr0a%;DJPkudyDSx{XpIFN-`(C>xy8tEaC`b&WRW^v)YPbMQbly6y)Vtw!o-tA0(`dp`fTpK0&8oPW?_>Z)hw+>@hA_CghX<;C=5L&wMB zcbEII`UzyQ5IP7m%)UnJJF$!4JaLuXPbE=AfHl)7EzLCd2ySj62`(5!Z7;*Sh$Gb!~~;sm3CqoSZ-pj-DU-z z9{zS7uLVKh_sMoQ)=G_nyQ5o~04HE14LU{ziw;<0 zfxyWAK^xd8#8f+LJgt^*gyE5eEt!JpNyab?+TTYR8DCA+nQ6dL(cT!6zM^et;%S9% zu%a~7f>S07*absh(1skMiG#Tp#?)D@;NScT7W|~SV4MCQrO<{W;M_1mOKY1 zyq4v(ZMZ(+I6ZPtTRq$_4?k$~kLCPev!-Z4`S+OVW(O9ptp$Q_f;jM=a&rTia3e7zM~YlPaP+%K}Q13a7uy0c+&>x zLA;2u_|d%N{EZAZ!Cx3d7@)YIjDLvwsDe~6y?MP6&l3j4Op0u&E~!B+m=lYpd=3Q)Qggt&|-F_@p-Z1fM`VzR@PZ2}pf~-(l=@L_v zfH1Zb8MP96e%L(%VV$Iv;B&a$*)a$Bb7To|ZWv%0v7y;55kd{cmkARe+$DLU_^hGt zUS+fpXQk9ch}+D16fK<>nHJV!P}_AC5;IhPHZ_9S4-_IRf*OXwlGvzdXg^vA*riS1 z^EMJTj-0m+qftFRI#YEH@gFiHO>mcTIAV0vGf)hog~a#XD2Y2B0V2KK8&Pu&^WTyE zJ{6Rp`iNXqVOSCjf1`~Qx|JyY%InXbK%-#SaPgYIiI!Ny=ec9VN+Ntr z2mT2@+77rGXW93*u(RugY@TB)pMGB4eFSW-Ewy82v;0`dZbuDf3z0947;Z_%hY}kQ zer$zBae$IpOyc8`BN#F+v!=mArNu&@&2(6bX3Y{z^x$-*`$?t?W@ zq72yu%BlCSHDGuXM71iR6G9>g8l@XJ!&%)aX zL!WW!>%rw)g&GzEpYw)s__q@jR`zLx0ae*H>ZH%~z;s9le-9tn-cY4*&);V>$e{@iLw}!Z}Y^My2e=^t64_DLZzO31Y79x2W$sz8vK z?4^W`5K#m>34T`Oos8-a21PO-%**>^<=S@F{hYdvM?9yF_l3lrLpk5Kd;>qnz{{uO zk2<#}D5{j=W&yP!UXF8#xn22EU_88&=wlY3Au1^>3hgD5GuSd%MNAipvOv?AoGZv$ z5GX=So&s8q0LVZe5HQ2aoeg*rhkZkA$Ql>`{cKT}8wtGWdwVP}3VQ4N3mrT#kPf<$ zEFr0pK@q>w&KG8^=8K+pXoULCb&rM)(fud=C5z!&b%g*g1iQ$>GMo5xpjxyU@O9@g z6XzU)^t-wWv&!{2tFN5dQsJP%U6|WrtI3ftB^JbSK7azQg0xd)y(ncV&>PXmcOYFC z6KRZC!Opk8DzQS4rEybipj0A%_J@0O3o!|&2Et&Y^aD%@}{&?`e^5NloFDI?<>UZ_@Oz&O@ABVN;z%;nf{>@eVFZL4>UcVbfH1_z+ z3FaIXp)@K@`*-VJe4@(NTN!2%*f>@>Bp-3uw<&% zcZ3I2lDN4qS7MT|QwUr)v|4;ypK1F|VT;z6Gv}UEhisWIewj=C{e!B!Tx+|f zl~8hUThjyC#4*#qZOFv7_<_=!bh(ax`!vUI&-a%`ES++?N;O)JdK$Fw2WF~Yd3Jt1 z;SqU>tICiMRcJE;=v`)rJwA}X(OUeIRWB}`&F6uZ{A=+T0fAG%6+VaZT!76k{xp}r zk*&Pnp@=+ekv%QR^LtG%9{knjwyP{9IEZJ*-$5Gg-yAP^-yE!BUFmV-wozsUXR`}lhwp4aWTe`@t!#JVQKn3*)Q%9FW=&bYM7 zbt)l_6}bGR(wSUOGMydDUKBf&M4jjhk2pCyISP&2@(p`JA{;?k|1b)aphiQT5*FGd z=Shp#?%3lGz^qOB?wHE!iNN~~R>5P`EM>YLL^=q{uJR~WT6UhE>)a>plTbKi4$lQ$ z@|u)|dL_9nvC!CD`Jj4TTBacDS5PmJ5k%0_U%w>~ zOq0?3_xI3`s2PS0eKNHia)+Bb7(4h#N-|p5?cz|M@+=sfEDrn-Nd`&8F+_B!h)TUW zCJ!{g3UM+fB*t0CGI-udL0I6>GfX<{AT&v|uB zQ%GqsFLApR+@yf38@yAPEfEXLi=oLOrZ(08LK_Loc;a4G#1FS**&F!Q^`gvgvoyQq zya9>eZNBh+O@%1nbI`ifbytYzb&`AjQg6rTVsCLH*K(V-|gw>^D&8 zC1!!InzX3Vm);}ZuUOtOkOMgzS=&&ne`yJhm$|S-lh*&j2Yy6l-5XaUrbL=8En6lJHzR_bM%BauG8)%l0>l z#hU<~v3@^Qkc2ye!=dF>Plm0ceUmttTZI- zQ?D5}P#=LK5J3;Y-Egdni@2bbqThc|3T=0wW1uJ^WLr#i;WbAvwav>5?U%EhDoxze z?%5M*VX@VUus6|hyJ=6DfHcK}#CZAN&tXp;V{Z?yF;D69J@65sXy%~VK))HhMNofX zOVUD3QPg5Ha%3FmjjVqEC9%#^;5ib7!~y|TfQ)+W+}&4*4gXkx!F)$Ys5ps2o*#9S ztUe1mFK}>Yv&0)e@iiA?kUXVwfZK}$YaFZ5r6}P|`RTwL>?Hi*eo52d#Y;9=RmNRg zdt?LxXGtP1-y3zPLeLPOA_vkj?al@-qZZl{EH+66m=~<)$4V-EjOKaK@qq>*S9%k( zsu;5hczKfg-Qr{jz8~zXIJx$6(pwYJkxskLThz(8w{; zXf&WTn=d>yppwIEiDG%?xuQ;p;g#SC(X%Ad;e%1Lrx7Sx61tzJ@dC_W?$?73uScCW zqMRfuaZzdocE@<%c-7D}HmJL%*h-_TV7AEhPv$IaC0>bZ7z*c0Xe2=EBO~|zNV6Z^ z3p;aN3&%%~1`|Tk!^6gH$R$I9e{F9)6oN@biDt+mpXI)Gxu2Nr@Bh%4y=_jkJV{Xz zp69n&V1LLDdXGQ@Z@i6lS(^FxzkD@Ak>!q3Dt6}i;|Qsha4YEGp21#f(vBF5DoVO2 z&QWW^R_B!;0)C^a{fr?FF*T-Qs~miR?Jf|vJyLCvG48DO5!PAsty)(Rc-Synciq?d zC>45=1c~$yKg<=;8L(#xd)!xA2{l6sL|6olsC4+J*^DX~0I&Y;1^Woz%qAJoBdIB^DUzHu8epAVQ=aX4`pHqWNfBUA>cKrIE zg{H-c{(d}GoA9CkTI4>0h!9(ZI;I40Z28hu0&b2%BlD&eR)aXK-j4Ad>p!RY8ddq- zU+Rj<2QXkj+?6#M8R(no%}KfOT;|l<`MLxuS+z9gSb7w`QiKwipbx_; z4w+p&1)6UJKbJZ2l+p6!w_G!o=eO==2!DWsp79M~;`wK}4Lw3EvLS3uuW`KxGJFvN zO&B$p$Y$A&HZe5TvMtTBcwNT?Zyr`^TbcAACbxjtpdX(Q2)(rldhA&7CYp)_!%z5k zf7BL$G%z@yC`n217sO268f*r$algA0+VH^bG4OsaE8!%!4X!*ay0mN^iCQ7^x)7@8 zJCCzV|8)8xV)N_u&fvj+H(@Bst4DQvO=m4>G~eI3N&B|F=X8FOjKAZxqOl&hsh18Q2oc7+?sNx7J%W>68*E0{TC*jXekH54g;!K zU$x1{`A^(Y2Qq1!Vr4!=t7?zJtDF=a1YM@_AZW%_k)HhoyLy{;IwMVzUa-)}>r8pu z-|jOKk>^60h|!S|ZK6+AurM^R^s&UWN`P;UPjRNV3(bTUl=U@8W2xN?Y3knrrO-(0 z#zwaG3Gz!rv?RPDAQu$2!-fmZ(%QUxNw>GIw1?jN?gf$8c&ETqu$85!(jFf$_Wgay z&eHNyCD?WZpQsvUF|VMC)ZDZI1iO=52F;LdY)d7FsHjX#{H3MhxgL`AKiz!o?0X1r zZ&|-rUkb!RMtwNM2{l*s&MxvM{pso=^OYW!+2j?p+#Lmv+xtMY7lK|1MI9PAr zzFk}b`>(v+>GA|T&b{|)+qKQ&G)uoSXRfD~keCygQW;+$;z>JyMz91CXVhP{&y8=? zPNZr9n7LA}*&;&?Y2ggyEfZTPgA0P0q>ZP;@9(j#xoDve&~iOyj)E#5wBN1QH$U27 zGJR;q@`~8`bua$(n2jdBt*ST#y~d0_ce|(ii5MoE65@c)YL`H>^O^9AaH=7S7rwA_ zQlCP2GbnXPGo7$a5a=5*sME!x8f~a{39xH>t^ySoO6Dk?S~6G++}E{4+@C()Pe$KQ z>;U=af;6!@H4qXbxpfC6#3{P@L3^OL=)YZCG z>)b;a>Jd7EUHi$Sj9YkiCD!EuLI|DR2w*|RG{(@e-WYEsccW5x{}Ux+FIzH zYmL6YHg!AT)`7G(5^M-3)HS5A=F;m=`hM1G+n?*bCoRZ)>_;Ia^EuGRE6Mm(wawf^ z;6zYuqtES8xcdpXU8v(@b4Lt_7Tf_8M-g>aOp4|T7iG@^r|!Eh%okEcsz1YR{&k^E znCGBSg?^$(Bnnl;h&gOJp%=q$rXl0-*0xUVpkN_i40rnlWNHJB&}T~ZxbJL~#9{;I zbD{Z&9BU0bZMVPEDD?h~HO*<~j7p3n7W6)uGmFCL@cT#$9S8GF!Qge5CjQCi;``&j zPL(L^fSjU@M9D1C%2tdw;>W$Bm)O!QDI6Sv}ITq}`1g)@B$ zwrm`1Ilj%{3$3K?$)!^NMaxp|%@#M|C}io^Dr9_m7eNz#+-Z|Go5c@o_=X0-nA0dI zuX-Q(v~T;auqrA~84lAb@Lf`r3imHkY=ts_iZ?5z!%Npor0a5&r>P=cinK*(?30%H zgLQ*FclbF0eC9Y|{&g^xtcKasQW8RF#C?v3R+hR{USRK&wsLgir(?-V?OLBpB5Aan zmDNuQ-%I3`u}5HQ`&5D7qM@-C;odEr5~Y56;7@m2^>fwsWzZ}qxU?9c)6RZmNuYuWgL%l;LeJvXG;!u#>rSR1L!9nWz(Wf# zA5C!D5)kTfBCMafn=te_RH+l>@HrT)eGvK=GH3!y(h}2>XHqa;kP5zs?D4o8t2+d9 zlP5-f=>5?Zy)jCnhBP$K9Xcz#Sc}i)?v^#!C{x^fEdmbD>GKj>7vQ!V78(C{^k`06MZv z&u?{G1>i3@ZP1*Vt7!I|KW-8(wNpR77M)vfecwkG1nC#h)IMXh+H|;F9+Jlw7<}@~ zWC8)_C;;w@k?Nj*`~?5GhO%a5(85kls-P)`9-)N_jX}xJ8sEv)pd*#UZW)Q$m1K~^ z!pG{a>hTi8gm^DXfxO`5)#&j6zZ`kz>0Ku!C5i8604?4gb)~W5fa4JTK@5dbQ}Hrw$-oExG%9aJh?8_4fa=LDeOKlS z=a5+fG$aG)lPrkX$^cv-t}uK!D&a@w$x%pV`}jyl`keEPvp(?SZ0W;(4+OnluKc$A z!^)`l-c+A}L=_T_NTf=%WMfv#e&2BgFHw@&oT!`Lluw=@lg;Fr{2JC6`>j2vE<^pzBG& zhZbL15QN2Jv;?6$v~4LuuL>X1_vFf7qfeV~MR_S{IaEL7?MPH(t^D|mM%D`^Grrz5 zZ4TIQ&Us$^z`Hwbl5|>jJ`6An(6BRvXZ)$#`k)HmzQ=*qZaNeQal;FJ?H7CX3#rdtIh6rwvZU)_V>BI*W-Hcmq+)k z-QmhqtTLy+nX{r=t=^**hOdeFzh)>bOXr>1hw|61PlUt}7*h*w{BEvnFOOl`^u z|Hn@vD~d_neLjdoq}mcP7hzo_EuKGo{k%}{w%F&POBD1luW-B#ESt=u*h~v4B^G%< z4MQUixT=qeqMep*PGk90yLh{C$%Obh^CxqHJ!PZPVl2W;Dd8wj;$DT_uz4q|eS411 z5guO3ku)dj+!+3~wxY>C@3-QP^@=3{7vl0QI}Dn;ML#kkSuI6Pq=C==&P!ZM`^@Lr z^oV5(f8ui8w4w9p=UW>acb{}?IrJ(eb&BZ?6&9Gi3nI{CDYa89bj?Q|XV!F?)>k{$ zq&k}xM4FDVUh<%W-!8Cbitu-Je)Ye^>+86Rux#drLXb!@AI|n=k%z5Kjtxhr@MfY+Gt^71h1oP5IsqK# zQu2n3YpH5OA;psKeM9f^fxkQi!9}tL?KR5u{%qp-$SZie*skAg(`C+UG!oo=jBmZW zNS6QMxD-V#DkTX`F}x(tGJebT$DUiJsYcJPmB-d*x$;n|oS&Z~i4b90x!mVhXKSB< zo?gzcO9wo@TSyIZt@AXiPtg0|aqi?wvQ!Vd6Kx)Wke{-QL`sfHWnoEqEz7_luaARG z-%Y{oTWwg>$ltHJ-uu_lb!9fO7@B-Y4@MJ&B`7 zgDapaAzM_z7fxs?6zwY2%6?fG3Q>^xd5e!TLFjpZ6t-zMYzAz&rA9+1FFU*Q>2M6y z$C|B&5wG7poZI6LocQuuQP746&s}GE{_Eba;^?~QN5R>Es|absTUg_>&29}N!+fV2 z=33;k${gh0W|hV)9j3=)8B)G~RtpOP>pWNw8y}Uwh<2aOu13$Z@q*Rw_)Y;U({O)O=~c~!!JSHt_$clY48gHcwIuh^c5 z-;u?BbWwYlIcU|$O_++98B;UN()vcd53??D3=z}s+=_EoFoxp&ZWGrTrF(!V7VP}S z+qnUE|C?8_y6+Es``FyX{{jpK!1>imGeS9YaD1nhqr?GYtq(CcCw+8UJ>~ z`vgrlvvjdz>{zw*#xsi{^*d5!zG}95pmlvFKF-~sR(oM!V4#+6{phGnQKf!$e4HZZ z*oBKXlL%}_3(dM@YcAnD(g{(w*6nI&=mS`8ou5Rh?Brnqjt<6IwFL5>k|8!+P1<(E zvbWC80X-fc^MlXHT+?(EmiV-dXG{0#I1|yBb7N}&Dlv3dGlx$j?s;C>!({^e9NwoP zaAe+`u?K5QpIP;9g|q$M?h5LFdI(m2Ay5g_|H&MCu&~Vbw~ozLj*n1TS$W889ox#g zHvghSqzhe08W2>Q0k-Is8DMm0^h?oX@f|4#JZU88dWfps>_sw_#_{OL{WL49QIMU- z-AHLx7TKr5DU|i`kI{Tym%*8-MPCsCl$~%uCqS9j-|E&60DzB`lN8gWi?YwQz^1Y|?yd8WUtntxrM zT3J~~wdUQC&#L&Tm8SY0BqJ>itDN`zdiBuzaA_y-v`Iz@Wt>3Fhlz%=$*Eb5LIRj7 zc{tjckU#I`LerSGJXS5wNvKMX*~4t9v-o`1k&&qlC;}{s4S!wiGF8;BN2y6jOf(X9 zKbg}@`%=IC#s9oZ)@wISC$M3~kPIvZpjHtH@b&k;$WQdh&V!rH7y8gleu9J2?2#|c zkR%{q&HxKE0@c1Em~ut;FEK4k$t@CCYidV6pfE!q)ZnM3X(A`WZJ)PMk@t6wiQt9 zXCIcbc4mxL6bIaS6EDIVhEntN!M8t7B<7V|X6V|4v_K zyW!$xXDkP8T)jM@teme{Oka)a|6%{q#&dJWrs4=3VVmbx|uwMRb$nlyTS3Lb*~2m|{CA2!^cAqXimy7jKU zmIt-vAk@st%1U0j?~)$fjF?(gTB2Gs;bXj3_TSx0>FuNSud?vD%it-c_q|ukPkqPS zJSZOjZfyLUb{ZkO8|}l=dr5_;KTe6JKRxdl4*^|}HC3_mRIxu*(5lx0MR6!Qfud?1 z=%Wrs*fXDV^19daSku$f)&FG#d!76{UHA=AM{aI!Y+4RN{=DraQ=jJgOTD1}!mq_p z@O031e8YR_6roHA1;i))(#?C@kJRBLRz*}`z^Rd^}d3)QhM!LWr!^MP+@q0z6ezn4z^#yD zr)hZbjS$8rjUx(k7|#Tc%xAdt0Z39_n53%xjYux!UMl>;^mcgYMP>UamS)rBciuZF z+IrTf-Z)u|8lz`(nTt6Ur}5^m0OD!8+qsA0?=26C)t`>yJBFhu(vp&rKUGX4(l2|T zu^O@I>HHlw%U?@+LU7hojsPf(6wo6Ke#oEkF)%O!c%kWE9vqCc+Ga8?og8~j&X1u* z(c?M(UxQ2jkcEfYq~zK}N>9Iz=ybO(2R5(G@cy2J5Ssto4{w?plP1(dJefmBmGSw3 z)+eIr(I9|NTWwb|O=`ps1u|et%4g^9$xsVE?oO{EC6gaJmk|P#Ksy%Dq)_DlJmj(q z$Ow?UTRv$rDhX>!Jj8AGuzTV}#4u@(`F}0w8FP>-ZT~34_i%3ePEwXQ!ev)kp6hou zh5gAN?*ASg9qpON6np6@UiFzqC%rS(Nu_o1lBrFbS4&8T`GFWV9r8|EUL;%ez5b!I zqi5bQdILf)yNF>V{g_fo#c+xdt#`09grfi3A8RJ&4MX=-Jq4G8ym;n#csOOS2{Lm` zbhPyff@D$Eo8<|Zd5;0yO5)HfM186id8_$II`n^gVGzPGI*;G1br_)WPA%!nIq0`f z`T`?-$Y|-~n;UYt^}o%S)wK^Q2Jt~Z+HtS{G4)OQPk7Z?S8ci0<=z91eqmx@{Ej03 zB*Ph-y5JtnQL)$rgW(IqZ{b+_=UJCVbQg*~j(vCL?X3k%zmXcEv9XC)lp)RlPhk(? z|CDRV_dWlV@ny;=Mg)m!38`XxU4Y9;OpN9JGcRU51cDj6!S(Wp=o1>-7J`*?fk_NN zj4;=RAL1AQ6pG2k&GAvu_QdkR{|xs*4l@`i4I+nv3jKnkTF7QuogjmajNJDoCPFMOpNd%qT2uWFrilufl z%{oWf8vfH~gN!mMST&TQ;7mY8Se1Puq7E7;VlWlu_X|r*sJH>2reJt9gWrEA>Lou> z1aQF5$elZ#nAY8-G#CRd90=PxRi+ujzi|F3Rg5M=9+td^kMzloL6Xxf_Mfr{YM0P|zi@m)!dkV? zv4k^IPM+m@Ngkg2}wM&2>S(hx)ldyNue{{UTh4;ra$(nSFv OmzGvk#tN$d literal 0 HcmV?d00001 diff --git a/debug-pe-compat-p1.md b/debug-pe-compat-p1.md deleted file mode 100644 index 5bc9cc5..0000000 --- a/debug-pe-compat-p1.md +++ /dev/null @@ -1,48 +0,0 @@ -# Debug Session: pe-compat-p1 - -- Session ID: `pe-compat-p1` -- 创建: 2026-08-03 -- 状态: [OPEN] -- 范围: BUG-20260801-005 / 006 / 007(PE 兼容层三条 P1)修复 + QEMU 端到端验证 - -## 前置证据状态 - -三条缺陷**根因已定案**(非猜测),证据链完整登记于 `docs/RE/Bug记录.txt`: - -| Bug | 定案证据 | 根因一句话 | -|-----|---------|-----------| -| 006 | QEMU `-d int` v=0e e=0003 + utsm.elf 反汇编(`pe_shim_setup_exit` 尾声从脏栈 pop 7 槽)+ R12/R13= log_hex64 缓冲区 ASCII 残片 | `pe_shim_setup_exit()` 内 `__builtin_setjmp` 后函数即返回,longjmp 跨已返回帧 = C 标准 UB | -| 007 | `-d int` v=0e RIP 落在 shim_printf 体内(反汇编确认);串口止于 "[PE] running PE32 via x86emu32" | shim 表 fn32 列全 NULL,fn64 回退把 32 位 guest VA(0x401017)当 host 指针解引用 | -| 005 | `-d int`: `old:0xe new:0xd`(投递 #PF 即 #GP)→ `old:0x8 new:0xd` → Triple | `idt_halt_all()` 静默 halt 门在 DSK 阶段异常无法投递,零诊断 | - -本会话不再重复根因调查,直接进入**修复实施 → 修复后对比验证**阶段。 -可证伪假设(针对修复设计的验证点,非根因猜测): - -- **H1 (006)**: `__builtin_setjmp` 下沉到 `pe_service_run` 本体(PE 执行全程存活的帧)后,longjmp 恢复的 RSP/RBP/R12-15 来自存活帧,HELLO64 能干净走完 `[PE] ExitProcess code=` → `[cmd] PE run end`。 - 证伪条件: 若仍三重故障 → pe_call_on_stack 切栈与 longjmp 存在第二处交互问题。 -- **H2 (007)**: `g_emu_mem_base` + 各 shim 解引用点 `gp()` 转换(guest VA < 0x10000000 时加 emu->mem 基址)可让 hello32 `printf(0x401017)` 输出 "Hello from PE32!"。 - 证伪条件: 输出乱码=gp 遗漏解引用点;仍 #PF=base 设置时机/阈值有误。 -- **H3 (005)**: DSK 交接以诊断 IDT(0-31→isr_stub 走 idt_handler 打现场;32-255→halt stub 保持 IRQ 防御)替换全静默 halt 后,故意 #PF 串口可见 `[IDT] exception vector=0xe cr2=...`。 - 证伪条件: 仍三重故障 → DSK 阶段投递机制存在更深层问题(GDT/TSS),需 `-d int` 再定位。 - 旁证: BUG-20260729-013 登记行为表明 halt_all 引入**之前** DSK 阶段异常能送达 idt_handler(isr_stub 门投递在 DSK 阶段是已验证可用路径)。 -- **H4 (回归)**: `gp()` 在 `g_emu_mem_base=0`(原生路径)为恒等映射,HELLO64/Deaicup/cmd.exe 行为不变。 - 证伪条件: Deaicup 首帧 BitBlt 消失或输出异常。 - -## 修复方案(与 Bug 记录方向一致) - -1. **006**: pe_shim.c 删除 `pe_shim_setup_exit()` 包装;新增 `pe_shim_exit_jmpbuf()` 返回 jmpbuf 指针;pe_service.c 两条路径(PE32+/PE32)在 `pe_service_run` 本体内直接 `__builtin_setjmp(pe_shim_exit_jmpbuf())`。 -2. **007**: pe_shim.c 新增 `g_emu_mem_base` + `pe_shim_set_emu_base()` + `gp()` 内联;x86emu32.c `try_iat_call` 调 shim 前设 base;各 shim 在**指针解引用点**(非参数入口盲目转换——避免把退出码/长度等小整数误转)套 gp()。user32/gdi32 结构体布局 32/64 位不同,PE32 GUI 明确不在本轮范围。 -3. **005**: idt.c 新增 `idt_install_dsk_diag()`(0-31 diag + 32-255 halt + lidt),`idt_install_pe_diag` 改为其别名;dsk_loader.c 交接处以 `idt_install_dsk_diag()` 替换 `idt_halt_all()`;gen_pe.py 新增 `fault64.exe`(`mov [0x10],imm` 故意 #PF)作验收样本,pe-compat.ps1 加会话 D 断言 `[IDT] exception`。 - -## 修复日志 - -(实施中填写) - -## 验证记录 - -(QEMU 实测后填写 pre/post 对比) - -## 清理清单 - -- [ ] debug 文件收尾(用户确认后归档状态) -- [ ] Bug记录.txt 三条状态 OPEN→FIXED,填修复提交与验证结果 diff --git a/tests/qemu/Run-All.ps1 b/tests/qemu/Run-All.ps1 deleted file mode 100644 index 524c6e5..0000000 --- a/tests/qemu/Run-All.ps1 +++ /dev/null @@ -1,115 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - Run-All — Deshab QEMU 自动化测试套件总运行器 - -.DESCRIPTION - 顺序执行 tests/qemu 下全部 9 个用例脚本,每用例独立 powershell 子进程 - (独立 QEMU 会话 + firstInit/AUTOEXEC 场景隔离),汇总 PASS/FAIL/耗时。 - - 用例顺序(依赖与耗时综合考虑,快用例靠后): - 1. boot-regression.ps1 B1 首启向导 / B2 正常 / B3 dev_mode / B4 无盘 / B5 无 NIC - 2. driver-matrix.ps1 16 清单驱动加载矩阵 + NVMe 写路径回归 - 3. linux-compat.ps1 VMM 分流(QEMU 走 VMM unavailable 优雅降级路径) - 4. pe-compat.ps1 PE32+ 原生 + PE32 x86emu32 + 错误路径 - 5. deaicup-e2e.ps1 Deaicup GUI PE 端到端(首帧像素/键鼠消息/Esc 退出链) - 6. userapps-matrix.ps1 shell dev_tests + ping/editor/fileman/browser 工具链 - 7. network-e2e.ps1 N-A netman 全链路 + N-B curl 主机侧端到端 - 8. performance-baseline.ps1 - 启动阶段墙上计时 → .build_tmp/perf_baseline.json - 9. x86emu32-coverage.ps1 x86emu32 静态枚举 + hello32 动态解释执行 - -.PARAMETER Only - 只跑指定用例(逗号分隔的脚本基名,大小写不敏感): - .\Run-All.ps1 -Only network-e2e,x86emu32-coverage - -.PARAMETER Build - 先执行一次 build.ps1(全量编译+打包镜像)再跑用例。 - -.NOTES - 退出码:全部用例 PASS → 0;任一 FAIL → 1。 - 每用例完整输出存档 .build_tmp\tests\runall\.log。 -#> -param( - [string[]]$Only = @(), - [switch]$Build -) -$ErrorActionPreference = 'Stop' -$suiteRoot = $PSScriptRoot -$repoRoot = [System.IO.Path]::GetFullPath((Join-Path $suiteRoot '..\..')) -$outDir = Join-Path $repoRoot '.build_tmp\tests\runall' -New-Item -ItemType Directory -Force -Path $outDir | Out-Null - -# ---- 跑前清理:只杀本套件历史会话残留的 QEMU(-name deshabtest- 标记)。 -# 绝不全局清理——可能有其他代理/人工在并发跑 QEMU(如 monitor 45454 的调试会话)。 -# 本套件所有会话默认 -snapshot(镜像只读),无需靠杀进程解决镜像锁。 ---- -. (Join-Path $suiteRoot 'lib\QemuTest.ps1') -Stop-ResidualQemu - -# ---- 可选:先构建一次 ---- -if ($Build) { - Write-Host '[runall] running build.ps1 ...' - & powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build.ps1') - if ($LASTEXITCODE -ne 0) { Write-Host "[runall] build.ps1 FAILED (exit $LASTEXITCODE)"; exit 1 } -} - -$cases = @( - 'boot-regression', - 'driver-matrix', - 'linux-compat', - 'pe-compat', - 'deaicup-e2e', - 'userapps-matrix', - 'network-e2e', - 'performance-baseline', - 'x86emu32-coverage' -) -if ($Only.Count -gt 0) { - # -File 调用下逗号列表常作为单个字符串传入,统一按逗号再切一次 - $want = @($Only | ForEach-Object { $_ -split ',' } | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ }) - $cases = @($cases | Where-Object { $want -contains $_.ToLower() }) - if ($cases.Count -eq 0) { Write-Host "[runall] -Only 过滤后无用例可跑: $($Only -join ',')"; exit 1 } -} - -$rows = New-Object System.Collections.ArrayList -$suiteStart = Get-Date -foreach ($case in $cases) { - $script = Join-Path $suiteRoot "$case.ps1" - $log = Join-Path $outDir "$case.log" - Write-Host '' - Write-Host ("================ [runall] {0} ================" -f $case) - $t0 = Get-Date - & powershell -ExecutionPolicy Bypass -File $script 2>&1 | Tee-Object -FilePath $log | Out-Host - $rc = $LASTEXITCODE - $dur = [math]::Round(((Get-Date) - $t0).TotalSeconds, 1) - - $outText = '' - if (Test-Path $log) { $outText = Get-Content $log -Raw } - $manualCount = ([regex]::Matches($outText, '\[MANUAL\]')).Count - $skipCount = ([regex]::Matches($outText, '\[SKIP\]')).Count - $status = 'PASS' - if ($rc -ne 0) { $status = 'FAIL' } - - [void]$rows.Add([pscustomobject]@{ - Case = $case - Status = $status - Duration = "${dur}s" - Manual = $manualCount - Skip = $skipCount - ExitCode = $rc - Log = $log - }) -} - -# ---- 汇总表 ---- -$totalDur = [math]::Round(((Get-Date) - $suiteStart).TotalMinutes, 1) -Write-Host '' -Write-Host ("================ 测试套件汇总 ({0} min) ================" -f $totalDur) -$rows | Format-Table Case, Status, Duration, Manual, Skip, ExitCode -AutoSize | Out-Host - -$failCount = @($rows | Where-Object { $_.Status -eq 'FAIL' }).Count -$passCount = @($rows | Where-Object { $_.Status -eq 'PASS' }).Count -Write-Host ("PASS: {0} FAIL: {1} TOTAL: {2}" -f $passCount, $failCount, $rows.Count) -Write-Host ("日志目录: {0}" -f $outDir) - -if ($failCount -gt 0) { exit 1 } else { exit 0 } diff --git a/tests/qemu/boot-regression.ps1 b/tests/qemu/boot-regression.ps1 deleted file mode 100644 index e6b450d..0000000 --- a/tests/qemu/boot-regression.ps1 +++ /dev/null @@ -1,232 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - boot-regression — 启动链路回归测试(docs/RE/test-cases/boot-regression.md) - -.DESCRIPTION - 场景覆盖: - B1 首次启动 firstInit.txt="0\n0" UTSM → DSK → mouseInit → netman → FirstInit 向导(自动按键) → desktop - B2 正常启动 firstInit.txt="1\n0" UTSM → DSK → login(skip) → cmd(AUTOEXEC=ver/exit) → desktop - B3 dev_mode firstInit.txt="1\n1" DSK → shell dev_tests → Esc → cmd → desktop - B4 无 SATA 盘 UTSM DSK 加载回退 Limine boot module,DSK 降级运行不崩溃 - B5 无 NIC e1000/virtio_net not found,系统继续启动到 desktop - B6 Linux 兼容层 见 linux-compat.ps1 - - 退出码:全部通过 0,任一 FAIL 1。 -#> -param( - [switch]$Build, - [string[]]$Scenarios = @('B1', 'B2', 'B3', 'B4', 'B5') -) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'boot-regression' - -# ---------------------------------------------------------------- -# FirstInit 设置向导自动填表: -# 账户卡片(3 字段) → 偏好页(5×Enter) → 网络页(3×Enter) → 完成 -# 每步都等串口锚点再发键,字段验证失败自动重试。 -# ---------------------------------------------------------------- -function Complete-FirstInitWizard { - param([Parameter(Mandatory)]$Session) - - $hit = Wait-QemuLog -Session $Session -Patterns @('[FI] fadein done') -TimeoutSeconds 150 - if (-not $hit) { Add-TestCheck $result 'B1: FirstInit 账户卡片就绪' 'FAIL' 'missing [FI] fadein done'; return $false } - - $done = $false - for ($attempt = 1; $attempt -le 3 -and -not $done; $attempt++) { - Send-QemuText -Session $Session -Text 'testpc' -Enter - Start-Sleep -Milliseconds 400 - Send-QemuText -Session $Session -Text 'tester' -Enter - Start-Sleep -Milliseconds 400 - Send-QemuText -Session $Session -Text 'pass123' -Enter - $hit = Wait-QemuLog -Session $Session -Patterns @('[FI] all fields done', '[FI] validation failed') -TimeoutSeconds 30 - if ($hit -eq '[FI] all fields done') { $done = $true } - else { Add-TestNote $result "B1: FirstInit 字段验证失败,重试第 $attempt 次" } - } - if (-not $done) { Add-TestCheck $result 'B1: FirstInit 账户字段填写' 'FAIL' 'validation did not pass after 3 attempts'; return $false } - Add-TestCheck $result 'B1: FirstInit 账户字段填写' 'PASS' - - Start-Sleep -Milliseconds 600 - Send-QemuKeys -Session $Session -Keys @('ret', 'ret', 'ret', 'ret', 'ret') -DelayMs 350 - $hit = Wait-QemuLog -Session $Session -Patterns @('[FirstInit] entering network setup page') -TimeoutSeconds 40 - if (-not $hit) { Add-TestCheck $result 'B1: FirstInit 偏好页' 'FAIL' 'did not reach network setup page'; return $false } - Add-TestCheck $result 'B1: FirstInit 偏好页(5xEnter)' 'PASS' - - Start-Sleep -Milliseconds 400 - Send-QemuKeys -Session $Session -Keys @('ret', 'ret', 'ret') -DelayMs 350 - $hit = Wait-QemuLog -Session $Session -Patterns @('[FirstInit] user setup finished') -TimeoutSeconds 40 - if (-not $hit) { Add-TestCheck $result 'B1: FirstInit 网络页' 'FAIL' 'setup did not finish'; return $false } - Add-TestCheck $result 'B1: FirstInit 网络页(3xEnter)' 'PASS' - return $true -} - -# ---------------------------------------------------------------- -function Invoke-ScenarioB1 { - Write-Host "`n--- B1 首次启动 (firstInit=0) ---" - Set-DeshabFirstInit -First 0 -DevMode 0 - $s = Start-QemuSession -Name 'bootreg-b1' -MonitorPort 45501 - try { - if (-not (Complete-FirstInitWizard -Session $s)) { return } - $hit = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 120 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - Assert-QemuLog $result $log -Ordered @( - '[UTSM] SELFTEST PASS', - '[DSK] boot', - '[DSK] firstInit=0', - '[DSK:SCHED] decision: is_first=1 -> mouseInit -> netman -> FirstInit -> desktop', - '[DSK] loading mouseInit', - '[DSK] loading netman', - '[DSK] loading FirstInit', - '[FirstInit] boot', - '[FirstInit] user setup finished', - '[DSK] FirstInit returned, persisting user.conf', - '[DSK] writing USER.CONF', - '[DSK] firstInit.txt flip rc=', - '[DSK] loading desktop', - 'desktop ready' - ) -MustNotContain @('[PANIC]') - if ($hit) { Add-TestCheck $result 'B1: desktop ready' 'PASS' } - else { Add-TestCheck $result 'B1: desktop ready' 'FAIL' 'timeout waiting desktop ready' } - } finally { - Stop-QemuSession $s - } -} - -function Invoke-ScenarioB2 { - Write-Host "`n--- B2 正常启动 (firstInit=1, AUTOEXEC=ver/exit) ---" - Set-DeshabFirstInit -First 1 -DevMode 0 - Set-DeshabAutoexec -Lines @('ver', 'exit') - $s = Start-QemuSession -Name 'bootreg-b2' -MonitorPort 45501 - try { - $hit = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 150 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - Assert-QemuLog $result $log -Ordered @( - '[UTSM] SELFTEST PASS', - '[UTSM] loading DSK', - '[DSK] boot', - '[DSK] context ok', - '[DSK] firstInit != 0', - '[DSK:SCHED] decision: is_first=0 -> login then desktop', - '[DSK:SCHED] login skipped by FUCK config', - '[DSK:SCHED] default_shell=cmd -> cmd.elf', - '[cmd] boot', - '[cmd] running AUTOEXEC.BAT', - '[DSK] cmd.elf returned, falling back to desktop', - '[DSK:SCHED] loading desktop', - 'desktop ready' - ) -MustNotContain @('[PANIC]') - if ($hit) { Add-TestCheck $result 'B2: desktop ready' 'PASS' } - else { Add-TestCheck $result 'B2: desktop ready' 'FAIL' 'timeout waiting desktop ready' } - } finally { - Stop-QemuSession $s - } -} - -function Invoke-ScenarioB3 { - Write-Host "`n--- B3 dev_mode (firstInit=1\1) ---" - Set-DeshabFirstInit -First 1 -DevMode 1 - Set-DeshabAutoexec -Lines @('ver', 'exit') - $s = Start-QemuSession -Name 'bootreg-b3' -MonitorPort 45501 - try { - $hit = Wait-QemuLog -Session $s -Patterns @('[8] rm TEST.TXT') -TimeoutSeconds 150 - if (-not $hit) { - $result.LogPath = $s.LogPath - Add-TestCheck $result 'B3: shell dev_tests 执行' 'FAIL' 'missing [8] rm TEST.TXT' - return - } - Start-Sleep -Seconds 2 - Send-QemuKeys -Session $s -Keys @('esc') - $hit2 = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 150 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - Assert-QemuLog $result $log -Ordered @( - '[DSK] dev_mode=1 (developer auto-test)', - '[DSK:SCHED] decision: dev_mode -> shell.elf', - '[shell] boot', - '[1] ls', - '[4] cp TEST.TXT COPY.TXT', - '[8] rm TEST.TXT', - '[shell] esc -> return to DSK', - '[DSK] shell auto-test returned', - '[DSK:SCHED] default_shell=cmd -> cmd.elf', - 'desktop ready' - ) -MustNotContain @('[PANIC]') - if ($hit2) { Add-TestCheck $result 'B3: Esc 返回后到 desktop' 'PASS' } - else { Add-TestCheck $result 'B3: Esc 返回后到 desktop' 'FAIL' 'timeout waiting desktop ready' } - } finally { - Stop-QemuSession $s - } -} - -function Invoke-ScenarioB4 { - Write-Host "`n--- B4 无 SATA 盘 (DSK 回退 Limine module) ---" - $s = Start-QemuSession -Name 'bootreg-b4' -NoSata -MonitorPort 45501 - try { - $hit = Wait-QemuLog -Session $s -Patterns @('[DSK] firstInit.txt not found', '[DSK] desktop load failed', '[PANIC]') -TimeoutSeconds 120 - Start-Sleep -Seconds 3 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - Assert-QemuLog $result $log -Ordered @( - '[UTSM] SELFTEST PASS', - '[UTSM] loading DSK', - '[UTSM] FAT32 block path unavailable; trying Limine module', - '[DSK] boot', - '[DSK] context ok', - '[DSK] firstInit.txt not found, assuming first boot' - ) -MustContain @('[DSK] mouseInit not found') -MustNotContain @('[PANIC]') - if ($hit -eq '[PANIC]') { Add-TestCheck $result 'B4: 无盘降级不崩溃' 'FAIL' 'kernel panicked' } - else { Add-TestCheck $result 'B4: 无盘降级不崩溃' 'PASS' } - } finally { - Stop-QemuSession $s - } -} - -function Invoke-ScenarioB5 { - Write-Host "`n--- B5 无 NIC (e1000/virtio_net not found, 继续启动) ---" - Set-DeshabFirstInit -First 1 -DevMode 0 - Set-DeshabAutoexec -Lines @('ver', 'exit') - $s = Start-QemuSession -Name 'bootreg-b5' -NoNet -MonitorPort 45501 - try { - $hit = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 150 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - Assert-QemuLog $result $log -MustContain @( - '[e1000] device not found', - '[virtio_net] virtio-net device not found' - ) -Ordered @( - '[UTSM] SELFTEST PASS', - '[DSK] boot', - '[DSK] firstInit != 0', - 'desktop ready' - ) -MustNotContain @('[PANIC]') - if ($hit) { Add-TestCheck $result 'B5: 无 NIC 到 desktop' 'PASS' } - else { Add-TestCheck $result 'B5: 无 NIC 到 desktop' 'FAIL' 'timeout waiting desktop ready' } - } finally { - Stop-QemuSession $s - } -} - -# ---------------------------------------------------------------- -try { - foreach ($sc in $Scenarios) { - switch ($sc.ToUpper()) { - 'B1' { Invoke-ScenarioB1 } - 'B2' { Invoke-ScenarioB2 } - 'B3' { Invoke-ScenarioB3 } - 'B4' { Invoke-ScenarioB4 } - 'B5' { Invoke-ScenarioB5 } - default { Add-TestNote $result "未知场景 $sc,跳过" } - } - } - Add-TestNote $result 'B6 (Linux 兼容层启用) 由 linux-compat.ps1 覆盖' -} finally { - Restore-DeshabScenario -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/deaicup-e2e.ps1 b/tests/qemu/deaicup-e2e.ps1 deleted file mode 100644 index 8e44fbe..0000000 --- a/tests/qemu/deaicup-e2e.ps1 +++ /dev/null @@ -1,369 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - deaicup-e2e - Deaicup GUI PE end-to-end test (docs/RE/test-cases/pe-compat.md) - -.DESCRIPTION - AUTOEXEC runs `pe DEAICUP.EXE` (Rust/egui original Win32 API PE64 GUI app), - then the script drives the app through the QEMU monitor and asserts on the - serial log plus screendump pixel analysis: - - 1. boot chain: 9-anchor ordered chain - (cmdline -> native run -> mainCRTStartup -> RegisterClassExW -> - CreateWindowExW -> CreateDIBSection -> Context::default -> - BitBlt first frame -> bitblt ok) - 2. screendump pixel analysis of the rendered GUI frame: - not-all-black, distinct color threshold, GUI window rectangle - detection (window body color / count / bounding box / fill ratio) - 3. mouse: AUX init + mouse_move -> mpkt (WM_MOUSEMOVE), - mouse_button -> LDOWN/LUP edges (WM_LBUTTONDOWN/WM_LBUTTONUP), - cursor-region pixel diff between two screendumps - 4. keyboard: sendkey -> KEYDOWN/CHAR/KEYUP (WM_KEYDOWN/WM_CHAR/WM_KEYUP) - 5. Esc -> WM_CLOSE -> ExitProcess(0) -> [cmd] PE run end clean exit chain - (also regresses BUG-006 long-run return chain and BUG-005 noise) - - Forbidden patterns over the whole session: - [PANIC] / [IDT] exception / Unimplemented handler - - WHPX note: native PE32+ execution enables SSE via CR4 and WHPX may kill - QEMU with "Unexpected VP exit code 4" (platform limitation, not a guest - crash). If the first-frame anchor is missed under the requested - accelerator the session is rerun once with -Accel tcg - (pass -Accel tcg to skip WHPX directly). - - NOTE: keep this file ASCII-only. PS 5.1 reads BOM-less UTF-8 as GBK; - CJK comment bytes can swallow newlines and merge code into comments. - - Exit code: 0 = all checks PASS, 1 = any FAIL. -#> -param( - [switch]$Build, - [string]$Accel = 'whpx', - [int]$MonitorPort = 45454 -) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'deaicup-e2e' - -# ================================================================ -# PPM helpers. WARNING: [byte] -shl N truncates back to byte in -# PS 5.1, always cast to [int] before shifting (the scratch -# analyze script lost R/G channels that way). -# ================================================================ -function Read-PpmFile { - param([Parameter(Mandatory)][string]$Path) - $b = [System.IO.File]::ReadAllBytes($Path) - $p = 0 - $tok = @() - while ($tok.Count -lt 4) { - while ($p -lt $b.Length) { - $c = $b[$p] - if ($c -eq 0x23) { while ($p -lt $b.Length -and $b[$p] -ne 0x0A) { $p++ } } - elseif ($c -le 0x20) { $p++ } - else { break } - } - $s = $p - while ($p -lt $b.Length -and $b[$p] -gt 0x20) { $p++ } - $tok += [Text.Encoding]::ASCII.GetString($b, $s, $p - $s) - } - $p++ - if ($tok[0] -ne 'P6') { throw "not a P6 ppm: $($tok[0])" } - return @{ data = $b; off = $p; w = [int]$tok[1]; h = [int]$tok[2] } -} - -# Pass 1: histogram + non-black count. Pass 2: window rectangle. -# Window body color = most common color with mid-dark luminance -# (excludes black console background and bright text/cursor/highlight). -function Get-PpmStats { - param([Parameter(Mandatory)]$F) - $npix = $F.w * $F.h - $colors = @{} - $nonBlack = 0 - for ($i = 0; $i -lt $npix; $i++) { - $o = $F.off + $i * 3 - $r = [int]$F.data[$o]; $g = [int]$F.data[$o + 1]; $b = [int]$F.data[$o + 2] - $key = ($r -shl 16) -bor ($g -shl 8) -bor $b - if ($colors.ContainsKey($key)) { $colors[$key]++ } else { $colors[$key] = 1 } - if ($r -ge 8 -or $g -ge 8 -or $b -ge 8) { $nonBlack++ } - } - $winKey = -1; $winCount = 0 - foreach ($e in ($colors.GetEnumerator() | Sort-Object Value -Descending)) { - $k = [int]$e.Key - $r = ($k -shr 16) -band 0xFF; $g = ($k -shr 8) -band 0xFF; $b = $k -band 0xFF - $lum = 0.299 * $r + 0.587 * $g + 0.114 * $b - if ($lum -ge 15 -and $lum -le 90) { $winKey = $k; $winCount = [int]$e.Value; break } - } - # Window rectangle: the GUI window body is nearly solid window-color, while - # anti-aliased console text rows only contain a sparse sprinkle of it. - # So: per-row window-color count -> longest contiguous run of "solid" rows - # (>= 50% of the max row count) -> x extent + fill ratio inside that run. - $box = $null - if ($winKey -ge 0) { - $rowCnt = New-Object int[] $F.h - $rowFirst = New-Object int[] $F.h - $rowLast = New-Object int[] $F.h - for ($y = 0; $y -lt $F.h; $y++) { - $rowOff = $F.off + $y * $F.w * 3 - $cnt = 0; $fx = -1; $lx = -1 - for ($x = 0; $x -lt $F.w; $x++) { - $o = $rowOff + $x * 3 - $key = ([int]$F.data[$o] -shl 16) -bor ([int]$F.data[$o + 1] -shl 8) -bor [int]$F.data[$o + 2] - if ($key -eq $winKey) { - $cnt++ - if ($fx -lt 0) { $fx = $x } - $lx = $x - } - } - $rowCnt[$y] = $cnt; $rowFirst[$y] = $fx; $rowLast[$y] = $lx - } - $maxRow = 0 - for ($y = 0; $y -lt $F.h; $y++) { if ($rowCnt[$y] -gt $maxRow) { $maxRow = $rowCnt[$y] } } - $rowThresh = [int]($maxRow * 0.5) - $bestY0 = -1; $bestY1 = -1; $curY0 = -1 - for ($y = 0; $y -le $F.h; $y++) { - $inRun = ($y -lt $F.h) -and ($rowCnt[$y] -ge $rowThresh) - if ($inRun -and $curY0 -lt 0) { $curY0 = $y } - if (-not $inRun -and $curY0 -ge 0) { - if (($y - 1 - $curY0) -gt ($bestY1 - $bestY0)) { $bestY0 = $curY0; $bestY1 = $y - 1 } - $curY0 = -1 - } - } - if ($bestY0 -ge 0) { - $minX = $F.w; $maxX = -1; $inside = 0 - for ($y = $bestY0; $y -le $bestY1; $y++) { - if ($rowCnt[$y] -gt 0) { - if ($rowFirst[$y] -lt $minX) { $minX = $rowFirst[$y] } - if ($rowLast[$y] -gt $maxX) { $maxX = $rowLast[$y] } - $inside += $rowCnt[$y] - } - } - $bw = $maxX - $minX + 1; $bh = $bestY1 - $bestY0 + 1 - $box = @{ X0 = $minX; Y0 = $bestY0; X1 = $maxX; Y1 = $bestY1; W = $bw; H = $bh - Fill = [math]::Round($inside / [double]($bw * $bh), 4) } - } - } - return @{ Colors = $colors.Count; NonBlack = $nonBlack; Pixels = $npix - WinKey = $winKey; WinCount = $winCount; WinBox = $box } -} - -# Sampled diff (every 2nd pixel) between two frames over a region. -# A pixel counts as changed when |dR|+|dG|+|dB| > 30. -function Compare-PpmRegion { - param([Parameter(Mandatory)]$A, [Parameter(Mandatory)]$B, - [int]$X0, [int]$Y0, [int]$X1, [int]$Y1) - $changed = 0 - for ($y = $Y0; $y -le $Y1; $y += 2) { - $rowA = $A.off + $y * $A.w * 3 - $rowB = $B.off + $y * $B.w * 3 - for ($x = $X0; $x -le $X1; $x += 2) { - $oa = $rowA + $x * 3 - $ob = $rowB + $x * 3 - $d = [Math]::Abs([int]$A.data[$oa] - [int]$B.data[$ob]) + - [Math]::Abs([int]$A.data[$oa + 1] - [int]$B.data[$ob + 1]) + - [Math]::Abs([int]$A.data[$oa + 2] - [int]$B.data[$ob + 2]) - if ($d -gt 30) { $changed++ } - } - } - return $changed -} - -function Convert-PpmToPng { - param([Parameter(Mandatory)][string]$PpmPath, [Parameter(Mandatory)][string]$PngPath) - Add-Type -AssemblyName System.Drawing - $bytes = [System.IO.File]::ReadAllBytes($PpmPath) - # ASCII decode: 1 byte == 1 char, so string index == byte offset. - # QEMU screendump header is "P6\n \n255\n" + single whitespace byte. - $hdr = [System.Text.Encoding]::ASCII.GetString($bytes, 0, [Math]::Min(64, $bytes.Length)) - $m = [regex]::Match($hdr, '^P6\s+(\d+)\s+(\d+)\s+(\d+)\s') - if (-not $m.Success) { throw 'bad ppm header' } - $w = [int]$m.Groups[1].Value; $h = [int]$m.Groups[2].Value; $max = [int]$m.Groups[3].Value - if ($max -ne 255) { throw "unsupported maxval $max" } - $pos = $m.Length - $bmp = New-Object System.Drawing.Bitmap $w, $h, ([System.Drawing.Imaging.PixelFormat]::Format24bppRgb) - $rect = New-Object System.Drawing.Rectangle 0, 0, $w, $h - $data = $bmp.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::WriteOnly, $bmp.PixelFormat) - try { - $rowBytes = $w * 3 - $rgb = New-Object byte[] ($rowBytes) - for ($y = 0; $y -lt $h; $y++) { - $srcOff = $pos + $y * $rowBytes - for ($x = 0; $x -lt $w; $x++) { - $rgb[$x*3+0] = $bytes[$srcOff + $x*3 + 2] # B - $rgb[$x*3+1] = $bytes[$srcOff + $x*3 + 1] # G - $rgb[$x*3+2] = $bytes[$srcOff + $x*3 + 0] # R - } - [System.Runtime.InteropServices.Marshal]::Copy($rgb, 0, [IntPtr]($data.Scan0.ToInt64() + $y * $data.Stride), $rowBytes) - } - } finally { $bmp.UnlockBits($data) } - $bmp.Save($PngPath, [System.Drawing.Imaging.ImageFormat]::Png) - $bmp.Dispose() -} - -$script:UsedAccel = $Accel -$s = $null -try { - Set-DeshabFirstInit -First 1 -DevMode 0 - Set-DeshabAutoexec -Lines @('pe DEAICUP.EXE') - - $s = Start-QemuSession -Name "deaicup-$Accel" -Accel $Accel -MonitorPort $MonitorPort - $result.LogPath = $s.LogPath - - # ---- 1. boot chain + first frame (WHPX miss -> TCG rerun once) ---- - $waitFirst = 150 - if ($Accel -eq 'tcg') { $waitFirst = 360 } # TCG full emulation is much slower - $hit = Wait-QemuLog -Session $s -Patterns @('[shim] BitBlt first frame on screen', '[PANIC]', '[IDT] exception') -TimeoutSeconds $waitFirst - - if ($hit -ne '[shim] BitBlt first frame on screen' -and $Accel -eq 'whpx') { - $why = 'first-frame anchor missed under WHPX' - $early = Read-QemuLogText $s.LogPath - if ($early.Contains('WHPX: Unexpected VP exit')) { $why = 'WHPX terminated QEMU at native PE execution (Unexpected VP exit, platform limitation)' } - Add-TestNote $result "$why; rerunning once with TCG" - Stop-QemuSession $s - $s = $null - $script:UsedAccel = 'tcg' - $s = Start-QemuSession -Name 'deaicup-tcg' -Accel 'tcg' -MonitorPort $MonitorPort - $result.LogPath = $s.LogPath - $hit = Wait-QemuLog -Session $s -Patterns @('[shim] BitBlt first frame on screen', '[PANIC]', '[IDT] exception') -TimeoutSeconds 360 - } - - $log = Read-QemuLogText $s.LogPath - if ($hit -ne '[shim] BitBlt first frame on screen') { - Add-TestCheck $result 'first frame: BitBlt first frame on screen' 'FAIL' "hit=$hit" - throw 'first-frame anchor missing' - } - Add-TestCheck $result 'first frame: BitBlt first frame on screen' 'PASS' - Assert-QemuLog $result $log -Ordered @( - '[cmd] cmdline: DEAICUP.EXE', - '[PE] running PE32+ natively', - '[deaicup] mainCRTStartup', - '[shim] RegisterClassExW ok', - '[shim] CreateWindowExW ok', - '[shim] CreateDIBSection ok', - '[dc] Context::default ok', - '[shim] BitBlt first frame on screen', - '[dc] bitblt ok' - ) -MustNotContain @('[PANIC]', '[IDT] exception', 'Unimplemented handler') - - # ---- 2. screendump A: pixel analysis of the rendered GUI frame ---- - # QEMU human monitor parses backslashes as escapes inside quoted strings - # (\C etc. illegal -> screendump silently fails); OutPath must use '/'. - Start-Sleep -Seconds 2 - $ppmA = ($script:TestWorkDir -replace '\\', '/') + '/deaicup-frame-a.ppm' - $ppmB = ($script:TestWorkDir -replace '\\', '/') + '/deaicup-frame-b.ppm' - $png = Join-Path $script:TestWorkDir 'deaicup-frame.png' - $fa = $null - $statsA = $null - if (Get-QemuScreenshot -Session $s -OutPath $ppmA) { - try { - Convert-PpmToPng -PpmPath $ppmA -PngPath $png - Add-TestCheck $result "screenshot: evidence frame -> $png" 'PASS' - } catch { - Add-TestCheck $result 'screenshot: PPM->PNG convert' 'FAIL' $_.Exception.Message - } - $fa = Read-PpmFile $ppmA - $statsA = Get-PpmStats $fa - $boxDesc = 'none' - if ($statsA.WinBox) { - $boxDesc = ("({0},{1})-({2},{3}) {4}x{5} fill={6:P1}" -f $statsA.WinBox.X0, $statsA.WinBox.Y0, - $statsA.WinBox.X1, $statsA.WinBox.Y1, $statsA.WinBox.W, $statsA.WinBox.H, $statsA.WinBox.Fill) - } - Add-TestNote $result ("frame {0}x{1}: distinctColors={2} nonBlack={3}/{4} winColor=#{5:X6} winCount={6} winBox={7}" -f - $fa.w, $fa.h, $statsA.Colors, $statsA.NonBlack, $statsA.Pixels, $statsA.WinKey, $statsA.WinCount, $boxDesc) - - if ($statsA.NonBlack -ge 100000) { - Add-TestCheck $result "pixels: not all black (nonBlack=$($statsA.NonBlack) >= 100000)" 'PASS' - } else { - Add-TestCheck $result 'pixels: not all black' 'FAIL' "nonBlack=$($statsA.NonBlack) (< 100000)" - } - if ($statsA.Colors -ge 32) { - Add-TestCheck $result "pixels: distinct colors >= 32 ($($statsA.Colors))" 'PASS' - } else { - Add-TestCheck $result 'pixels: distinct colors >= 32' 'FAIL' "only $($statsA.Colors)" - } - $wb = $statsA.WinBox - if ($statsA.WinKey -ge 0 -and $statsA.WinCount -ge 300000 -and $wb -and - $wb.W -ge 600 -and $wb.W -le 900 -and $wb.H -ge 450 -and $wb.H -le 700 -and $wb.Fill -ge 0.75) { - Add-TestCheck $result "pixels: GUI window rect ($($wb.W)x$($wb.H) fill=$($wb.Fill.ToString('P1')))" 'PASS' - } else { - Add-TestCheck $result 'pixels: GUI window rect' 'FAIL' ("winKey=#{0:X6} count={1} box={2}" -f $statsA.WinKey, $statsA.WinCount, $boxDesc) - } - } else { - Add-TestCheck $result 'screenshot: screendump A' 'FAIL' 'ppm not produced' - } - - # ---- 3. mouse: mouse_move -> WM_MOUSEMOVE (mpkt) + cursor pixel diff ---- - $mouseInitOk = $log.Contains('[shim] mouse init ok (AUX streaming, polled)') - Send-QemuMouseMove -Session $s -Dx 120 -Dy 60 - Start-Sleep -Milliseconds 700 - Send-QemuMouseMove -Session $s -Dx 40 -Dy 20 - $hitMv = Wait-QemuLog -Session $s -Patterns @('[shim] mpkt dx=', '[PANIC]', '[IDT] exception') -TimeoutSeconds 60 - if ($mouseInitOk -and $hitMv -eq '[shim] mpkt dx=') { - Add-TestCheck $result 'mouse: AUX init + mouse_move -> WM_MOUSEMOVE (mpkt)' 'PASS' - } else { - Add-TestCheck $result 'mouse: AUX init + mouse_move -> WM_MOUSEMOVE (mpkt)' 'FAIL' "init=$mouseInitOk hit=$hitMv" - } - - Start-Sleep -Seconds 2 - $cursorDiff = -1 - if ($fa -and (Get-QemuScreenshot -Session $s -OutPath $ppmB)) { - $fb = Read-PpmFile $ppmB - $x0 = 4; $y0 = 4; $x1 = $fa.w - 5; $y1 = $fa.h - 5 - if ($statsA.WinBox) { - $x0 = $statsA.WinBox.X0 + 4; $y0 = $statsA.WinBox.Y0 + 4 - $x1 = $statsA.WinBox.X1 - 4; $y1 = $statsA.WinBox.Y1 - 4 - } - $cursorDiff = Compare-PpmRegion -A $fa -B $fb -X0 $x0 -Y0 $y0 -X1 $x1 -Y1 $y1 - Add-TestNote $result "cursor-region diff (sampled, region ($x0,$y0)-($x1,$y1)): $cursorDiff px" - } - if ($cursorDiff -ge 8 -and $cursorDiff -le 60000) { - Add-TestCheck $result "mouse: cursor-region pixel diff after move ($cursorDiff px)" 'PASS' - } else { - Add-TestCheck $result 'mouse: cursor-region pixel diff after move' 'FAIL' "diff=$cursorDiff (expect 8..60000)" - } - - # ---- 4. mouse buttons: mouse_button -> WM_LBUTTONDOWN/UP edges ---- - [void](Invoke-QemuMonitor -Session $s -Command 'mouse_button 1' -SettleMs 500) - [void](Invoke-QemuMonitor -Session $s -Command 'mouse_button 0' -SettleMs 500) - $hitUp = Wait-QemuLog -Session $s -Patterns @('[shim] LUP', '[PANIC]', '[IDT] exception') -TimeoutSeconds 60 - $log = Read-QemuLogText $s.LogPath - if ($hitUp -eq '[shim] LUP' -and $log.Contains('[shim] LDOWN lp=')) { - Add-TestCheck $result 'mouse: mouse_button -> WM_LBUTTONDOWN/UP edges' 'PASS' - } else { - Add-TestCheck $result 'mouse: mouse_button -> WM_LBUTTONDOWN/UP edges' 'FAIL' "hit=$hitUp ldown=$($log.Contains('[shim] LDOWN lp='))" - } - - # ---- 5. keyboard: sendkey -> WM_KEYDOWN / WM_CHAR / WM_KEYUP ---- - Send-QemuKeys -Session $s -Keys @('a') - $hitK = Wait-QemuLog -Session $s -Patterns @('[shim] CHAR ch=', '[PANIC]', '[IDT] exception') -TimeoutSeconds 60 - $log = Read-QemuLogText $s.LogPath - if ($hitK -eq '[shim] CHAR ch=' -and $log.Contains('[shim] KEYDOWN vk=') -and $log.Contains('[shim] KEYUP vk=')) { - Add-TestCheck $result 'keyboard: sendkey -> WM_KEYDOWN/WM_CHAR/WM_KEYUP' 'PASS' - } else { - Add-TestCheck $result 'keyboard: sendkey -> WM_KEYDOWN/WM_CHAR/WM_KEYUP' 'FAIL' "hit=$hitK" - } - - # ---- 6. Esc -> WM_CLOSE -> ExitProcess(0) -> cmd continues ---- - Send-QemuKeys -Session $s -Keys @('esc') - $hitQ = Wait-QemuLog -Session $s -Patterns @('[cmd] PE run end', '[PANIC]', '[IDT] exception') -TimeoutSeconds 90 - $log = Read-QemuLogText $s.LogPath - if ($hitQ -eq '[cmd] PE run end') { - Add-TestCheck $result 'exit chain: Esc -> WM_CLOSE -> ExitProcess(0) -> cmd continues' 'PASS' - } else { - Add-TestCheck $result 'exit chain: Esc -> WM_CLOSE -> ExitProcess(0) -> cmd continues' 'FAIL' "hit=$hitQ" - } - Assert-QemuLog $result $log -Ordered @( - '[deaicup] quit, ExitProcess(0)', - '[PE] ExitProcess code=', - '[cmd] PE run end' - ) -MustNotContain @('[PANIC]', '[IDT] exception', 'Unimplemented handler') - - Add-TestNote $result "accelerator: $script:UsedAccel" -} finally { - if ($s) { Stop-QemuSession $s } - Restore-DeshabScenario -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/driver-matrix.ps1 b/tests/qemu/driver-matrix.ps1 deleted file mode 100644 index 23ca606..0000000 --- a/tests/qemu/driver-matrix.ps1 +++ /dev/null @@ -1,100 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - driver-matrix — DKM 驱动功能矩阵(docs/RE/test-cases/driver-matrix.md) - -.DESCRIPTION - 单次 QEMU 会话挂全设备(SATA 测试盘 + NVMe 测试盘 + xHCI USB 盘 + e1000), - 验证 4 个 stage 共 16 个清单驱动的加载顺序与功能锚点: - - stage0: timer / apic / acpi / pci - stage1: console_fb / ahci / nvme / xhci / bootfs - stage2: vfs / fat32 / devfs - stage3: e1000 / virtio_net(无设备,降级) / ath9k(FUCK 禁用) / ps2kbd - - 已知限制(与文档一致): - - virtio_net 在无 virtio-net 设备时走 not-found 降级路径(断言降级锚点) - - ath9k 被 FUCK [drivers] ath9k=0 禁用(断言 DSM 禁用锚点;QEMU 无 WiFi 设备) - - xhci USB MSC 枚举在当前实现下可能失败,仅断言驱动就绪 - - 退出码:全部通过 0,任一 FAIL 1。 -#> -param([switch]$Build) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'driver-matrix' - -Write-Host "`n--- 全设备单会话驱动矩阵 ---" -$s = Start-QemuSession -Name 'driver-matrix' -MonitorPort 45502 -try { - $hit = Wait-QemuLog -Session $s -Patterns @('[DSK] boot') -TimeoutSeconds 150 - Start-Sleep -Seconds 2 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - - # ---- stage 加载顺序(driver ready 锚点按 manifest 顺序) ---- - Assert-QemuLog $result $log -Ordered @( - '[timer] driver ready', - '[apic] driver ready', - '[acpi] driver ready', - '[pci] driver ready', - '[console_fb] ready', - '[ahci] driver ready', - '[nvme] driver ready', - '[xhci] driver ready', - '[bootfs] driver ready', - '[vfs] driver ready', - '[fat32] driver ready', - '[devfs] driver ready', - '[e1000] driver ready', - '[virtio_net] driver ready', - '[kbd] driver ready', - '[UTSM] SELFTEST PASS' - ) - - # ---- 功能验证点 ---- - Assert-QemuLog $result $log -MustContain @( - '[ahci] block provider index=', # ahci: ahci0 block provider 注册 - '[nvme] block provider index=', # nvme: block provider 注册 - '[fat32] using block provider image', # fat32: 经块设备解析测试镜像 - '[e1000] netdev registered', # e1000: 注册 netdev - '[e1000] link=up', # e1000: 链路就绪 - '[kbd] IRQ1 registered', # ps2kbd: IRQ1 注册 - '[virtio_net] virtio-net device not found', # virtio_net: e1000-only 拓扑降级 - '[DSM] driver disabled by FUCK' # ath9k: FUCK 禁用路径 - ) - - # ---- NVMe 数据面回归(写路径):签名盘自检 + 写/刷/读回 + provider 序号 ---- - Assert-QemuLog $result $log -MustContain @( - '[nvme] LBA0 signature OK', # LBA0 DESHABNVME0 签名识别(测试盘) - '[nvme] write+flush+readback verify OK', # 写→FLUSH→读回逐字节校验(PRP2 + PRP list 两趟) - '[nvme] LBA0 sig intact after write tests' # 写测试后 LBA0 签名未损坏 - ) - # ahci0=0 → nvme0=1 注册顺序([BLOCK] device registered → 名称 → index 三联行) - Assert-QemuLog $result $log -Ordered @( - '[INFO] ahci0', - '[BLOCK] index=0x0000000000000000', - '[INFO] nvme0', - '[BLOCK] index=0x0000000000000001' - ) - Assert-QemuLog $result $log -CountPattern '[nvme] write+flush+readback verify OK' -MinCount 2 - - # ---- DSM 装载统计:16 个 manifest 驱动中 15 个 active(ath9k 禁用) ---- - Assert-QemuLog $result $log -CountPattern '[DKM] external driver active' -MinCount 15 - - # ---- 4 个 stage 均完成 ---- - Assert-QemuLog $result $log -CountPattern '[DSM] stage end' -MinCount 4 - - if ($hit) { Add-TestCheck $result '系统到达 DSK' 'PASS' } - else { Add-TestCheck $result '系统到达 DSK' 'FAIL' 'timeout waiting [DSK] boot' } - - Add-TestNote $result 'ath9k: QEMU 无 WiFi 设备模拟,FUCK ath9k=0,走 DSM 禁用路径(文档第 14 行 ⏭)' - Add-TestNote $result 'xhci: USB MSC 枚举当前可能失败(已知限制),仅断言 [xhci] driver ready' -} finally { - Stop-QemuSession $s -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/lib/QemuTest.ps1 b/tests/qemu/lib/QemuTest.ps1 deleted file mode 100644 index 4c0be1f..0000000 --- a/tests/qemu/lib/QemuTest.ps1 +++ /dev/null @@ -1,861 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - Deshab QEMU 自动化测试公共库(tests/qemu/lib/QemuTest.ps1) - -.DESCRIPTION - 提供: - - QEMU 会话管理(启动 / 计时杀进程 / 串口日志收集) - - 日志断言引擎(必须匹配 / 禁止匹配 / 顺序匹配 / 计数匹配) - - QEMU monitor TCP 客户端(sendkey / mouse_move / screendump) - - 场景准备(firstInit.txt 与 AUTOEXEC.BAT 备份-替换-恢复 + SATA 镜像重建) - - 用例结果报告(PASS/FAIL/MANUAL/SKIP,失败时输出日志尾部上下文) - - 兼容 PowerShell 5.1。所有临时文件写入 .build_tmp\tests\。 -#> - -# ---------------- 路径 ---------------- -$script:RepoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..')) -$script:BuildTmp = Join-Path $script:RepoRoot '.build_tmp' -$script:TestWorkDir = Join-Path $script:BuildTmp 'tests' -$script:IsoDir = Join-Path $script:RepoRoot 'ISO' -$script:SystemDir = Join-Path $script:RepoRoot 'SYSTEM' -# 并发隔离布局(另一代理并发跑 QEMU/build,共享路径一律只读,写入全进私有区): -# .build_tmp\tests\img\ 私有镜像副本(deshab/nvme/usb,QEMU 只挂副本) -# .build_tmp\tests\SYSTEM\ SYSTEM 镜像树(robocopy /MIR;场景文件只翻这里) -# .build_tmp\tests\CODE\...\deaicup.exe mkfat32 引用的单文件副本 -# .build_tmp\tests\fat32gen\ mkfat32.ps1 副本 + 私有 SATA 镜像输出 -$script:SuiteImgDir = Join-Path $script:TestWorkDir 'img' -$script:MkFat32Dir = Join-Path $script:TestWorkDir 'fat32gen' -$script:MirrorSystem = Join-Path $script:TestWorkDir 'SYSTEM' -$script:Config = $null - -# 场景备份(模块级,保证只备份一次、恢复幂等) -$script:FirstInitBackup = $null -$script:AutoexecBackup = $null -$script:FuckBackup = $null - -# ================================================================ -# 环境初始化 -# ================================================================ - -function Initialize-QemuTest { - param([switch]$Build) - - # --- 定位 QEMU --- - $qemu = $null - $cmd = Get-Command 'qemu-system-x86_64' -ErrorAction SilentlyContinue - if ($cmd) { $qemu = $cmd.Source } - foreach ($c in @('C:\Program Files\qemu\qemu-system-x86_64.exe', - 'C:\Program Files (x86)\qemu\qemu-system-x86_64.exe', - 'C:\msys64\mingw64\bin\qemu-system-x86_64.exe', - 'C:\msys64\ucrt64\bin\qemu-system-x86_64.exe')) { - if (-not $qemu -and (Test-Path $c)) { $qemu = $c } - } - if (-not $qemu) { throw 'qemu-system-x86_64 not found (install QEMU or add to PATH)' } - - # --- 定位 OVMF --- - $ovmf = $null - foreach ($c in @('C:\Program Files\qemu\share\edk2-x86_64-code.fd', - 'C:\Program Files\qemu\share\OVMF_CODE.fd', - 'C:\msys64\mingw64\share\edk2\ovmf\OVMF_CODE.fd', - 'C:\msys64\ucrt64\share\edk2\ovmf\OVMF_CODE.fd')) { - if (Test-Path $c) { $ovmf = $c; break } - } - if (-not $ovmf) { throw 'OVMF UEFI firmware not found under QEMU install directory' } - - New-Item -ItemType Directory -Force -Path $script:TestWorkDir | Out-Null - New-Item -ItemType Directory -Force -Path $script:SuiteImgDir | Out-Null - New-Item -ItemType Directory -Force -Path $script:MkFat32Dir | Out-Null - - $imgSrc = Join-Path $script:IsoDir 'deshab-dev.img' - $nvmeSrc = Join-Path $script:BuildTmp 'nvme_test.img' - $usbSrc = Join-Path $script:BuildTmp 'usb_test.img' - - if ($Build -or -not (Test-Path $imgSrc)) { - Write-Host '[init] building images via build.ps1 ...' - Stop-ResidualQemu - & powershell -ExecutionPolicy Bypass -File (Join-Path $script:RepoRoot 'build.ps1') | Out-Null - if ($LASTEXITCODE -ne 0) { throw "build.ps1 failed with exit code $LASTEXITCODE" } - } - if (-not (Test-Path $imgSrc)) { throw "image missing: $imgSrc (run with -Build)" } - - # --- 并发隔离:SYSTEM 镜像树 + 私有 SATA 生成器 + 私有镜像副本 --- - Sync-SuiteTree - Invoke-MkFat32 - $sata = Join-Path $script:MkFat32Dir 'sata_fat32_dsk.img' - if (-not (Test-Path $sata)) { throw "private sata image not generated: $sata" } - - $img = Copy-SuiteImage -Source $imgSrc -FileName 'deshab.img' -Mandatory - $nvme = Copy-SuiteImage -Source $nvmeSrc -FileName 'nvme_test.img' - $usb = Copy-SuiteImage -Source $usbSrc -FileName 'usb_test.img' - - $script:Config = [pscustomobject]@{ - QemuExe = $qemu - Ovmf = $ovmf - Img = $img - SataImg = $sata - NvmeImg = $nvme - UsbImg = $usb - } - Write-Host "[init] QEMU: $qemu" - Write-Host "[init] OVMF: $ovmf" - Write-Host "[init] IMG: $img (private copy)" - return $script:Config -} - -# ================================================================ -# 并发隔离:共享文件只读复制到套件私有区 -# ================================================================ - -# share-tolerant 复制:源可能被并发代理的 QEMU/打包器短暂占用,重试至多 60s。 -# 目标不变(mtime+size 相同)时跳过,避免每用例重复拷 768MB。 -function Copy-SuiteImage { - param( - [Parameter(Mandatory)][string]$Source, - [Parameter(Mandatory)][string]$FileName, - [switch]$Mandatory - ) - if (-not (Test-Path $Source)) { - if ($Mandatory) { throw "image missing: $Source (run with -Build)" } - return $null - } - $dest = Join-Path $script:SuiteImgDir $FileName - $srcItem = Get-Item $Source - if ((Test-Path $dest)) { - $dstItem = Get-Item $dest - if ($dstItem.Length -eq $srcItem.Length -and $dstItem.LastWriteTimeUtc -eq $srcItem.LastWriteTimeUtc) { - return $dest - } - } - $deadline = (Get-Date).AddSeconds(60) - while ($true) { - $in = $null; $out = $null - try { - $in = [System.IO.File]::Open($Source, 'Open', 'Read', [System.IO.FileShare]::ReadWrite -bor [System.IO.FileShare]::Delete) - $out = [System.IO.File]::Open($dest, 'Create', 'Write', [System.IO.FileShare]::None) - $in.CopyTo($out, 1048576) - $out.Close(); $in.Close() - [System.IO.File]::SetLastWriteTimeUtc($dest, $srcItem.LastWriteTimeUtc) - Write-Host "[init] private copy: $FileName ($([math]::Round($srcItem.Length/1MB))MB)" - return $dest - } catch [System.IO.IOException] { - if ($out) { try { $out.Close() } catch {} } - if ($in) { try { $in.Close() } catch {} } - if ((Get-Date) -gt $deadline) { - throw "cannot copy $Source (locked by concurrent process for >60s)" - } - Start-Sleep -Seconds 2 - } catch { - if ($out) { try { $out.Close() } catch {} } - if ($in) { try { $in.Close() } catch {} } - throw - } - } -} - -# SYSTEM 镜像树 + deaicup.exe 单文件 + mkfat32.ps1 生成器副本。 -# 场景翻转(firstInit/AUTOEXEC)只作用于镜像树,共享工作树绝不写入。 -function Sync-SuiteTree { - & robocopy $script:SystemDir $script:MirrorSystem /MIR /R:2 /W:1 /NFL /NDL /NJH /NJS /NP | Out-Null - if ($LASTEXITCODE -ge 8) { throw "robocopy SYSTEM mirror failed (rc=$LASTEXITCODE)" } - - $deaSrc = Join-Path $script:RepoRoot 'CODE\Deaicup Software\pe\target\x86_64-pc-windows-msvc\release\deaicup.exe' - if (Test-Path $deaSrc) { - $deaDst = Join-Path $script:TestWorkDir 'CODE\Deaicup Software\pe\target\x86_64-pc-windows-msvc\release\deaicup.exe' - New-Item -ItemType Directory -Force -Path (Split-Path -Parent $deaDst) | Out-Null - $srcItem = Get-Item $deaSrc - $need = $true - if (Test-Path $deaDst) { - $dstItem = Get-Item $deaDst - if ($dstItem.Length -eq $srcItem.Length -and $dstItem.LastWriteTimeUtc -eq $srcItem.LastWriteTimeUtc) { $need = $false } - } - if ($need) { Copy-Item $deaSrc $deaDst -Force } - } - - $mkSrc = Join-Path $script:BuildTmp 'mkfat32.ps1' - if (-not (Test-Path $mkSrc)) { throw "mkfat32.ps1 not found: $mkSrc (run build.ps1 once)" } - Copy-Item $mkSrc (Join-Path $script:MkFat32Dir 'mkfat32.ps1') -Force -} - -# ================================================================ -# 进程管理 -# ================================================================ - -# 清理本套件历史会话残留的 QEMU(Start-QemuSession 以 -name deshabtest-... 标记)。 -# 只按 deshabtest- 标记精确匹配,绝不动其他代理/人工启动的 QEMU(并发隔离要求: -# 另一代理可能正挂着本仓库镜像跑调试,全局或按路径匹配都会误杀)。 -function Stop-ResidualQemu { - $procs = Get-CimInstance Win32_Process -Filter "Name='qemu-system-x86_64.exe'" -ErrorAction SilentlyContinue - foreach ($p in $procs) { - if ($p.CommandLine -and $p.CommandLine.Contains('deshabtest-')) { - Write-Host "[qemu] killing residual suite qemu pid=$($p.ProcessId)" - Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue - } - } -} - -# monitor 端口:45501-45508 按用例分配,deaicup-e2e 固定 45454 -# (避开人工调试 4444 与其他套件 456xx/457xx)。 -# -Preferred 传入用例固定端口;被占用(如 TIME_WAIT)时扫描段内其余端口, -# 最后兜底 45509-45599。段内端口被他人占用时只换端口,绝不杀占用进程。 -function Get-FreeMonitorPort { - param([int]$Preferred = 0) - $candidates = New-Object System.Collections.ArrayList - if (($Preferred -ge 45501 -and $Preferred -le 45508) -or $Preferred -eq 45454) { [void]$candidates.Add($Preferred) } - foreach ($p in 45501..45508) { if ($p -ne $Preferred) { [void]$candidates.Add($p) } } - foreach ($p in 45509..45599) { [void]$candidates.Add($p) } - foreach ($port in $candidates) { - $listener = $null - try { - $listener = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback, $port) - $listener.Start() - $listener.Stop() - return $port - } catch { - if ($listener) { try { $listener.Stop() } catch {} } - } - } - throw 'no free TCP port available for QEMU monitor' -} - -# ================================================================ -# QEMU 会话 -# ================================================================ - -function Start-QemuSession { - param( - [Parameter(Mandatory)][string]$Name, - [string]$Accel = 'whpx', - [switch]$NoSata, - [switch]$NoNvme, - [switch]$NoUsb, - [switch]$NoNet, - [string]$NetModel = 'e1000', - [switch]$NoMonitor, - [int]$MonitorPort = 0, - [switch]$NoSnapshot, - [string]$Cpu = '', - [string[]]$ExtraArgs = @() - ) - if (-not $script:Config) { throw 'call Initialize-QemuTest first' } - $cfg = $script:Config - - $tag = "deshabtest-$Name-" + [guid]::NewGuid().ToString('N').Substring(0, 8) - $stamp = Get-Date -Format 'HHmmss' - $log = Join-Path $script:TestWorkDir "$Name-$stamp.log" - $bat = Join-Path $script:TestWorkDir "$Name-$stamp.bat" - - # WHPX 不支持 nested VMX:默认关闭 guest VMX CPUID(与 ISO/run_qemu.bat 一致)。 - # -Cpu 可显式覆盖(如 'max' 用于探测 nested VMX 可用性)。 - $cpu = 'qemu64' - if ($Accel -eq 'whpx') { $cpu = 'qemu64,-vmx' } - if ($Cpu -ne '') { $cpu = $Cpu } - - $qargs = @( - '-accel', $Accel, - '-machine', 'q35', - '-m', '2G', - '-cpu', $cpu, - '-serial', 'stdio', - '-display', 'none', - '-no-reboot', - '-name', $tag, - '-drive', ('if=pflash,format=raw,readonly=on,file="{0}"' -f $cfg.Ovmf), - # 已知坑4:主盘必须显式 virtio-blk-pci + bootindex=1。 - # `-drive if=virtio` 自动插件时,OVMF 只在 virtio-blk 恰好落到 - # PCI 0x3(无其他 PCI 设备)才会为其自动创建启动项;附加 NVMe/ - # USB/e1000 任一设备后 virtio 挪到 0x4/0x5,OVMF 启动项枚举整个 - # 失效,直接掉 EFI Internal Shell(ESP/BOOTX64.EFI 均正常也没用)。 - # bootindex=1 经 fw_cfg bootorder 强制 OVMF 首选本盘,与设备组合无关。 - '-drive', ('if=none,id=osdisk,format=raw,file="{0}"' -f $cfg.Img), - '-device', 'virtio-blk-pci,drive=osdisk,bootindex=1' - ) - - # -snapshot 默认开:镜像只读 + 写入落临时 overlay,避免镜像写锁与并发代理冲突。 - if (-not $NoSnapshot) { $qargs += '-snapshot' } - - $port = 0 - if (-not $NoMonitor) { - $port = Get-FreeMonitorPort -Preferred $MonitorPort - $qargs += @('-monitor', "tcp:127.0.0.1:$port,server,nowait") - } - if (-not $NoSata) { - $qargs += @('-drive', ('id=sata0,format=raw,file="{0}",if=none' -f $cfg.SataImg), - '-device', 'ide-hd,drive=sata0,bus=ide.0') - } - if (-not $NoNvme -and $cfg.NvmeImg) { - $qargs += @('-drive', ('if=none,id=nvme0,file="{0}",format=raw' -f $cfg.NvmeImg), - '-device', 'nvme,drive=nvme0,serial=deadbeef') - } - if (-not $NoUsb -and $cfg.UsbImg) { - $qargs += @('-device', 'qemu-xhci,id=xhci0', - '-drive', ('id=usb0,if=none,file="{0}",format=raw' -f $cfg.UsbImg), - '-device', 'usb-storage,bus=xhci0.0,drive=usb0') - } - if (-not $NoNet) { - $qargs += @('-netdev', 'user,id=net0') - if ($NetModel -eq 'e1000') { - $qargs += @('-device', 'e1000,netdev=net0,mac=52:54:00:12:34:56') - } elseif ($NetModel -eq 'virtio-net-pci') { - $qargs += @('-device', 'virtio-net-pci,netdev=net0') - } - } else { - # 已知坑3:不显式禁网时 QEMU 会自动补一块默认 e1000 网卡(q35 默认型号), - # -NoNet 场景必须用 -nic none 把默认网络整个关掉。 - $qargs += @('-nic', 'none') - } - foreach ($x in $ExtraArgs) { $qargs += $x } - $qargs += @('-boot', 'menu=on') - - # 已知坑1:Start-Process 下 -serial file: 不生效;用 cmd 批处理重定向 stdio。 - # 已知坑2:-snapshot 会在 %TEMP% 创建临时 overlay,Windows 用户名含非 ASCII - # (如中文)时 QEMU 创建失败;把 TEMP/TMP 重定向到仓库内 ASCII 目录。 - $qemuTmp = Join-Path $script:BuildTmp 'qemu-tmp' - New-Item -ItemType Directory -Force -Path $qemuTmp | Out-Null - $cmdline = '"' + $cfg.QemuExe + '" ' + ($qargs -join ' ') - $batContent = "@echo off`r`nset `"TEMP=$qemuTmp`"`r`nset `"TMP=$qemuTmp`"`r`n$cmdline > `"$log`" 2>&1`r`n" - [System.IO.File]::WriteAllText($bat, $batContent) - - Stop-ResidualQemu - Start-Process -FilePath $bat -WindowStyle Hidden - - $session = [pscustomobject]@{ - Name = $Name - Tag = $tag - BatPath = $bat - LogPath = $log - MonitorPort = $port - Accel = $Accel - StartTime = Get-Date - } - - # 早夭自愈:WHPX 分区被强杀后偶发重建异常,guest 启动即三重故障 - # (实测日志仅一行 "Ignoring request for interrupt vector 0")。 - # 启动后观察 3s:进程已死且日志近乎为空 → 同一 bat 重开一次 - # (tag/monitor 端口不变,旧进程已死无冲突;bat 重定向会重建日志)。 - Start-Sleep -Seconds 3 - if (Test-QemuSessionExited $session) { - $logSize = 0 - if (Test-Path $log) { $logSize = (Get-Item $log).Length } - if ($logSize -lt 4096) { - Write-Host "[qemu] session '$Name' died at launch (log ${logSize}B, WHPX startup flake), relaunching once" - Start-Sleep -Milliseconds 800 - Start-Process -FilePath $bat -WindowStyle Hidden - } else { - Write-Host "[qemu] session '$Name' exited early (log ${logSize}B)" - } - } - - Write-Host "[qemu] session '$Name' started (monitor=$port, log=$log)" - return $session -} - -function Test-QemuSessionExited { - param([Parameter(Mandatory)]$Session) - $procs = Get-CimInstance Win32_Process -Filter "Name='qemu-system-x86_64.exe'" -ErrorAction SilentlyContinue - foreach ($p in $procs) { - if ($p.CommandLine -and $p.CommandLine.Contains($Session.Tag)) { return $false } - } - return $true -} - -function Stop-QemuSession { - param([Parameter(Mandatory)]$Session) - $procs = Get-CimInstance Win32_Process -Filter "Name='qemu-system-x86_64.exe'" -ErrorAction SilentlyContinue - foreach ($p in $procs) { - if ($p.CommandLine -and $p.CommandLine.Contains($Session.Tag)) { - Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue - } - } - Start-Sleep -Milliseconds 500 -} - -# ================================================================ -# 串口日志 -# ================================================================ - -function Read-QemuLogText { - param([Parameter(Mandatory)][string]$Path) - if (-not (Test-Path $Path)) { return '' } - $fs = $null - try { - $fs = [System.IO.File]::Open($Path, 'Open', 'Read', 'ReadWrite') - $sr = New-Object System.IO.StreamReader($fs, [System.Text.Encoding]::UTF8) - $text = $sr.ReadToEnd() - $sr.Close() - return $text - } catch { - return '' - } finally { - if ($fs) { $fs.Close() } - } -} - -# 轮询串口日志直到任一 pattern 出现 / QEMU 退出 / 超时。返回匹配到的 pattern 或 $null。 -function Wait-QemuLog { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)][string[]]$Patterns, - [int]$TimeoutSeconds = 90, - [int]$PollMs = 1000 - ) - $deadline = (Get-Date).AddSeconds($TimeoutSeconds) - while ((Get-Date) -lt $deadline) { - $text = Read-QemuLogText $Session.LogPath - foreach ($p in $Patterns) { - if ($text.Contains($p)) { return $p } - } - if (Test-QemuSessionExited $Session) { - Write-Host '[qemu] session exited unexpectedly' - return $null - } - Start-Sleep -Milliseconds $PollMs - } - Write-Host "[qemu] wait timeout after ${TimeoutSeconds}s (patterns: $($Patterns -join ' | '))" - return $null -} - -# ================================================================ -# QEMU monitor(TCP) -# ================================================================ - -function Invoke-QemuMonitor { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)][string]$Command, - [int]$SettleMs = 250 - ) - if (-not $Session.MonitorPort) { throw 'session started with -NoMonitor' } - $client = New-Object System.Net.Sockets.TcpClient - $client.ReceiveTimeout = 3000 - $client.SendTimeout = 3000 - $client.Connect('127.0.0.1', [int]$Session.MonitorPort) - try { - $stream = $client.GetStream() - $buf = New-Object byte[] 8192 - Start-Sleep -Milliseconds 200 - while ($stream.DataAvailable) { [void]$stream.Read($buf, 0, $buf.Length) } # banner - $writer = New-Object System.IO.StreamWriter($stream) - $writer.AutoFlush = $true - $writer.WriteLine($Command) - Start-Sleep -Milliseconds $SettleMs - $resp = '' - while ($stream.DataAvailable) { - $n = $stream.Read($buf, 0, $buf.Length) - $resp += [System.Text.Encoding]::ASCII.GetString($buf, 0, $n) - } - return $resp - } finally { - $client.Close() - } -} - -function ConvertTo-QemuKeyName { - param([Parameter(Mandatory)][char]$C) - $i = [int]$C - if ($i -ge 97 -and $i -le 122) { return [string]$C } # a-z - if ($i -ge 65 -and $i -le 90) { return 'shift-' + ([string]$C).ToLower() } # A-Z - if ($i -ge 48 -and $i -le 57) { return [string]$C } # 0-9 - switch ($C) { - ' ' { return 'spc' } - '.' { return 'dot' } - ',' { return 'comma' } - '/' { return 'slash' } - '\' { return 'backslash' } - '-' { return 'minus' } - '=' { return 'equal' } - ';' { return 'semicolon' } - ':' { return 'shift-semicolon' } - '>' { return 'shift-dot' } - '<' { return 'shift-comma' } - '_' { return 'shift-minus' } - '!' { return 'shift-1' } - '"' { return 'shift-apostrophe' } - '''' { return 'apostrophe' } - '(' { return 'shift-9' } - ')' { return 'shift-0' } - '?' { return 'shift-slash' } - '*' { return 'shift-8' } - '+' { return 'shift-equal' } - '@' { return 'shift-2' } - '#' { return 'shift-3' } - '$' { return 'shift-4' } - '%' { return 'shift-5' } - '^' { return 'shift-6' } - '&' { return 'shift-7' } - '|' { return 'shift-backslash' } - '~' { return 'shift-grave_accent' } - } - return $null -} - -# 通过 monitor 发送一串按键(单 TCP 连接内完成) -function Send-QemuKeys { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)][string[]]$Keys, - [int]$DelayMs = 120 - ) - if (-not $Session.MonitorPort) { throw 'session started with -NoMonitor' } - $client = New-Object System.Net.Sockets.TcpClient - $client.Connect('127.0.0.1', [int]$Session.MonitorPort) - try { - $stream = $client.GetStream() - $buf = New-Object byte[] 4096 - Start-Sleep -Milliseconds 200 - while ($stream.DataAvailable) { [void]$stream.Read($buf, 0, $buf.Length) } - $writer = New-Object System.IO.StreamWriter($stream) - $writer.AutoFlush = $true - foreach ($k in $Keys) { - $writer.WriteLine("sendkey $k") - Start-Sleep -Milliseconds $DelayMs - } - } finally { - $client.Close() - } -} - -# 输入一段文本(自动映射大小写/符号),可选追加回车 -function Send-QemuText { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)][string]$Text, - [switch]$Enter, - [int]$DelayMs = 90 - ) - $keys = New-Object System.Collections.ArrayList - foreach ($c in $Text.ToCharArray()) { - $k = ConvertTo-QemuKeyName $c - if ($k) { [void]$keys.Add($k) } - } - if ($Enter) { [void]$keys.Add('ret') } - Send-QemuKeys -Session $Session -Keys ($keys.ToArray()) -DelayMs $DelayMs -} - -function Send-QemuMouseMove { - param( - [Parameter(Mandatory)]$Session, - [int]$Dx = 0, - [int]$Dy = 0, - [string]$Button = '' - ) - $cmd = "mouse_move $Dx $Dy" - [void](Invoke-QemuMonitor -Session $Session -Command $cmd) - if ($Button) { - [void](Invoke-QemuMonitor -Session $Session -Command "mouse_button $Button") - } -} - -function Get-QemuScreenshot { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)][string]$OutPath - ) - $resp = Invoke-QemuMonitor -Session $Session -Command "screendump `"$OutPath`"" -SettleMs 600 - return (Test-Path $OutPath) -} - -# ================================================================ -# 场景准备(firstInit.txt / AUTOEXEC.BAT + SATA 镜像重建) -# ================================================================ - -# 私有 SATA 镜像生成:运行 fat32gen\ 下的 mkfat32.ps1 副本。 -# 副本的 PSScriptRoot=fat32gen → 输出 fat32gen\sata_fat32_dsk.img(私有), -# 输入 tests\SYSTEM 镜像树 + tests\CODE\...\deaicup.exe,不读写任何共享路径。 -function Invoke-MkFat32 { - $mk = Join-Path $script:MkFat32Dir 'mkfat32.ps1' - if (-not (Test-Path $mk)) { throw "mkfat32.ps1 copy not found: $mk (Initialize-QemuTest runs Sync-SuiteTree)" } - & powershell -ExecutionPolicy Bypass -File $mk | Out-Null - if ($LASTEXITCODE -ne 0) { throw "mkfat32.ps1 failed with exit code $LASTEXITCODE" } -} - -# 写镜像树 firstInit.txt(格式 "`n",LF)并重建私有 SATA 镜像;原内容自动备份。 -# 只写 .build_tmp\tests\SYSTEM 镜像树,共享 SYSTEM 工作树保持不变(并发代理可能正在翻转它)。 -# 已知坑5:DSK 的 dev_mode 是「FUCK dev_mode OR firstInit 第二行」单向合并, -# firstInit 只能置 1 不能清 0。dev 镜像 FUCK dev_mode=1 时 shell.elf 自动 -# 测试后进入交互主循环阻塞启动流,cmd.elf 永远调度不到。因此 -DevMode 0 -# 必须同步把镜像树 FUCK [boot] dev_mode 也改 0(仅测试镜像树,不动共享树)。 -function Set-DeshabFirstInit { - param( - [Parameter(Mandatory)][int]$First, - [Parameter(Mandatory)][int]$DevMode - ) - $path = Join-Path $script:MirrorSystem 'system\user\use\firstInit.txt' - if ($null -eq $script:FirstInitBackup) { - $script:FirstInitBackup = [System.IO.File]::ReadAllBytes($path) - } - [System.IO.File]::WriteAllText($path, "$First`n$DevMode") - # 同步 FUCK [boot] dev_mode(单向合并导致 firstInit 无法清 dev_mode,见上) - $fuckPath = Join-Path $script:MirrorSystem 'system\deshab64\FUCK' - if ($null -eq $script:FuckBackup) { - $script:FuckBackup = [System.IO.File]::ReadAllBytes($fuckPath) - } - $fuckTxt = [System.IO.File]::ReadAllText($fuckPath) - $fuckTxt = $fuckTxt -replace '(?m)^dev_mode=\d', "dev_mode=$DevMode" - [System.IO.File]::WriteAllText($fuckPath, $fuckTxt) - Invoke-MkFat32 -} - -function Restore-DeshabFirstInit { - $path = Join-Path $script:MirrorSystem 'system\user\use\firstInit.txt' - if ($null -ne $script:FirstInitBackup) { - [System.IO.File]::WriteAllBytes($path, $script:FirstInitBackup) - $script:FirstInitBackup = $null - $restored = $true - } - $fuckPath = Join-Path $script:MirrorSystem 'system\deshab64\FUCK' - if ($null -ne $script:FuckBackup) { - [System.IO.File]::WriteAllBytes($fuckPath, $script:FuckBackup) - $script:FuckBackup = $null - $restored = $true - } - if ($restored) { Invoke-MkFat32 } -} - -# 替换镜像树 AUTOEXEC.BAT 内容(cmd.elf 启动时逐行自动执行)并重建私有 SATA 镜像;原内容自动备份 -function Set-DeshabAutoexec { - param([Parameter(Mandatory)][string[]]$Lines) - $path = Join-Path $script:MirrorSystem 'bin\AUTOEXEC.BAT' - if ($null -eq $script:AutoexecBackup) { - $script:AutoexecBackup = [System.IO.File]::ReadAllBytes($path) - } - [System.IO.File]::WriteAllText($path, (($Lines -join "`r`n") + "`r`n")) - Invoke-MkFat32 -} - -function Restore-DeshabAutoexec { - $path = Join-Path $script:MirrorSystem 'bin\AUTOEXEC.BAT' - if ($null -ne $script:AutoexecBackup) { - [System.IO.File]::WriteAllBytes($path, $script:AutoexecBackup) - $script:AutoexecBackup = $null - Invoke-MkFat32 - } -} - -function Restore-DeshabScenario { - Restore-DeshabFirstInit - Restore-DeshabAutoexec -} - -# ================================================================ -# 结果与断言 -# ================================================================ - -function New-TestCaseResult { - param([Parameter(Mandatory)][string]$Case) - return [pscustomobject]@{ - Case = $Case - Status = 'PASS' - StartTime = Get-Date - DurationSec = 0.0 - LogPath = '' - Checks = New-Object System.Collections.ArrayList - Notes = New-Object System.Collections.ArrayList - } -} - -function Add-TestCheck { - param( - [Parameter(Mandatory)]$Result, - [Parameter(Mandatory)][string]$Name, - [Parameter(Mandatory)][string]$Status, # PASS / FAIL / MANUAL / SKIP - [string]$Detail = '' - ) - [void]$Result.Checks.Add([pscustomobject]@{ Name = $Name; Status = $Status; Detail = $Detail }) - if ($Status -eq 'FAIL') { $Result.Status = 'FAIL' } -} - -function Add-TestNote { - param([Parameter(Mandatory)]$Result, [Parameter(Mandatory)][string]$Note) - [void]$Result.Notes.Add($Note) -} - -# 日志断言引擎:必须包含 / 禁止包含 / 按顺序包含 / 出现次数下限 -function Assert-QemuLog { - param( - [Parameter(Mandatory)]$Result, - [Parameter(Mandatory)][string]$LogText, - [string[]]$MustContain = @(), - [string[]]$MustNotContain = @(), - [string[]]$Ordered = @(), - [string]$CountPattern = '', - [int]$MinCount = 0 - ) - foreach ($p in $MustContain) { - if ($LogText.Contains($p)) { - Add-TestCheck $Result "锚点: $p" 'PASS' - } else { - Add-TestCheck $Result "锚点: $p" 'FAIL' 'pattern not found in serial log' - } - } - foreach ($p in $MustNotContain) { - if ($LogText.Contains($p)) { - Add-TestCheck $Result "禁止: $p" 'FAIL' 'forbidden pattern found in serial log' - } else { - Add-TestCheck $Result "禁止: $p" 'PASS' - } - } - if ($Ordered.Count -gt 0) { - $pos = 0 - $failAt = '' - foreach ($p in $Ordered) { - $idx = $LogText.IndexOf($p, $pos) - if ($idx -lt 0) { $failAt = $p; break } - $pos = $idx + $p.Length - } - if ($failAt -eq '') { - Add-TestCheck $Result ("顺序链({0} 锚点)" -f $Ordered.Count) 'PASS' ($Ordered -join ' -> ') - } else { - Add-TestCheck $Result ("顺序链({0} 锚点)" -f $Ordered.Count) 'FAIL' "missing/out-of-order: $failAt" - } - } - if ($CountPattern -ne '' -and $MinCount -gt 0) { - $count = 0 - $pos = 0 - while ($true) { - $idx = $LogText.IndexOf($CountPattern, $pos) - if ($idx -lt 0) { break } - $count++ - $pos = $idx + $CountPattern.Length - } - if ($count -ge $MinCount) { - Add-TestCheck $Result "计数: '$CountPattern' x$count (>= $MinCount)" 'PASS' - } else { - Add-TestCheck $Result "计数: '$CountPattern' x$count (>= $MinCount)" 'FAIL' "only $count occurrences" - } - } -} - -# 输出用例结果;FAIL 时附带日志尾部上下文 -function Write-TestCaseResult { - param([Parameter(Mandatory)]$Result) - $Result.DurationSec = [math]::Round(((Get-Date) - $Result.StartTime).TotalSeconds, 1) - Write-Host '' - Write-Host ("=== [{0}] {1} ({2}s) ===" -f $Result.Case, $Result.Status, $Result.DurationSec) - foreach ($c in $Result.Checks) { - $mark = '[PASS]' - if ($c.Status -eq 'FAIL') { $mark = '[FAIL] ' } - if ($c.Status -eq 'MANUAL') { $mark = '[MANUAL]' } - if ($c.Status -eq 'SKIP') { $mark = '[SKIP] ' } - Write-Host (" {0} {1}" -f $mark, $c.Name) - if ($c.Detail -and $c.Status -ne 'PASS') { - Write-Host (" -> {0}" -f $c.Detail) - } - } - foreach ($n in $Result.Notes) { Write-Host " [note] $n" } - if ($Result.Status -eq 'FAIL' -and $Result.LogPath -and (Test-Path $Result.LogPath)) { - Write-Host ' --- serial log tail (25 lines) ---' - Get-Content $Result.LogPath -Tail 25 | ForEach-Object { Write-Host " | $_" } - } -} - -# ================================================================ -# FirstInit 设置向导自动填表(首启流程公共助手) -# 账户卡片(3 字段) → 偏好页(5xEnter) → 网络页(3xEnter) → 完成 -# 每步等串口锚点再发键;字段验证失败自动重试 3 次。 -# ================================================================ -function Complete-DeshabFirstInitWizard { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)]$Result, - [string]$Prefix = 'FirstInit' - ) - - $hit = Wait-QemuLog -Session $Session -Patterns @('[FI] fadein done') -TimeoutSeconds 150 - if (-not $hit) { Add-TestCheck $Result "${Prefix}: 账户卡片就绪" 'FAIL' 'missing [FI] fadein done'; return $false } - - $done = $false - for ($attempt = 1; $attempt -le 3 -and -not $done; $attempt++) { - Send-QemuText -Session $Session -Text 'testpc' -Enter - Start-Sleep -Milliseconds 400 - Send-QemuText -Session $Session -Text 'tester' -Enter - Start-Sleep -Milliseconds 400 - Send-QemuText -Session $Session -Text 'pass123' -Enter - $hit = Wait-QemuLog -Session $Session -Patterns @('[FI] all fields done', '[FI] validation failed') -TimeoutSeconds 30 - if ($hit -eq '[FI] all fields done') { $done = $true } - else { Add-TestNote $Result "${Prefix}: 字段验证失败,重试第 $attempt 次" } - } - if (-not $done) { Add-TestCheck $Result "${Prefix}: 账户字段填写" 'FAIL' 'validation did not pass after 3 attempts'; return $false } - Add-TestCheck $Result "${Prefix}: 账户字段填写" 'PASS' - - Start-Sleep -Milliseconds 600 - Send-QemuKeys -Session $Session -Keys @('ret', 'ret', 'ret', 'ret', 'ret') -DelayMs 350 - $hit = Wait-QemuLog -Session $Session -Patterns @('[FirstInit] entering network setup page') -TimeoutSeconds 40 - if (-not $hit) { Add-TestCheck $Result "${Prefix}: 偏好页" 'FAIL' 'did not reach network setup page'; return $false } - Add-TestCheck $Result "${Prefix}: 偏好页(5xEnter)" 'PASS' - - Start-Sleep -Milliseconds 400 - Send-QemuKeys -Session $Session -Keys @('ret', 'ret', 'ret') -DelayMs 350 - $hit = Wait-QemuLog -Session $Session -Patterns @('[FirstInit] user setup finished') -TimeoutSeconds 40 - if (-not $hit) { Add-TestCheck $Result "${Prefix}: 网络页" 'FAIL' 'setup did not finish'; return $false } - Add-TestCheck $Result "${Prefix}: 网络页(3xEnter)" 'PASS' - return $true -} - -# ================================================================ -# 极简 HTTP 测试服务器(network-e2e 用) -# slirp 网关 10.0.2.2 → 主机 127.0.0.1;guest curl 经 slirp 访问主机服务。 -# 后台 Job 接收连接,把每个请求首行写入 $LogPath(主机侧端到端证据), -# 回复固定 HTTP/1.0 200 + 标记体后关闭。 -# ================================================================ -function Start-TestHttpServer { - param( - [int]$Port = 8000, - [Parameter(Mandatory)][string]$LogPath, - [string]$Marker = 'DESHAB-NET-E2E-MARKER' - ) - if (Test-Path $LogPath) { Remove-Item $LogPath -Force } - $job = Start-Job -ScriptBlock { - param($Port, $LogPath, $Marker) - $listener = $null - try { - $listener = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback, $Port) - $listener.Start() - $body = "

$Marker

" - $deadline = (Get-Date).AddMinutes(30) - while ((Get-Date) -lt $deadline) { - if (-not $listener.Pending()) { Start-Sleep -Milliseconds 200; continue } - $client = $listener.AcceptTcpClient() - try { - $stream = $client.GetStream() - $stream.ReadTimeout = 5000 - $buf = New-Object byte[] 4096 - $req = '' - while ($stream.DataAvailable -or $req -eq '') { - $n = 0 - try { $n = $stream.Read($buf, 0, $buf.Length) } catch { break } - if ($n -le 0) { break } - $req += [System.Text.Encoding]::ASCII.GetString($buf, 0, $n) - if ($req.Contains("`r`n`r`n")) { break } - } - $firstLine = ($req -split "`r`n")[0] - [System.IO.File]::AppendAllText($LogPath, "$(Get-Date -Format 'HH:mm:ss') $firstLine`r`n") - $resp = "HTTP/1.0 200 OK`r`nContent-Type: text/html`r`nContent-Length: $($body.Length)`r`nConnection: close`r`n`r`n$body" - $bytes = [System.Text.Encoding]::ASCII.GetBytes($resp) - $stream.Write($bytes, 0, $bytes.Length) - $stream.Flush() - } finally { - $client.Close() - } - } - } finally { - if ($listener) { $listener.Stop() } - } - } -ArgumentList $Port, $LogPath, $Marker - Start-Sleep -Milliseconds 800 - if ($job.State -ne 'Running') { - $err = Receive-Job $job 2>&1 | Out-String - throw "http test server failed to start on port ${Port}: $err" - } - Write-Host "[http] test server listening on 127.0.0.1:$Port (log=$LogPath)" - return $job -} - -function Stop-TestHttpServer { - param($Job) - if ($Job) { - Stop-Job $Job -ErrorAction SilentlyContinue - Remove-Job $Job -Force -ErrorAction SilentlyContinue - } -} diff --git a/tests/qemu/linux-compat.ps1 b/tests/qemu/linux-compat.ps1 deleted file mode 100644 index c62451c..0000000 --- a/tests/qemu/linux-compat.ps1 +++ /dev/null @@ -1,167 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - linux-compat — UTSM+Linux 双内核兼容层端到端(docs/RE/test-cases/linux-compat.md) - -.DESCRIPTION - 动态检测 VMM/Linux guest 可用性,自动分流两条验证路径: - - 路径 A(guest VMX 可用,真机/KVM): - [UTSM] VMM init ok → VMM self-test PASS → bzImage 解析 → IPC shm → - vmlaunch → Linux version → initramfs init → daemon park → - [LNXC] service ready → DSK → shell dev_tests → dsl 命令实测 - - 路径 B(QEMU WHPX/TCG 无 nested VMX —— 本环境预期路径): - [UTSM] VMM unavailable → Linux loader 跳过 → DSK 正常启动不崩溃 → - shell 收到 linux_compat 服务表 → dsl 报告运行态不可用 → desktop。 - A5.1~A5.12 标记 SKIP(环境限制,非产品缺陷)。 - - 依据 ISO/run_qemu.bat 说明:WHPX 下 VMXON 会触发致命 hypervisor exit, - 因此框架默认 -cpu qemu64,-vmx 关闭 guest VMX CPUID,UTSM 走优雅降级。 - 若未来 WHPX/KVM 支持 nested VMX,可用 -Cpu 'max' 复测路径 A。 - - 退出码:全部通过 0,任一 FAIL 1。 -#> -param( - [switch]$Build, - [string]$Cpu = '' -) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'linux-compat' - -Set-DeshabFirstInit -First 1 -DevMode 1 -Set-DeshabAutoexec -Lines @('ver', 'exit') - -$s = Start-QemuSession -Name 'linux-compat' -Cpu $Cpu -MonitorPort 45503 -try { - # ---- 分流点:VMM 自检通过 还是 VMM 不可用 ---- - $fork = Wait-QemuLog -Session $s -Patterns @( - '[UTSM] VMM self-test PASS', - '[UTSM] VMM unavailable' - ) -TimeoutSeconds 180 - - if ($fork -eq '[UTSM] VMM self-test PASS') { - # ================= 路径 A:VMX 可用,完整双内核流程 ================= - Write-Host "`n--- 路径 A: guest VMX 可用,验证完整 Linux 兼容层 ---" - $hit = Wait-QemuLog -Session $s -Patterns @('=== 自动测试完成 ===', '[PANIC]') -TimeoutSeconds 300 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - - # A5.1 ~ A5.7 启动链 - Assert-QemuLog $result $log -Ordered @( - '[UTSM] VMM init ok', - '[UTSM] VMM self-test PASS', - '[LINUX] found bzImage module:', - '[UTSM] Linux loader init ok', - '[UTSM] IPC shm init ok', - '[VMM] launching guest', - 'Linux version', - '[utsm-linux] init started', - '[LINUX] guest parked (daemon ready)', - '[LNXC] service ready', - '[DSK] boot', - '[shell] linux_compat service ok' - ) -MustNotContain @('[PANIC]') - if (-not $hit -or $hit -eq '[PANIC]') { - Add-TestCheck $result 'A5.7: DSK/shell 接管' 'FAIL' 'dev_tests did not complete' - } else { - Add-TestCheck $result 'A5.7: DSK/shell 接管' 'PASS' - } - - # A5.8 ~ A5.12 shell dsl 交互(dev_mode 串口镜像开启) - if ($hit -eq '=== 自动测试完成 ===') { - Start-Sleep -Seconds 1 - Send-QemuText -Session $s -Text 'dsl' -Enter - $h1 = Wait-QemuLog -Session $s -Patterns @('Linux 兼容层已就绪') -TimeoutSeconds 60 - if ($h1) { Add-TestCheck $result 'A5.8: dsl 状态查询' 'PASS' } - else { Add-TestCheck $result 'A5.8: dsl 状态查询' 'FAIL' 'missing 兼容层已就绪' } - - Send-QemuText -Session $s -Text 'dsl ls' -Enter - $h2 = Wait-QemuLog -Session $s -Patterns @('bin', 'etc', 'deshab#') -TimeoutSeconds 60 - if ($h2) { Add-TestCheck $result 'A5.9: dsl ls 目录列表' 'PASS' } - else { Add-TestCheck $result 'A5.9: dsl ls 目录列表' 'FAIL' 'no listing output' } - - Send-QemuText -Session $s -Text 'dsl uname -a' -Enter - $h3 = Wait-QemuLog -Session $s -Patterns @('Linux') -TimeoutSeconds 60 - if ($h3) { Add-TestCheck $result 'A5.10: dsl uname -a' 'PASS' } - else { Add-TestCheck $result 'A5.10: dsl uname -a' 'FAIL' 'no uname output' } - - Send-QemuText -Session $s -Text 'dsl nosuchprog' -Enter - $h4 = Wait-QemuLog -Session $s -Patterns @('deshab# ') -TimeoutSeconds 60 - Start-Sleep -Seconds 1 - $log2 = Read-QemuLogText $s.LogPath - if ($h4 -and -not $log2.Contains('[PANIC]')) { - Add-TestCheck $result 'A5.12: dsl 错误路径不崩溃' 'PASS' - } else { - Add-TestCheck $result 'A5.12: dsl 错误路径不崩溃' 'FAIL' 'shell did not recover' - } - - Send-QemuKeys -Session $s -Keys @('esc') - $h5 = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 150 - if ($h5) { Add-TestCheck $result 'A5.x: Esc 后到 desktop' 'PASS' } - else { Add-TestCheck $result 'A5.x: Esc 后到 desktop' 'FAIL' 'no desktop ready' } - } - Add-TestNote $result '路径 A 执行:guest VMX 可用(真机/KVM 环境)' - } - elseif ($fork -eq '[UTSM] VMM unavailable') { - # ================= 路径 B:无 nested VMX,优雅降级 ================= - Write-Host "`n--- 路径 B: guest 无 VMX(QEMU 预期),验证优雅降级 ---" - $hit = Wait-QemuLog -Session $s -Patterns @('=== 自动测试完成 ===', '[PANIC]') -TimeoutSeconds 240 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - - # 降级锚点 + 系统继续正常启动 - Assert-QemuLog $result $log -MustContain @('[UTSM] VMM unavailable') - Assert-QemuLog $result $log -Ordered @( - '[UTSM] SELFTEST PASS', - '[UTSM] VMM unavailable', - '[DSK] boot', - '[DSK] dev_mode=1 (developer auto-test)', - '[shell] boot', - '[shell] linux_compat service ok' - ) -MustNotContain @('[PANIC]') - if ($hit -eq '=== 自动测试完成 ===') { - Add-TestCheck $result '降级路径: dev_tests 完成(Linux 缺失不阻塞启动)' 'PASS' - } else { - Add-TestCheck $result '降级路径: dev_tests 完成(Linux 缺失不阻塞启动)' 'FAIL' "hit=$hit" - } - - # dsl 运行态不可用报告(服务表已传递,is_available()=0) - if ($hit -eq '=== 自动测试完成 ===') { - Start-Sleep -Seconds 1 - Send-QemuText -Session $s -Text 'dsl' -Enter - $h = Wait-QemuLog -Session $s -Patterns @('linux_compat: not initialized') -TimeoutSeconds 40 - if ($h) { Add-TestCheck $result '降级路径: dsl 报告运行态不可用' 'PASS' } - else { Add-TestCheck $result '降级路径: dsl 报告运行态不可用' 'FAIL' 'missing linux_compat: not initialized' } - - Send-QemuKeys -Session $s -Keys @('esc') - $h2 = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 150 - if ($h2) { Add-TestCheck $result '降级路径: Esc 后经 cmd 到 desktop' 'PASS' } - else { Add-TestCheck $result '降级路径: Esc 后经 cmd 到 desktop' 'FAIL' 'no desktop ready' } - } - - # A5.1~A5.12 环境性跳过 - foreach ($t in @('A5.1 VMM self-test', 'A5.2 bzImage 加载', 'A5.3 EPT+vmlaunch', - 'A5.4 Linux 内核启动', 'A5.5 init 脚本', 'A5.6 daemon park', - 'A5.8 dsl 状态就绪', 'A5.9 dsl ls', 'A5.10 dsl uname', - 'A5.11 dsl cat hostname', 'A5.12 dsl 错误路径')) { - Add-TestCheck $result $t 'SKIP' 'QEMU WHPX/TCG 无 nested VMX(见 run_qemu.bat 注释),需真机或 KVM 复测' - } - Add-TestCheck $result 'A5.7: Linux 缺失时 DSK 正常接管' 'PASS' - Add-TestNote $result '路径 B 执行:QEMU 无 nested VMX,Linux guest 不可用属预期环境限制' - Add-TestNote $result '如需完整验证:真机(VT-x)或 Linux KVM (-enable-kvm -cpu host) 下以 -Cpu max 重跑' - } - else { - $result.LogPath = $s.LogPath - Add-TestCheck $result 'VMM 分流探测' 'FAIL' 'neither VMM self-test PASS nor VMM unavailable within 180s' - } -} finally { - Stop-QemuSession $s - Restore-DeshabScenario -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/network-e2e.ps1 b/tests/qemu/network-e2e.ps1 deleted file mode 100644 index d5b0dd2..0000000 --- a/tests/qemu/network-e2e.ps1 +++ /dev/null @@ -1,189 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - network-e2e — 网络端到端测试(docs/RE/test-cases/network-e2e.md) - -.DESCRIPTION - 场景 N-A(netman 全链路,首启调度): - firstInit="0\n0" 首启,挂 e1000(net0) + virtio-net(net1) 双网卡。 - DSK 首启链 mouseInit → netman → FirstInit,netman 读取 conf.conf(mode=dhcp), - 在 index=0 的 e1000 上执行 DHCP DISCOVER/OFFER/REQUEST/ACK → ARP 网关 → DNS example.com。 - 断言锚点(netman log_hex 为 16 位定宽小写十六进制,同行输出): - [virtio_net] selftest PASS: RX path ok (stage3 驱动自测,真实 slirp 后端 DHCP OFFER) - [netman] netdev_count=0x0000000000000002 (e1000 + virtio_net 双设备) - [netman] DHCP OFFER ip= / DHCP ACK, leased ip= / DHCP final rc=0x0...0 - [netman] ARP: gateway resolved / ARP final rc=0x0...0 - [netman] DNS example.com= / DNS final rc=0x0...0 - [netman] network final rc=0x0000000000000000 - netman 之后停在 FirstInit 向导即杀会话(向导自动化由 boot-regression B1 覆盖)。 - - 场景 N-B(curl.elf 主机侧端到端,dev_mode shell): - firstInit="1\n1" → shell dev_tests → 交互 `curl`(PATH → /bin/CURL.ELF)。 - 主机侧 Start-TestHttpServer 监听 127.0.0.1:8000,guest 输入 - `http://10.0.2.2:8000/` 经 slirp 网关访问主机服务; - 断言主机侧 HTTP 服务器日志收到 "GET / HTTP/1.0"(端到端最强证据), - guest 侧断言 [curl] boot / [curl] exit / [shell] path: tool returned。 - 响应体只写帧缓冲(无串口),标 MANUAL。 - - 退出码:全部通过 0,任一 FAIL 1。 -#> -param( - [switch]$Build, - [string[]]$Scenarios = @('NA', 'NB') -) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'network-e2e' - -# ---------------------------------------------------------------- -function Invoke-ScenarioNA { - Write-Host "`n--- N-A netman 全链路 (首启, e1000 + virtio-net 双网卡) ---" - Set-DeshabFirstInit -First 0 -DevMode 0 - # run_qemu.bat 默认仅 e1000;追加第二网卡 virtio-net-pci 挂独立 slirp 后端 net1。 - $s = Start-QemuSession -Name 'nete2e-na' -MonitorPort 45506 -ExtraArgs @( - '-netdev', 'user,id=net1', - '-device', 'virtio-net-pci,netdev=net1' - ) - try { - $hit = Wait-QemuLog -Session $s -Patterns @( - '[netman] network final rc=', - '[netman] DHCP: no OFFER received', - '[netman] ARP: timeout', - '[netman] DNS: timeout', - '[PANIC]' - ) -TimeoutSeconds 240 - Start-Sleep -Seconds 2 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - - # ---- N1/N2: 双网卡驱动与设备注册 ---- - Assert-QemuLog $result $log -Ordered @( - '[e1000] netdev registered, index=0x0000000000000000', - '[virtio_net] netdev registered, index=', - '[virtio_net] selftest: TX path ok', - '[virtio_net] selftest: DHCP OFFER yiaddr=', - '[virtio_net] selftest PASS: RX path ok' - ) - # ---- netman 全链路(N3 DHCP / N4 ARP / N5 DNS) ---- - Assert-QemuLog $result $log -Ordered @( - '[DSK] loading netman', - '[netman] boot', - '[netman] netdev_count=0x0000000000000002', - '[netman] DHCP: sending DISCOVER', - '[netman] DHCP OFFER ip=', - '[netman] DHCP: sending REQUEST', - '[netman] DHCP ACK, leased ip=', - '[netman] DHCP final rc=0x0000000000000000', - '[netman] ARP: gateway resolved', - '[netman] ARP final rc=0x0000000000000000', - '[netman] DNS example.com=', - '[netman] DNS final rc=0x0000000000000000', - '[netman] network final rc=0x0000000000000000' - ) -MustNotContain @( - '[PANIC]', - '[netman] DHCP: no OFFER received', - '[netman] ARP: timeout', - '[netman] DNS: timeout' - ) - - if ($hit -eq '[netman] network final rc=') { - Add-TestCheck $result 'N-A: netman DHCP+ARP+DNS 全链路 rc=0' 'PASS' - } else { - Add-TestCheck $result 'N-A: netman DHCP+ARP+DNS 全链路 rc=0' 'FAIL' "hit=$hit" - } - Add-TestNote $result 'N-A: netman 仅首启调度;后续 FirstInit 向导自动化由 boot-regression.ps1 B1 覆盖,本场景断言后即结束会话' - } finally { - Stop-QemuSession $s - } -} - -# ---------------------------------------------------------------- -function Invoke-ScenarioNB { - Write-Host "`n--- N-B curl.elf 主机侧端到端 (dev_mode shell → http://10.0.2.2:8000/) ---" - Set-DeshabFirstInit -First 1 -DevMode 1 - Set-DeshabAutoexec -Lines @('ver', 'exit') - - $httpLog = Join-Path $script:TestWorkDir 'nete2e-http-requests.log' - $httpJob = Start-TestHttpServer -Port 8000 -LogPath $httpLog - $s = Start-QemuSession -Name 'nete2e-nb' -MonitorPort 45506 - try { - $hit = Wait-QemuLog -Session $s -Patterns @('=== 自动测试完成 ===', '[PANIC]') -TimeoutSeconds 240 - if ($hit -ne '=== 自动测试完成 ===') { - $result.LogPath = $s.LogPath - Add-TestCheck $result 'N-B: shell dev_tests 完成' 'FAIL' "hit=$hit" - return - } - Add-TestCheck $result 'N-B: shell dev_tests 完成' 'PASS' - - # --- 启动 curl 工具(PATH → /bin/CURL.ELF) --- - Start-Sleep -Seconds 1 - Send-QemuText -Session $s -Text 'curl' -Enter - $h = Wait-QemuLog -Session $s -Patterns @('[curl] boot') -TimeoutSeconds 60 - if (-not $h) { Add-TestCheck $result 'N-B: curl.elf PATH 加载启动' 'FAIL' 'missing [curl] boot'; return } - Add-TestCheck $result 'N-B: curl.elf PATH 加载启动' 'PASS' - - # --- 输入 URL:经 slirp 网关 10.0.2.2 访问主机 127.0.0.1:8000 --- - Start-Sleep -Milliseconds 800 - Send-QemuText -Session $s -Text 'http://10.0.2.2:8000/' -Enter -DelayMs 110 - - # 主机侧端到端证据:HTTP 服务器收到 guest 的 GET 请求 - $deadline = (Get-Date).AddSeconds(45) - $hostGot = $false - while ((Get-Date) -lt $deadline) { - if ((Test-Path $httpLog) -and ((Get-Content $httpLog -Raw -ErrorAction SilentlyContinue) -match 'GET / HTTP/1\.0')) { - $hostGot = $true; break - } - Start-Sleep -Milliseconds 800 - } - if ($hostGot) { - Add-TestCheck $result 'N-B: 主机 HTTP 服务器收到 guest GET /(TCP+IP+ETH 全链路)' 'PASS' - } else { - Add-TestCheck $result 'N-B: 主机 HTTP 服务器收到 guest GET /(TCP+IP+ETH 全链路)' 'FAIL' 'no request logged by host http server within 45s' - } - - # --- Esc 退出 curl 返回 shell --- - Start-Sleep -Seconds 2 - Send-QemuKeys -Session $s -Keys @('esc') - $h2 = Wait-QemuLog -Session $s -Patterns @('[curl] exit') -TimeoutSeconds 30 - $logc = Read-QemuLogText $s.LogPath - if ($h2 -and $logc.Contains('[shell] path: tool returned')) { - Add-TestCheck $result 'N-B: curl Esc 退出返回 shell' 'PASS' - } else { - Add-TestCheck $result 'N-B: curl Esc 退出返回 shell' 'FAIL' 'missing [curl] exit / tool returned' - } - - # --- Esc 回 DSK → cmd → desktop --- - Send-QemuKeys -Session $s -Keys @('esc') - $h3 = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 150 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - Assert-QemuLog $result $log -MustNotContain @('[PANIC]') - if ($h3) { Add-TestCheck $result 'N-B: Esc → cmd → desktop ready' 'PASS' } - else { Add-TestCheck $result 'N-B: Esc → cmd → desktop ready' 'FAIL' 'no desktop ready' } - - Add-TestCheck $result 'N-B: HTTP 200 响应体与 DESHAB-NET-E2E-MARKER 渲染' 'MANUAL' 'curl 响应输出仅帧缓冲(无串口锚点);主机服务器固定回复 marker 页面,可 screendump 人工核对' - Add-TestNote $result 'N6/N11 ping 链路:由 userapps-matrix.ps1 交互阶段覆盖(ping 工具 10.0.2.2 四报文)' - Add-TestNote $result 'N7/N9 外网访问:依赖主机联网,不在自动化断言范围(文档标 🔶)' - } finally { - Stop-QemuSession $s - Stop-TestHttpServer $httpJob - } -} - -# ---------------------------------------------------------------- -try { - foreach ($sc in $Scenarios) { - switch ($sc.ToUpper()) { - 'NA' { Invoke-ScenarioNA } - 'NB' { Invoke-ScenarioNB } - default { Add-TestNote $result "未知场景 $sc,跳过" } - } - } -} finally { - Restore-DeshabScenario -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/pe-compat.ps1 b/tests/qemu/pe-compat.ps1 deleted file mode 100644 index 1494f3c..0000000 --- a/tests/qemu/pe-compat.ps1 +++ /dev/null @@ -1,248 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - pe-compat — PE/EXE 兼容层测试(docs/RE/test-cases/pe-compat.md) - -.DESCRIPTION - 正常启动到 cmd.elf,用 AUTOEXEC.BAT 自动执行 PE 命令序列。 - 因两个已登记系统 bug(详见 docs/RE/Bug记录.txt),拆三个独立会话: - - 会话 A (pe64):pe HELLO64.EXE → PE32+ 原生 Ring0 执行,shim_WriteFile 输出串口。 - BUG-20260801-006 已修复(setjmp 下沉 pe_service_run 本体): - ExitProcess longjmp 干净返回,"[cmd] PE run end" 稳定出现。 - 会话 B (pe32):pe HELLO32.EXE → PE32 x86emu32 解释执行。 - BUG-20260801-007 已修复(gp() guest→host 指针转换 + - fn64 调用 typedef 显式 ms_abi):"Hello from PE32!" 稳定输出。 - 会话 D (fault64):pe FAULT64.EXE → BUG-20260801-005 验收:DSK 阶段故意 - #PF 必须打出 [IDT] exception 完整现场(vector=0xe, err=0x2, - cr2=0x40000000000)。修复前 halt_all 静默吞异常零日志。 - 会话 C (peerr):pe NONEXIST.EXE + exit → 错误路径:不产生 [cmd] PE run begin; - exit 正常返回 DSK("[DSK] cmd.elf returned")。 - - 串口可断言锚点: - [cmd] PE run begin / [cmd] cmdline: ... / [cmd] PE run end (logl/swrite, 始终上串口) - [PE] PE32+ (64-bit) / [PE] running PE32+ natively (pe_service) - [PE] running PE32 via x86emu32 (pe_service) - Hello from PE32+! / Hello from PE32! (shim WriteFile → 串口) - - 注意:cmd_pe 传 full_cmdline=args(不含 "pe " 前缀), - 所以串口回显为 "[cmd] cmdline: HELLO64.EXE" 而不是 "pe HELLO64.EXE"。 - - WHPX 已知问题:pe_service 为原生 PE32+ 执行写 CR4 启用 SSE 后, - WHPX 可能以 "Unexpected VP exit code 4" 提前终止 QEMU(WHPX 平台限制, - 非 guest 崩溃)。任一会话在 WHPX 下未命中锚点时自动以 -Accel tcg 重跑 - (可用 -Accel tcg 直接指定跳过 WHPX)。 - - peinfo / 无参用法提示只写帧缓冲(cmd 非 dev_mode 无串口镜像),标 MANUAL。 - - 退出码:全部通过 0,任一 FAIL 1。 -#> -param( - [switch]$Build, - [string]$Accel = 'whpx' -) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'pe-compat' - -# ---------------------------------------------------------------- -# 单会话执行一轮 AUTOEXEC:正常启动 → cmd → 命令序列 → 等锚点/会话结束 -# 填充 $script:RunLog / RunLogPath;返回命中的锚点或 $null。 -# ---------------------------------------------------------------- -$script:RunLog = '' -$script:RunLogPath = '' -function Invoke-PeSession { - param( - [Parameter(Mandatory)][string]$Name, - [Parameter(Mandatory)][string]$AccelMode, - [Parameter(Mandatory)][string[]]$Autoexec, - [Parameter(Mandatory)][string[]]$Anchors - ) - Set-DeshabFirstInit -First 1 -DevMode 0 - Set-DeshabAutoexec -Lines $Autoexec - - $timeout = 300 - if ($AccelMode -eq 'tcg') { $timeout = 600 } # TCG 全模拟显著更慢 - # 注:2GB ESP + Linux bzImage/initrd/512MB rootfs boot module 使 Limine - # 加载 + UTSM/DSK 启动到 cmd.elf 需要 ~150s(WHPX),AUTOEXEC/PE 执行 - # 还需额外时间,故 whpx 300s / tcg 600s。 - $s = Start-QemuSession -Name "$Name-$AccelMode" -Accel $AccelMode -MonitorPort 45504 - try { - $hit = Wait-QemuLog -Session $s -Patterns $Anchors -TimeoutSeconds $timeout - Start-Sleep -Seconds 2 - $script:RunLog = Read-QemuLogText $s.LogPath - $script:RunLogPath = $s.LogPath - return $hit - } finally { - Stop-QemuSession $s - } -} - -# WHPX 未命中锚点 → TCG 兜底重跑一轮 -function Invoke-PeSessionWithFallback { - param( - [Parameter(Mandatory)][string]$Name, - [Parameter(Mandatory)][string[]]$Autoexec, - [Parameter(Mandatory)][string[]]$Anchors - ) - $hit = Invoke-PeSession -Name $Name -AccelMode $Accel -Autoexec $Autoexec -Anchors $Anchors - if (-not $hit -and $Accel -eq 'whpx') { - $why = 'WHPX 下未命中锚点' - if ($script:RunLog.Contains('WHPX: Unexpected VP exit')) { $why = 'WHPX 在 PE 执行处终止 QEMU(Unexpected VP exit,WHPX 平台限制)' } - Add-TestNote $result "${Name}: $why,自动以 TCG 重跑" - $script:UsedAccel = 'tcg' - $hit = Invoke-PeSession -Name $Name -AccelMode 'tcg' -Autoexec $Autoexec -Anchors $Anchors - } - return $hit -} - -$script:UsedAccel = $Accel -try { - # ================================================================ - # 会话 A:B2.1 PE32+ 原生加载执行(BUG-006 已修复,ExitProcess 干净返航) - # ================================================================ - $hitA = Invoke-PeSessionWithFallback -Name 'pe64' -Autoexec @('pe HELLO64.EXE') -Anchors @('Hello from PE32+!', '[PANIC]') - $logA = $script:RunLog - $result.LogPath = $script:RunLogPath - - Assert-QemuLog $result $logA -Ordered @( - '[cmd] running AUTOEXEC.BAT', - '[cmd] PE run begin', - '[cmd] cmdline: HELLO64.EXE', - '[PE] PE32+ (64-bit)', - '[PE] running PE32+ natively', - 'Hello from PE32+!' - ) -MustNotContain @('[PANIC]') - - # ---- ABI 验证:cmd 启动时 ctx->reserved[4] + PE_SERVICE_MAGIC 校验 ---- - Assert-QemuLog $result $logA -MustContain @('[cmd] PE service ok') - - # ---- B2.6 ExitProcess 返回链:BUG-006 已修复,转硬断言(回归守护) ---- - if ($logA.Contains('[cmd] PE run end')) { - Add-TestCheck $result 'B2.6: ExitProcess 后 cmd 继续(PE32+ 返回链)' 'PASS' - } else { - Add-TestCheck $result 'B2.6: ExitProcess 后 cmd 继续(PE32+ 返回链)' 'FAIL' 'missing [cmd] PE run end(BUG-20260801-006 回归?)' - } - - # ================================================================ - # 会话 B:B2.2 PE32 x86emu32 解释执行(BUG-007 已修复,shim 指针转换) - # ================================================================ - $hitB = Invoke-PeSessionWithFallback -Name 'pe32' -Autoexec @('pe HELLO32.EXE') -Anchors @('Hello from PE32!', '[PANIC]', '[PE32] emulator error') - $logB = $script:RunLog - - Assert-QemuLog $result $logB -Ordered @( - '[cmd] PE run begin', - '[cmd] cmdline: HELLO32.EXE', - '[PE] running PE32 via x86emu32' - ) -MustNotContain @( - '[PANIC]', - '[PE32] emulator error', - '[PE32] emu init failed', - '[PE] unimplemented import', - '[PE32] unimplemented import', - '[PE] unimplemented API called' - ) - - # ---- B2.2 shim 输出:BUG-007 已修复,转硬断言(回归守护) ---- - if ($logB.Contains('Hello from PE32!')) { - Add-TestCheck $result 'B2.2: PE32 解释执行 shim 输出' 'PASS' - } else { - Add-TestCheck $result 'B2.2: PE32 解释执行 shim 输出' 'FAIL' 'missing Hello from PE32!(BUG-20260801-007 回归?)' - } - - # ================================================================ - # 会话 D:BUG-20260801-005 验收 — DSK 阶段故意 #PF 必须打出完整异常现场 - # FAULT64.EXE: movabs rax,0x400000000000 ; mov [rax],1 ; jmp $ - # 预期 vector=0xe / err=0x2(supervisor write non-present) / - # cr2=0x0000400000000000;修复前 halt_all 静默吞掉零日志。 - # 异常后 idt_handler 安全停机(hlt 循环),QEMU 存活 → 锚点命中即返回。 - # 若异常投递失效则三重故障 → -no-reboot QEMU 退出 → hit=$null → FAIL。 - # ================================================================ - $hitD = Invoke-PeSessionWithFallback -Name 'fault64' -Autoexec @('pe FAULT64.EXE') -Anchors @('[IDT] exception', '[PANIC]') - $logD = $script:RunLog - - if (-not $hitD) { - Add-TestCheck $result 'BUG-005: DSK 阶段 #PF 异常投递' 'FAIL' 'session exited without [IDT] exception(异常被吞/三重故障)' - } else { - Add-TestCheck $result 'BUG-005: DSK 阶段 #PF 异常投递' 'PASS' - } - Assert-QemuLog $result $logD -Ordered @( - '[cmd] cmdline: FAULT64.EXE', - '[PE] running PE32+ natively', - '[IDT] exception', - '[IDT] vector=0x000000000000000e', - '[IDT] err=0x0000000000000002', - '[IDT] cr2=0x0000400000000000' - ) - - # ================================================================ - # 会话 C:B2.4 文件不存在错误路径 + cmd exit 正常返回 DSK - # ================================================================ - $hitC = Invoke-PeSessionWithFallback -Name 'peerr' -Autoexec @('pe NONEXIST.EXE', 'exit') -Anchors @('[DSK] cmd.elf returned', '[PANIC]') - $logC = $script:RunLog - - # NONEXIST 不得进入 PE 执行路径 - if ($logC.Contains('[cmd] cmdline: NONEXIST.EXE') -or $logC.Contains('[cmd] PE run begin')) { - Add-TestCheck $result 'B2.4: NONEXIST.EXE 不进入 PE 执行路径' 'FAIL' 'unexpected PE run begin / cmdline echo' - } else { - Add-TestCheck $result 'B2.4: NONEXIST.EXE 不进入 PE 执行路径' 'PASS' - } - Assert-QemuLog $result $logC -MustNotContain @('[PANIC]') - - if ($hitC -eq '[DSK] cmd.elf returned') { - Add-TestCheck $result '链路: cmd exit 正常返回 DSK' 'PASS' - } else { - Add-TestCheck $result '链路: cmd exit 正常返回 DSK' 'FAIL' "hit=$hitC(missing [DSK] cmd.elf returned)" - } - - # ================================================================ - # 会话 E:真实 DLL 链(pe_dll_manager → SATA /lib 真实 DLL) - # RDLL64.EXE 静态导入 msvcrt!strcat/strstr/atoi(shim 表外函数) - # → resolve_imports 落 pe_dll_resolve → fat32_read_path("lib/msvcrt.dll") - # → pe_load_image 真实 DLL(DllMain ATTACH)→ 导出表解析 - # (Win10 msvcrt 转发 ucrtbase → 验证转发导出跟随)。 - # 另 LoadLibraryA("ucrtbase.dll") + GetProcAddress(strlen/memcpy) - # → 按真实镜像基址解析并直接调用真实代码。 - # ================================================================ - $hitE = Invoke-PeSessionWithFallback -Name 'realdll' -Autoexec @('pe RDLL64.EXE') -Anchors @('[REALDLL] end', '[PANIC]') - $logE = $script:RunLog - - Assert-QemuLog $result $logE -Ordered @( - '[cmd] cmdline: RDLL64.EXE', - '[REALDLL] begin', - '[REALDLL] A1 strcat: OK', - '[REALDLL] A2 strstr: OK', - '[REALDLL] A3 atoi: OK', - '[REALDLL] B1 LoadLibraryA(ucrtbase.dll): OK', - '[REALDLL] B2 GetProcAddress(strlen): OK', - '[REALDLL] B2 strlen call: OK', - '[REALDLL] B3 GetProcAddress(memcpy): OK', - '[REALDLL] B3 memcpy call: OK', - '[REALDLL] end fails=0' - ) -MustContain @( - '[PE] DLL manager initialized', - '[PE] loaded DLL:', - '[PE] DllMain ATTACH ok' - ) -MustNotContain @( - '[PANIC]', - '[PE] unimplemented import', - '[PE] ordinal forward target not found', - '[PE] malformed forwarder string', - '[PE] DllMain returned FALSE' - ) - - Add-TestNote $result "加速方案: $script:UsedAccel" - Add-TestNote $result 'BUG-006/007 已修复,返回链断言已转硬断言;会话仍按故障域拆分(单会话全序列合并留作后续优化)' - - # ---- 帧缓冲限定项(无串口输出,标 MANUAL) ---- - Add-TestCheck $result 'B2.3: peinfo HELLO64.EXE 头解析' 'MANUAL' 'peinfo 输出仅帧缓冲(cmd 非 dev_mode 无串口镜像),手动验证 Machine: 0x8664' - Add-TestCheck $result 'B2.7: peinfo HELLO32.EXE 头解析' 'MANUAL' '同上,预期 Machine: 0x14c / image_base=0x400000' - Add-TestCheck $result 'B2.5: pe 无参用法提示' 'MANUAL' '用法提示仅帧缓冲输出' -} finally { - Restore-DeshabScenario -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/performance-baseline.ps1 b/tests/qemu/performance-baseline.ps1 deleted file mode 100644 index 06e34e4..0000000 --- a/tests/qemu/performance-baseline.ps1 +++ /dev/null @@ -1,139 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - performance-baseline — 启动性能基线(docs/RE/test-cases/performance-baseline.md) - -.DESCRIPTION - 正常启动场景(firstInit="1\n0", AUTOEXEC=ver/exit),按墙上时钟记录 - 关键阶段锚点在串口日志中首次出现的时刻,计算阶段时间差: - - t0 QEMU 进程启动(含 OVMF + Limine bootloader) - [UTSM] boot UTSM 内核入口 - [UTSM] SELFTEST PASS - UTSM 自检完成(位于 4 stage 共 16 驱动装载之后, - 即"驱动加载完"时刻) - [DSK] boot DSK 主内核接管 - desktop ready 桌面就绪(login skipped → cmd(AUTOEXEC) → desktop) - - 输出 .build_tmp\perf_baseline.json(锚点绝对时刻 + 阶段毫秒数 + 环境信息)。 - 本用例只测量展示、不做阈值断言(文档标 P2,QEMU 数值仅作回归参考)。 - 多次测量取中位数:默认 3 轮,可用 -Runs 调整。 - - 退出码:测量完成 0;会话/锚点异常 1。 -#> -param( - [switch]$Build, - [int]$Runs = 3 -) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'performance-baseline' - -# 阶段锚点定义(顺序即启动顺序) -$anchorDefs = @( - @{ Key = 'utsm_boot'; Pattern = '[UTSM] boot' }, - @{ Key = 'utsm_selftest'; Pattern = '[UTSM] SELFTEST PASS' }, - @{ Key = 'dsk_boot'; Pattern = '[DSK] boot' }, - @{ Key = 'desktop_ready'; Pattern = 'desktop ready' } -) - -# 轮询串口日志,记录每个锚点首次出现的墙上时刻;返回 hashtable 或 $null -function Measure-BootPhases { - param([Parameter(Mandatory)]$Session, [int]$TimeoutSeconds = 240) - $marks = @{} - foreach ($a in $anchorDefs) { $marks[$a.Key] = $null } - $deadline = (Get-Date).AddSeconds($TimeoutSeconds) - $remaining = $anchorDefs.Count - while ((Get-Date) -lt $deadline -and $remaining -gt 0) { - $text = Read-QemuLogText $Session.LogPath - foreach ($a in $anchorDefs) { - if ($null -eq $marks[$a.Key] -and $text.Contains($a.Pattern)) { - $marks[$a.Key] = Get-Date - $remaining-- - } - } - if (Test-QemuSessionExited $Session) { break } - Start-Sleep -Milliseconds 200 - } - return $marks -} - -$allRuns = New-Object System.Collections.ArrayList -try { - for ($run = 1; $run -le $Runs; $run++) { - Write-Host "`n--- perf run $run/$Runs (正常启动 → desktop ready) ---" - Set-DeshabFirstInit -First 1 -DevMode 0 - Set-DeshabAutoexec -Lines @('ver', 'exit') - - $t0 = Get-Date - $s = Start-QemuSession -Name "perf-run$run" -MonitorPort 45507 - try { - $marks = Measure-BootPhases -Session $s -TimeoutSeconds 240 - $result.LogPath = $s.LogPath - } finally { - Stop-QemuSession $s - } - - if ($null -eq $marks['desktop_ready']) { - Add-TestCheck $result "run${run}: 锚点采集完整" 'FAIL' 'desktop ready not observed within timeout' - continue - } - Add-TestCheck $result "run${run}: 锚点采集完整" 'PASS' - - $ms = [ordered]@{ - ovmf_bootloader_ms = [int]($marks['utsm_boot'] - $t0).TotalMilliseconds - utsm_drivers_ms = [int]($marks['utsm_selftest'] - $marks['utsm_boot']).TotalMilliseconds - selftest_to_dsk_ms = [int]($marks['dsk_boot'] - $marks['utsm_selftest']).TotalMilliseconds - dsk_to_desktop_ms = [int]($marks['desktop_ready'] - $marks['dsk_boot']).TotalMilliseconds - total_boot_ms = [int]($marks['desktop_ready'] - $t0).TotalMilliseconds - } - [void]$allRuns.Add([pscustomobject]@{ - run = $run - started_at = $t0.ToString('yyyy-MM-dd HH:mm:ss') - metrics = $ms - }) - Write-Host (" ovmf={0}ms utsm+drivers={1}ms dsk_load={2}ms dsk->desktop={3}ms total={4}ms" -f ` - $ms.ovmf_bootloader_ms, $ms.utsm_drivers_ms, $ms.selftest_to_dsk_ms, $ms.dsk_to_desktop_ms, $ms.total_boot_ms) - } -} finally { - Restore-DeshabScenario -} - -# ---- 中位数汇总 + JSON 输出 ---- -if ($allRuns.Count -gt 0) { - $median = [ordered]@{} - foreach ($k in @('ovmf_bootloader_ms', 'utsm_drivers_ms', 'selftest_to_dsk_ms', 'dsk_to_desktop_ms', 'total_boot_ms')) { - $vals = @($allRuns | ForEach-Object { [int]$_.metrics[$k] } | Sort-Object) - $median[$k] = $vals[[int]($vals.Count / 2)] - } - - $out = [ordered]@{ - generated_at = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') - method = 'host wall-clock polling of serial log anchors (200ms poll, +/-200ms 精度)' - environment = [ordered]@{ - accel = 'whpx' - cpu = 'qemu64,-vmx' - machine = 'q35' - memory = '2G' - scenario = 'firstInit=1 dev_mode=0 AUTOEXEC=ver,exit -> desktop ready' - snapshot = $true - } - median_ms = $median - runs = $allRuns - } - $jsonPath = Join-Path $script:BuildTmp 'perf_baseline.json' - ($out | ConvertTo-Json -Depth 6) | Out-File -Encoding utf8 $jsonPath - Add-TestNote $result "基线 JSON: $jsonPath" - - Add-TestCheck $result ("P1: UTSM 启动+驱动装载 (utsm_boot→SELFTEST) = {0} ms (目标<500ms QEMU, 仅展示)" -f $median.utsm_drivers_ms) 'PASS' - Add-TestCheck $result ("DSK 加载 (SELFTEST→[DSK] boot) = {0} ms" -f $median.selftest_to_dsk_ms) 'PASS' - Add-TestCheck $result ("DSK→desktop ready = {0} ms" -f $median.dsk_to_desktop_ms) 'PASS' - Add-TestCheck $result ("总启动 (QEMU 启动→desktop ready) = {0} ms" -f $median.total_boot_ms) 'PASS' - Add-TestNote $result '本用例不做阈值断言;QEMU WHPX 数值仅作回归参考(文档:QEMU≠真机,固定加速方案)' - Add-TestNote $result 'P2/P3/P6(单 ELF 加载计时)、P4 ping RTT、P7 桌面帧率、P8-P11(PE/VMM/Linux)需内核 TSC 打点日志,当前串口无对应锚点,保持 PENDING(见建议锚点清单)' -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/userapps-matrix.ps1 b/tests/qemu/userapps-matrix.ps1 deleted file mode 100644 index 79a5e84..0000000 --- a/tests/qemu/userapps-matrix.ps1 +++ /dev/null @@ -1,236 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - userapps-matrix — 用户态程序功能矩阵(docs/RE/test-cases/userapps-matrix.md) - -.DESCRIPTION - dev_mode 单会话覆盖 shell 内建命令与工具程序(串口镜像开启,全程可断言)。 - - 会话 A(唯一):dev_tests 硬编码序列 → help → id → pci 存活 → - ping(PATH 加载 → 10.0.2.2 四报文 → Esc 退出)→ - run EDITOR.ELF / FILEMAN.ELF / BROWSER.ELF(加载 + 按键 + Esc 退出)→ - Esc 回 DSK → cmd(AUTOEXEC) → desktop - - 历史背景(详见 docs/RE/Bug记录.txt,均已 FIXED,相关降级已全部回收): - - BUG-20260801-001 [shell] y/i/0 吞键(未跟踪 Ctrl 修饰键): - 已修复(0x1D/0x9D 跟踪 Ctrl,快捷键仅在 Ctrl 按下时生效) - → id/ping 等含 i 命令恢复自动化断言。 - - BUG-20260801-002 [shell] g_run_image 仅 256KB,editor(496KB) 拒绝加载: - 已修复(g_run_image 扩至 1MB,与 desktop 宿主对齐) - → run EDITOR.ELF 断言升级为 [editor] boot + [editor] exit。 - - BUG-20260801-003 [desktop_app.h] da_mouse_poll 吞键盘扫描码 + - BUG-20260801-008 [desktop_app.h] da_mouse_init 配置字污染: - 均已修复(先判 AUX 位再读 0x60;init 首尾 da_ps2_drain + 读全响应) - → fileman/editor/browser 键盘路径恢复,Esc 退出断言全部自动化; - 工具不再占住会话,三会合并为单会话顺序执行。 - - 退出码:全部通过 0,任一 FAIL 1。 -#> -param([switch]$Build) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'userapps-matrix' - -# ---------------------------------------------------------------- -# monitor sendkey 在 guest 重负载(AHCI 读盘刷屏、帧缓冲重绘)下会丢扫描码。 -# 对策:每键 150ms + "发送→等锚点→重试 ≤3 次",重试前先 backspace 清行。 -# 注意:backspace(0x0E) 不在 shell Ctrl+U/W/K 快捷键扫描码列表内,清行可靠。 -# ---------------------------------------------------------------- -function Invoke-ShellCmdRetry { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)][string]$Text, - [Parameter(Mandatory)][string[]]$Anchors, - [int]$TimeoutSeconds = 20, - [int]$MaxAttempts = 3 - ) - for ($a = 1; $a -le $MaxAttempts; $a++) { - if ($a -gt 1) { - $bk = @(); for ($i = 0; $i -lt 24; $i++) { $bk += 'backspace' } - Send-QemuKeys -Session $Session -Keys $bk -DelayMs 50 - Start-Sleep -Milliseconds 400 - Add-TestNote $result "输入 '$Text' 第 $($a-1) 次未命中锚点,清行重试" - } - Send-QemuText -Session $Session -Text $Text -Enter -DelayMs 150 - $h = Wait-QemuLog -Session $Session -Patterns $Anchors -TimeoutSeconds $TimeoutSeconds - if ($h) { return $h } - } - return $null -} - -# Esc 退出重试:只在锚点未出现时补发,锚点出现即停(避免多余 Esc 漏进 shell/cmd) -function Send-EscRetry { - param( - [Parameter(Mandatory)]$Session, - [Parameter(Mandatory)][string[]]$Anchors, - [int]$TimeoutSeconds = 15, - [int]$MaxAttempts = 2 - ) - for ($a = 1; $a -le $MaxAttempts; $a++) { - Send-QemuKeys -Session $Session -Keys @('esc') - $h = Wait-QemuLog -Session $Session -Patterns $Anchors -TimeoutSeconds $TimeoutSeconds - if ($h) { return $h } - if ($a -lt $MaxAttempts) { Add-TestNote $result "Esc 第 $a 次未命中($($Anchors -join ' | ')),补发" } - } - return $null -} - -try { - # ================================================================ - # 会话 A(唯一):shell 内建命令 + ping/editor/fileman/browser - # 工具链全交互 + Esc→cmd→desktop 链路 - # ================================================================ - Set-DeshabFirstInit -First 1 -DevMode 1 - Set-DeshabAutoexec -Lines @('ver', 'exit') - - $s = Start-QemuSession -Name 'userapps-matrix' -MonitorPort 45505 - try { - $hit = Wait-QemuLog -Session $s -Patterns @('=== 自动测试完成 ===', '[PANIC]') -TimeoutSeconds 240 - $log = Read-QemuLogText $s.LogPath - $result.LogPath = $s.LogPath - - Assert-QemuLog $result $log -Ordered @( - '[DSK:SCHED] decision: dev_mode -> shell.elf', - '[shell] boot', - '[shell] test.bas not found, fallback to hardcoded', - '[1] ls', - '[2] echo "Deshab dev test" > TEST.TXT', - '[3] cat TEST.TXT', - '[4] cp TEST.TXT COPY.TXT', - '[6] mv COPY.TXT MOVED.TXT', - '[8] rm TEST.TXT', - '[10] ls' - ) -MustNotContain @('[PANIC]') - if ($hit -eq '=== 自动测试完成 ===') { - Add-TestCheck $result 'shell: dev_tests 完成(ls/echo/cat/cp/mv/rm)' 'PASS' - } else { - Add-TestCheck $result 'shell: dev_tests 完成(ls/echo/cat/cp/mv/rm)' 'FAIL' "hit=$hit" - } - - if ($hit -eq '=== 自动测试完成 ===') { - # --- help --- - Start-Sleep -Seconds 1 - $h = Invoke-ShellCmdRetry -Session $s -Text 'help' -Anchors @('dsl push <') -TimeoutSeconds 20 - if ($h) { Add-TestCheck $result 'shell: help 命令列表' 'PASS' } - else { Add-TestCheck $result 'shell: help 命令列表' 'FAIL' 'missing help output' } - - # --- id(BUG-001 修复回归守护:i 不再被吞,输出上串口) --- - $h = Invoke-ShellCmdRetry -Session $s -Text 'id' -Anchors @('uid=0(root) gid=0(root)') -TimeoutSeconds 20 - if ($h) { Add-TestCheck $result 'shell: id 用户查询(BUG-001 修复后 i 可键入)' 'PASS' } - else { Add-TestCheck $result 'shell: id 用户查询(BUG-001 修复后 i 可键入)' 'FAIL' 'missing uid=0(root) output' } - - # --- pci(验证存活:pci 后 ver 仍有响应,锚点取 ver 独有行) --- - Send-QemuText -Session $s -Text 'pci' -Enter -DelayMs 150 - Start-Sleep -Seconds 3 - $h = Invoke-ShellCmdRetry -Session $s -Text 'ver' -Anchors @('Architecture: x86_64') -TimeoutSeconds 20 - if ($h) { Add-TestCheck $result 'shell: pci 枚举后 shell 存活' 'PASS' } - else { Add-TestCheck $result 'shell: pci 枚举后 shell 存活' 'FAIL' 'no response after pci' } - - # --- ping:BUG-001 修复后可键入;PATH → /bin/PING.ELF → 10.0.2.2 四报文 → Esc 退出 --- - $h = Invoke-ShellCmdRetry -Session $s -Text 'ping' -Anchors @('[ping] boot') -TimeoutSeconds 30 - if ($h) { - Add-TestCheck $result 'ping.elf: PATH 加载启动([ping] boot)' 'PASS' - Start-Sleep -Milliseconds 800 - # 4 个 ICMP Echo(每报文超时上限 2s);reply/统计仅帧缓冲,串口无锚点 - Send-QemuText -Session $s -Text '10.0.2.2' -Enter -DelayMs 150 - Start-Sleep -Seconds 10 - $h2 = Send-EscRetry -Session $s -Anchors @('[ping] exit') -TimeoutSeconds 15 -MaxAttempts 2 - $logp = Read-QemuLogText $s.LogPath - if ($h2 -and $logp.Contains('[shell] path: tool returned')) { - Add-TestCheck $result 'ping.elf: Esc 退出返回 shell' 'PASS' - } else { - Add-TestCheck $result 'ping.elf: Esc 退出返回 shell' 'FAIL' 'missing [ping] exit / path: tool returned' - } - } else { - Add-TestCheck $result 'ping.elf: PATH 加载启动([ping] boot)' 'FAIL' 'missing [ping] boot' - } - - # --- editor:BUG-002 已修复(g_run_image 扩至 1MB),run 加载 → 按键插入 → Esc 退出 --- - $h = Invoke-ShellCmdRetry -Session $s -Text 'run EDITOR.ELF' -Anchors @('[editor] boot') -TimeoutSeconds 30 - if ($h) { - Add-TestCheck $result 'editor.elf: run 加载启动([editor] boot,BUG-002 修复后行为)' 'PASS' - Start-Sleep -Seconds 1 - Send-QemuText -Session $s -Text 'abc' -DelayMs 150 # 编辑模式按键插入(驱动键盘路径,无串口锚点) - $h2 = Send-EscRetry -Session $s -Anchors @('[editor] exit') -TimeoutSeconds 15 -MaxAttempts 2 - $loge = Read-QemuLogText $s.LogPath - if ($h2 -and $loge.Contains('[shell] run: tool returned')) { - Add-TestCheck $result 'editor.elf: Esc 退出返回 shell([editor] exit)' 'PASS' - } else { - Add-TestCheck $result 'editor.elf: Esc 退出返回 shell([editor] exit)' 'FAIL' 'missing [editor] exit / run: tool returned' - } - } else { - Add-TestCheck $result 'editor.elf: run 加载启动([editor] boot,BUG-002 修复后行为)' 'FAIL' 'missing [editor] boot' - } - - # --- fileman:BUG-003/008 修复后键盘可用;方向键移动选区 + Esc 退出 --- - $h = Invoke-ShellCmdRetry -Session $s -Text 'run FILEMAN.ELF' -Anchors @('[fileman] boot') -TimeoutSeconds 30 - if ($h) { - Add-TestCheck $result 'fileman.elf: run 加载启动(未复现 QEMU 退出)' 'PASS' - Start-Sleep -Seconds 2 # 等 da_mouse_init + 目录加载 + 首帧渲染 - Send-QemuKeys -Session $s -Keys @('down', 'down', 'up') -DelayMs 200 # 选区移动(仅帧缓冲,驱动键盘路径) - $h2 = Send-EscRetry -Session $s -Anchors @('[fileman] exit') -TimeoutSeconds 15 -MaxAttempts 2 - $logfm = Read-QemuLogText $s.LogPath - if ($h2 -and $logfm.Contains('[shell] run: tool returned')) { - Add-TestCheck $result 'fileman.elf: 方向键 + Esc 退出返回 shell(BUG-003/008 修复后键盘路径)' 'PASS' - } else { - Add-TestCheck $result 'fileman.elf: 方向键 + Esc 退出返回 shell(BUG-003/008 修复后键盘路径)' 'FAIL' 'missing [fileman] exit / run: tool returned' - } - } else { - Add-TestCheck $result 'fileman.elf: run 加载启动(未复现 QEMU 退出)' 'FAIL' 'missing [fileman] boot' - } - - # --- browser:启动 + Esc 退出 --- - $h = Invoke-ShellCmdRetry -Session $s -Text 'run BROWSER.ELF' -Anchors @('[browser] boot') -TimeoutSeconds 30 - if ($h) { - Add-TestCheck $result 'browser.elf: run 加载启动' 'PASS' - Start-Sleep -Seconds 2 - $h2 = Send-EscRetry -Session $s -Anchors @('[browser] exit') -TimeoutSeconds 15 -MaxAttempts 2 - $logbr = Read-QemuLogText $s.LogPath - if ($h2 -and $logbr.Contains('[shell] run: tool returned')) { - Add-TestCheck $result 'browser.elf: Esc 退出返回 shell([browser] exit)' 'PASS' - } else { - Add-TestCheck $result 'browser.elf: Esc 退出返回 shell([browser] exit)' 'FAIL' 'missing [browser] exit / run: tool returned' - } - } else { - Add-TestCheck $result 'browser.elf: run 加载启动' 'FAIL' 'missing [browser] boot' - } - - # --- Esc 回 DSK → cmd(AUTOEXEC) → desktop --- - # 只重试到中间锚点 '[shell] esc -> return to DSK' 出现即停, - # 避免多余 Esc 漏进 cmd/desktop;desktop ready 纯被动等待。 - $h = Send-EscRetry -Session $s -Anchors @('[shell] esc -> return to DSK') -TimeoutSeconds 20 -MaxAttempts 2 - $hitDesk = Wait-QemuLog -Session $s -Patterns @('desktop ready') -TimeoutSeconds 150 - $logf = Read-QemuLogText $s.LogPath - Assert-QemuLog $result $logf -Ordered @( - '[shell] esc -> return to DSK', - '[DSK] shell auto-test returned' - ) -MustNotContain @('[PANIC]') - if ($hitDesk) { Add-TestCheck $result '链路: Esc → cmd → desktop ready' 'PASS' } - else { Add-TestCheck $result '链路: Esc → cmd → desktop ready' 'FAIL' 'no desktop ready' } - } - } finally { - Stop-QemuSession $s - } - - # ================= MANUAL / 引用项 ================= - Add-TestCheck $result 'ping.elf: ICMP reply/RTT 与四报文统计' 'MANUAL' 'reply/统计输出仅帧缓冲(无串口锚点);10.0.2.2 输入与 [ping] boot/exit 链已自动化' - Add-TestCheck $result 'editor.elf: 编辑内容渲染与保存' 'MANUAL' '编辑区/保存结果仅帧缓冲与磁盘(无串口锚点);boot/按键插入/exit 链已自动化' - Add-TestCheck $result 'fileman 浏览内容(列表渲染/进入子目录)' 'MANUAL' '列表渲染仅帧缓冲(无串口锚点);方向键/Esc 键盘路径已自动化' - Add-TestCheck $result 'FirstInit 向导(程序 1)' 'MANUAL' '由 boot-regression.ps1 B1 自动化覆盖(monitor 按键填表)' - Add-TestCheck $result 'login(程序 2)' 'SKIP' 'FUCK [dsk] skip_login=1 跳过;USER.CONF 解密路径需人工验证' - Add-TestCheck $result 'desktop 双击/窗口拖动/任务栏(程序 3)' 'MANUAL' '无串口锚点,需人工或 screendump 验证' - Add-TestCheck $result 'cmd pe 兼容(程序 5)' 'MANUAL' '由 pe-compat.ps1 自动化覆盖' - Add-TestCheck $result 'netman(程序 6)' 'MANUAL' '由 network-e2e.ps1 场景 N-A 自动化覆盖(netman 仅首启调度)' - Add-TestCheck $result 'mouseInit(程序 7)' 'MANUAL' '由 boot-regression.ps1 B1 首启链路覆盖' - Add-TestCheck $result 'curl.elf HTTP(程序 11)' 'MANUAL' '由 network-e2e.ps1 场景 N-B 自动化覆盖(主机侧 HTTP 服务器断言)' -} finally { - Restore-DeshabScenario -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 } diff --git a/tests/qemu/x86emu32-coverage.ps1 b/tests/qemu/x86emu32-coverage.ps1 deleted file mode 100644 index f79bebb..0000000 --- a/tests/qemu/x86emu32-coverage.ps1 +++ /dev/null @@ -1,169 +0,0 @@ -#requires -Version 5.1 -<# -.SYNOPSIS - x86emu32-coverage — x86emu32 解释器覆盖率(docs/RE/test-cases/x86emu32-coverage.md) - -.DESCRIPTION - 两层验证: - - 1) 静态枚举(源码侧): - 解析 CODE/UTSM/pe/x86emu32.c,统计 switch case 0xNN 标签数 - (= 已实现操作码处理分支),并校验 hello32.exe 头 - (machine=0x14C, optional magic=0x10B, 确为 PE32 目标样本)。 - - 2) 动态执行(QEMU 侧): - 正常启动 firstInit="1\n0" → cmd.elf,AUTOEXEC.BAT 自动执行: - pe HELLO32.EXE → PE32 走 x86emu32 解释器 - exit → 返回 DSK → desktop - 断言锚点(BUG-20260801-007 已修复,全链路硬断言): - [cmd] PE run begin / [cmd] cmdline: HELLO32.EXE - [PE] running PE32 via x86emu32 (解释器接管证据) - Hello from PE32! (解释执行 + shim IAT 拦截证据) - [cmd] PE run end / [DSK] cmd.elf returned - 已知系统侧阻塞(2026-08-04 monitor 取证定案):末锚点当前必缺失—— - cmd run_autoexec 共享 f32_data 缓冲被首行 pe 文件加载覆盖,exit 行 - 丢失,cmd 停留交互主循环(无异常、无三重故障,guest 存活轮询键盘)。 - 解释器与 shim 链路本身无恙;待 cmd 侧修复后末锚点自动转绿。 - 注意:[PE] PE32 (32-bit) 仅 pe_load_image(PE32+ 原生路径)输出, - 解释器路径(pe32_load_into)不打印该行,不可作锚点。 - 禁止锚点: - [PE32] emulator error / [PE] unimplemented import / - [PE] unimplemented API called / [PANIC] - (出现 unimplemented 即说明 hello32 触到未实现指令/导入,覆盖率倒退) - - 历史:BUG-20260801-007(shim fn32 全缺,fn64 回退把 guest 指针 - 0x401017 当 host 指针解引用 → #PF → 三重故障)曾截断于首个 shim - 调用;修复后全链路可断言。故障在 host 侧 shim 而非解释器: - 0x68 push / FF /2 call [disp32] / IAT 合成地址拦截链路均正确, - 故"无 emulator error"仍为有效覆盖证据。 - - 指令级逐条覆盖(文档矩阵 test_arith/test_logic/test_control/test_string) - 依赖尚未存在的分类测试 PE,标 MANUAL。 - - WHPX 已知问题:guest 在 PE 兼容层执行阶段可能触发 - "Unexpected VP exit code 4" 直接终止 QEMU(WHPX 平台限制)。 - 检测到该特征时自动以 -Accel tcg 重跑一轮(可用 -Accel tcg 直接指定)。 - - 退出码:全部通过 0,任一 FAIL 1。 -#> -param( - [switch]$Build, - [string]$Accel = 'whpx' -) -$ErrorActionPreference = 'Stop' -. (Join-Path $PSScriptRoot 'lib\QemuTest.ps1') - -Initialize-QemuTest -Build:$Build -$result = New-TestCaseResult 'x86emu32-coverage' - -# ================= 静态枚举 ================= -$emuSrc = Join-Path $script:RepoRoot 'CODE\UTSM\pe\x86emu32.c' -$src = Get-Content $emuSrc -Raw -$caseCount = ([regex]::Matches($src, 'case\s+0x[0-9A-Fa-f]+')).Count -Add-TestCheck $result "静态: x86emu32.c 已实现操作码分支 case 0xNN × $caseCount" 'PASS' '仅枚举计数,供覆盖率分母参考' -Add-TestNote $result "覆盖率分母: x86emu32.c 共 $caseCount 个 case 0xNN 操作码分支(含 0F 扩展页)" - -$h32 = [System.IO.File]::ReadAllBytes((Join-Path $script:SystemDir 'bin\hello32.exe')) -$peOff = [BitConverter]::ToInt32($h32, 0x3C) -$mach = [BitConverter]::ToUInt16($h32, $peOff + 4) -$optm = [BitConverter]::ToUInt16($h32, $peOff + 24) -if ($mach -eq 0x14C -and $optm -eq 0x10B) { - Add-TestCheck $result '静态: hello32.exe 为 PE32 (machine=0x14C, magic=0x10B)' 'PASS' -} else { - Add-TestCheck $result '静态: hello32.exe 为 PE32 (machine=0x14C, magic=0x10B)' 'FAIL' ("actual machine=0x{0:X} magic=0x{1:X}" -f $mach, $optm) -} - -# ================= 动态执行 ================= -# 单轮执行:正常启动 → cmd → AUTOEXEC(pe HELLO32.EXE / exit) → 返回 DSK -# 返回 $true=完整跑完;$false=会话提前退出(WHPX 终止等) -function Invoke-EmuRun { - param([Parameter(Mandatory)][string]$AccelMode) - Set-DeshabFirstInit -First 1 -DevMode 0 - Set-DeshabAutoexec -Lines @('pe HELLO32.EXE', 'exit') - - $timeout = 180 - if ($AccelMode -eq 'tcg') { $timeout = 420 } # TCG 全模拟显著更慢 - $s = Start-QemuSession -Name "x86emu32-$AccelMode" -Accel $AccelMode -MonitorPort 45508 - try { - $script:RunHit = Wait-QemuLog -Session $s -Patterns @('[DSK] cmd.elf returned', '[PANIC]') -TimeoutSeconds $timeout - Start-Sleep -Seconds 2 - $script:RunLog = Read-QemuLogText $s.LogPath - $script:RunLogPath = $s.LogPath - if (-not $script:RunHit) { return $false } - return $true - } finally { - Stop-QemuSession $s - } -} - -$script:RunLog = '' -$script:RunLogPath = '' -$script:RunHit = $null - -try { - $usedAccel = $Accel - $ok = Invoke-EmuRun -AccelMode $Accel - if (-not $ok -and $Accel -eq 'whpx' -and $script:RunLog.Contains('WHPX: Unexpected VP exit')) { - Add-TestNote $result "WHPX 在 PE 兼容层执行处终止 QEMU(Unexpected VP exit,WHPX 平台限制),自动以 TCG 重跑" - $usedAccel = 'tcg' - $ok = Invoke-EmuRun -AccelMode 'tcg' - } - $log = $script:RunLog - $result.LogPath = $script:RunLogPath - Add-TestNote $result "加速方案: $usedAccel" - $hit = $script:RunHit - - # ---- 解释器接管链(BUG-007 已修复,全链路可稳定断言) ---- - Assert-QemuLog $result $log -Ordered @( - '[cmd] running AUTOEXEC.BAT', - '[cmd] PE run begin', - '[cmd] cmdline: HELLO32.EXE', - '[PE] running PE32 via x86emu32' - ) -MustNotContain @( - '[PANIC]', - '[PE32] emulator error', - '[PE32] emu init failed', - '[PE] unimplemented import', - '[PE] unimplemented API called' - ) - - # ---- shim 输出与完整返回链:BUG-007/006 已修复,转硬断言(回归守护) ---- - if ($log.Contains('Hello from PE32!')) { - Add-TestCheck $result '动态: 解释执行 + shim IAT 拦截输出(Hello from PE32!)' 'PASS' - } else { - Add-TestCheck $result '动态: 解释执行 + shim IAT 拦截输出(Hello from PE32!)' 'FAIL' 'missing Hello from PE32!(BUG-20260801-007 回归?)' - } - if ($log.Contains('[cmd] PE run end') -and $log.Contains('[DSK] cmd.elf returned')) { - Add-TestCheck $result '动态: ExitProcess 回 cmd,AUTOEXEC 跑完返回 DSK' 'PASS' - } else { - # 已知系统侧阻塞(2026-08-04 诊断定案,非 BUG-006/007 回归): - # cmd run_autoexec 的 data/size 直指共享静态缓冲 f32_data(fat32_io.h), - # 首行 `pe HELLO32.EXE` 加载 PE 即覆盖剩余脚本,exit 行丢失 → cmd 停留 - # 交互主循环(monitor 取证 RIP=cmd.elf 内 inb 0x60 轮询点,无异常投递)。 - # pe-compat 会话 C(pe NONEXIST.EXE + exit)通过反证:文件未找到不覆盖 - # 缓冲时 exit 返回链正常。待 cmd 侧修复(脚本行先拷入私有缓冲再执行)。 - Add-TestCheck $result '动态: ExitProcess 回 cmd,AUTOEXEC 跑完返回 DSK' 'FAIL' 'missing [DSK] cmd.elf returned(已知系统 bug:cmd run_autoexec 共享 f32_data 被 pe 文件加载覆盖,exit 行丢失)' - } - - # 解释器侧覆盖判定:hello32 全指令流(含 shim IAT 拦截与参数读取) - # 均正确执行——无 emulator error / unimplemented 日志即解释器覆盖率 - # 不记倒退。 - if (-not $log.Contains('[PE] unimplemented') -and -not $log.Contains('[PE32] emulator') -and $log.Contains('[PE] running PE32 via x86emu32')) { - Add-TestCheck $result '动态: hello32 已执行指令全部命中已实现集(无 unimplemented/emulator error)' 'PASS' - } else { - Add-TestCheck $result '动态: hello32 已执行指令全部命中已实现集(无 unimplemented/emulator error)' 'FAIL' 'unimplemented/emulator error anchor present or interpreter not reached' - } - - # ---- 指令级分类覆盖(依赖尚不存在的分类测试 PE) ---- - foreach ($t in @('数据传送簇 (MOV/PUSH/POP 逐操作数形态)', '算术簇 (ADD/SUB/INC/DEC/MUL/DIV 逐标志验证)', - '逻辑簇 (AND/OR/XOR/NOT)', '控制流簇 (JMP/CALL/RET/Jcc)', - '字符串簇 (MOVSB/STOSB/REP 前缀)', '堆栈帧 (ENTER/LEAVE) + INT3 断点路径')) { - Add-TestCheck $result "指令级: $t" 'MANUAL' '需 CODE/tools/pe-samples/ 下分类测试 PE(test_arith.exe 等),当前未构建;hello32 仅综合冒烟' - } - Add-TestNote $result "pe-compat.ps1 已覆盖 PE32+ 原生对照组(HELLO64.EXE)与 PE 服务 ABI 锚点" -} finally { - Restore-DeshabScenario -} - -Write-TestCaseResult $result -if ($result.Status -eq 'FAIL') { exit 1 } else { exit 0 }