From 3a497e38ecf0f6b03a017abd3514de504c6cebfd Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 16 Oct 2026 15:26:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(usb):=20=E6=8E=A7=E5=88=B6=E4=BC=A0?= =?UTF-8?q?=E8=BE=93=E7=9F=AD=E8=AF=BB=E5=88=A4=E5=A4=B1=E8=B4=A5+?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E7=AC=A6=E9=93=BE=E5=AE=8C=E6=95=B4=E6=80=A7?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9BCCB=20=E8=AF=BB=E6=94=B9=E5=86=99?= =?UTF-8?q?=E5=B1=8F=E8=94=BD=20IRQ1=EF=BC=9B=E6=B8=85=E7=90=86=E5=B7=B2?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E6=8E=A2=E9=92=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uhci.control_transfer:除错误位外再比对 DATA TD 的 Actual Length(bit10:0)。短读时错误位可能全 0(QEMU/硬件不把短包当错误),只查错误位会漏 —— 现场是 USB 鼠标的 25 字节配置描述符只落下前 9 字节 ⇒ 接口解析为 0 ⇒ [usb_mse] no USB mouse found。 usb._parse_config 改为返回实际走完的偏移,enumerate_port 要求恰好走完 total_len,否则重读、仍不完整即报错返回(fail-fast,不带着半截描述符继续);同时删除两条探针。 keyboard.init/mouse.init 读-改-写 CCB 全程 pic.setMask(1):读 CCB 的 0x20 会把 CCB 值放进 0x60 输出缓冲并拉高 IRQ1,IRQ1 handler 会把它当扫描码抢走(现场 sc=0x47),使回读拿到残留值。改为只在校验失败时打印错误,去掉全部 [kbd] 探针。 清理已验证调试输出:idt 的 IRQ1 handler 探针及其计数器、desktop 的 [dbg] back_fb/row_buf/bg_row_buf、main 的 [DBG] LAPIC/idt32/swint/pic_isr 与 [DB] BootInfo 转储、timer 的 [PIT] 与自旋打印及随之无用的 import。 --- .../Kernel/drivers/input/keyboard/keyboard.vp | 47 ++++++++++----- VKernel/Kernel/drivers/input/mouse/mouse.vp | 5 ++ VKernel/Kernel/drivers/usb/uhci.vp | 8 +++ VKernel/Kernel/drivers/usb/usb.vp | 57 ++++++++++--------- VKernel/Kernel/intr/idt.vp | 13 +---- VKernel/Kernel/main.vp | 45 --------------- VKernel/Kernel/platform/pch/timer.vp | 23 +------- VKernel/Kernel/services/desktop.vp | 9 --- 8 files changed, 77 insertions(+), 130 deletions(-) diff --git a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp index 69b3481..acccaa7 100644 --- a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp +++ b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp @@ -3,7 +3,6 @@ import intr.idt as idt import drivers.serial.uart.serial as serial import platform.pch.pic as pic import string -import viperlib import t, c @@ -53,6 +52,14 @@ scancode_set1_shift: [128]char = [ '\0' ] +# 清空 8042 输出缓冲(上电自检字节 / 遗留 ACK / 读 CCB 时压入的值) +def _drain_outbuf(): + drain: uint32 = 0 + while (asm.inb(KBD_STATUS) & 0x01) != 0: + asm.inb(KBD_DATA) + drain += 1 + if drain > 1000: break + def _wait_read(): timeout: uint32 = 100000 while timeout > 0: @@ -74,11 +81,6 @@ def irq_handler() -> int: if (st & 0x20) != 0: return 0 scancode: uint8 = asm.inb(KBD_DATA) - # 按键验证:把 IRQ1 收到的扫描码打到串口(高位 0x80 = 释放), - # 用来确认"键盘中断是否到达"这一环。 - _kmsg: [48]char - viperlib.snprintf(&_kmsg, 48, "[kbd] sc=0x%x\n", int(scancode)) - serial.puts(&_kmsg) if buf_count < KBD_BUF_SIZE: buffer[buf_tail] = scancode buf_tail = buf_tail + 1 @@ -123,12 +125,7 @@ def init(): _kbd_inited = 1 # 清空控制器输出缓冲(上电自检字节 / 上一次遗留的 ACK 等) - drain: uint32 - drain = 0 - while (asm.inb(KBD_STATUS) & 0x01) != 0: - asm.inb(KBD_DATA) - drain += 1 - if drain > 1000: break + _drain_outbuf() # 读-改-写 CCB(与 mouse.init 同款写法): # bit0=1 使能键盘 IRQ1;bit6=1 启用 scancode set1 翻译;bit4=0 使能键盘时钟。 @@ -136,11 +133,18 @@ def init(): # 经常失败(键盘数据串入 CCB 读取、回读为 0),0xAE 便恢复不了 ⇒ 键盘接口停在 # 禁用态 ⇒ 之后按键完全不产生任何字节/IRQ1(现场:ki 恒为 2/3、无任何键盘字节)。 # 鼠标没有这个序列,所以它一直正常 —— 键盘对齐鼠标的写法即可。 + # + # 全程屏蔽 IRQ1:读 CCB 的 0x20 命令会把 CCB 值放进 0x60 输出缓冲并拉高 IRQ1, + # IRQ1 handler 一旦运行就会把这个字节当扫描码读走(现场:handler 装上后读到 + # sc=0x47,正是鼠标清 bit5 后的 CCB),这里的 inb(0x60) 便只能拿到残留值 ⇒ 可能把 + # 错误的 CCB 写回。屏蔽期 8259 仍会锁存请求,所以解屏蔽前先清空输出缓冲, + # 避免解屏蔽瞬间摄入一个陈旧字节。 + pic.setMask(1) + _wait_write() asm.outb(KBD_COMMAND, 0x20) _wait_read() ccb: uint8 = asm.inb(KBD_DATA) - ccb = ccb | 0x01 ccb = ccb | 0x40 ccb = ccb & 0xEF @@ -150,12 +154,25 @@ def init(): _wait_write() asm.outb(KBD_DATA, ccb) - # 使能键盘扫描(0xF4),并读走 ACK + # 回读校验:CCB 没写进去 ⇒ 扫描不使能 ⇒ 按键永不产生字节(必须报错,不能静默继续) + _wait_write() + asm.outb(KBD_COMMAND, 0x20) + _wait_read() + rb: uint8 = asm.inb(KBD_DATA) + if rb != ccb: + serial.puts("[kbd] CCB write verify failed\n") + pic.clearMask(1) + return + + # 使能键盘扫描(0xF4):非 0xFA ⇒ 命令没送达键盘 _wait_write() asm.outb(KBD_DATA, 0xF4) _wait_read() - asm.inb(KBD_DATA) + ack: uint8 = asm.inb(KBD_DATA) + if ack != 0xFA: + serial.puts("[kbd] scan-enable not ACKed\n") + _drain_outbuf() # 解屏蔽 IRQ1(对齐 mouse.init 的 pic.clearMask(12)),再装 handler pic.clearMask(1) idt.irqInstallHandler(1, irq_handler, "keyboard") diff --git a/VKernel/Kernel/drivers/input/mouse/mouse.vp b/VKernel/Kernel/drivers/input/mouse/mouse.vp index aa730bd..f3831ca 100644 --- a/VKernel/Kernel/drivers/input/mouse/mouse.vp +++ b/VKernel/Kernel/drivers/input/mouse/mouse.vp @@ -122,6 +122,10 @@ def get_buttons() -> uint8: def init(): _wait_write() asm.outb(MOUSE_COMMAND, 0xA8) + # 读-改-写 CCB 期间屏蔽 IRQ1:与 keyboard.init 同理 —— 0x20 读 CCB 会把 CCB 值放进 + # 0x60 输出缓冲并拉高 IRQ1,键盘 ISR 一旦运行就会把它当扫描码抢走(现场:sc=0x47 + # 正是这里清 bit5 后的 CCB),于是下面的 inb(0x60) 只能拿到残留值 ⇒ 可能把错误 CCB 写回。 + pic.setMask(1) _wait_write() asm.outb(MOUSE_COMMAND, 0x20) _wait_read() @@ -132,6 +136,7 @@ def init(): asm.outb(MOUSE_COMMAND, 0x60) _wait_write() asm.outb(MOUSE_DATA, status) + pic.clearMask(1) _write_cmd(0xF6) # 分辨率设为 8 counts/mm:0xF6(Set Defaults) 会把它重置为最粗的 4 counts/mm, # 于是每个计数对应的位移偏大。设细一档后光标更稳、不易“飞”。 diff --git a/VKernel/Kernel/drivers/usb/uhci.vp b/VKernel/Kernel/drivers/usb/uhci.vp index 4f379cc..739c9e3 100644 --- a/VKernel/Kernel/drivers/usb/uhci.vp +++ b/VKernel/Kernel/drivers/usb/uhci.vp @@ -55,6 +55,10 @@ define TD_CTRL_CERR_MASK = 0x18000000 define TD_CTRL_ERR_MASK = 0x00760000 # bit19 NAK:C_ERR(3) 用尽后仍被 NAK ⇒ 设备一直不给数据 define TD_CTRL_NAK_MASK = 0x00080000 +# Actual Length(bit10:0,编码为 n-1):IN 传输"短读"时错误位可能全 0(QEMU/硬件不把短包 +# 当错误),只查错误位会漏 ⇒ 必须比对实际字节数(现场:USB 鼠标的 25 字节配置描述符只落下 +# 前 9 字节,iface 解析为 0 ⇒ usb_mse 找不到鼠标)。 +define TD_CTRL_ACTLEN_MASK = 0x000007FF define TD_TOKEN_SETUP = 0x2D define TD_TOKEN_IN = 0x69 @@ -456,6 +460,10 @@ def control_transfer(dev_addr: uint8, endpoint: uint8, ls: int, max_pkt: uint8, if e_data != 0: fail = 0 - 20 - e_data which = 1 + elif data_in != 0 and ((st_data & TD_CTRL_ACTLEN_MASK) + 1) < uint32(data_len): + # 短读:实际字节数少于请求(ACTLEN 编码为 n-1)。错误位可能全 0,不能当成功。 + fail = 0 - 20 - 4 + which = 1 e_status: int = _td_error(status_td) if e_status != 0: fail = 0 - 30 - e_status diff --git a/VKernel/Kernel/drivers/usb/usb.vp b/VKernel/Kernel/drivers/usb/usb.vp index 7767ca0..b075bcd 100644 --- a/VKernel/Kernel/drivers/usb/usb.vp +++ b/VKernel/Kernel/drivers/usb/usb.vp @@ -139,9 +139,18 @@ def hid_set_idle(dev_addr: uint8, ls: int, max_pkt: uint8, iface: uint8, duratio _setup_packet(0x21, HID_REQ_SET_IDLE, uint16(duration) << 8, uint16(iface), 0, &setup) return _send_control(dev_addr, ls, max_pkt, &setup, None, 0, 0) -def _parse_config(dev: usb_device*, config_buf: rawptr, total_len: uint16): +def _parse_config(dev: usb_device*, config_buf: rawptr, total_len: uint16) -> int: offset: int = 0 found_hid: int = 0 + # 每次解析都从干净状态开始:调用方可能在"描述符没读全"时重试,残留的 iface/ep 字段 + # 会让上一次的半截结果串到这一次(例如 iface_class=3 但 ep_in=0 的不一致组合)。 + dev.iface_class = 0 + dev.iface_subclass = 0 + dev.iface_protocol = 0 + dev.iface_num = 0 + dev.ep_in = 0 + dev.ep_in_max = 0 + dev.ep_in_interval = 0 while offset < int(total_len): p: uint8* = config_buf + uint64(offset) desc_len: uint8 = *p @@ -167,6 +176,8 @@ def _parse_config(dev: usb_device*, config_buf: rawptr, total_len: uint16): dev.ep_in_interval = ep.bInterval found_hid = 0 offset = offset + int(desc_len) + # 返回实际走完的偏移:调用方据此判断描述符链是否完整(== total_len 才算读全) + return offset def enumerate_port(port: int) -> int: global _device_count @@ -204,12 +215,6 @@ def enumerate_port(port: int) -> int: desc: usb_dev_desc* = dev_buf max_pkt: uint8 = desc.bMaxPacketSize0 if max_pkt < 8: max_pkt = 8 - # 探针:设备描述符只要读回 18/8 字节,bLength 必为 18、bDescriptorType 必为 1。 - # 全 0 ⇒ 控制传输的 DATA 阶段没落盘(而非解析问题)。 - _db0: [96]char - viperlib.snprintf(&_db0, 96, "[usb] devdesc blen=%d btype=%d maxpkt0=%d\n", - int(desc.bLength), int(desc.bDescriptorType), int(desc.bMaxPacketSize0)) - serial.puts(&_db0) new_addr: uint8 = uint8(_device_count + 1) result = set_address(0, ls, max_pkt, new_addr) @@ -256,27 +261,25 @@ def enumerate_port(port: int) -> int: total_len: uint16 = cfg.wTotalLength if total_len > 256: total_len = 256 - result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, total_len) - for retry in range(3): - if result == 0: break - timer.timer_msleep(50) - result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, total_len) - if result != 0: - mm.free(config_buf) - mm.free(dev_buf) - return -8 - - _parse_config(dev, config_buf, total_len) + # 读完整配置描述符:不能只看控制传输是否"报成功"——短读时错误位可能全 0,缓冲区里只 + # 落下前一段(现场:USB 鼠标的 25 字节描述符只到前 9 字节),描述符链会在中途遇到 + # bLength==0 提前结束 ⇒ 接口描述符解析不到 ⇒ iface_class=0 ⇒ 驱动找不到设备。 + # 因此用"描述符链是否恰好走完 total_len"来判定,不满足就重读;重读仍不完整直接报错 + # (fail-fast,不带着半截描述符继续)。 + result = -1 + consumed: int = 0 + for retry in range(3): + result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, total_len) + if result == 0: + consumed = _parse_config(dev, config_buf, total_len) + if consumed == int(total_len): break + timer.timer_msleep(50) + if result != 0 or consumed != int(total_len): + serial.puts("[usb] config descriptor incomplete\n") + mm.free(config_buf) + mm.free(dev_buf) + return -8 - # 探针:配置描述符读回后,wTotalLength 应为 0x22/0x23、首字节应为 09 02(bLength=9, CONFIG)。 - # total=0 或首字节为 0 ⇒ DATA 阶段没传回来;有内容但 iface 仍为 0 ⇒ 解析/布局问题。 - _cb: uint8* = config_buf - _db1: [128]char - viperlib.snprintf(&_db1, 128, "[usb] cfgdesc total=%d b0=%02X t0=%02X iface=%02X proto=%02X ep=%d\n", - int(total_len), int(_cb[0]), int(_cb[1]), - int(dev.iface_class), int(dev.iface_protocol), int(dev.ep_in)) - serial.puts(&_db1) - result = set_configuration(new_addr, ls, max_pkt, cfg.bConfigurationValue) if result != 0: mm.free(config_buf) diff --git a/VKernel/Kernel/intr/idt.vp b/VKernel/Kernel/intr/idt.vp index 76f8be4..1dffc39 100644 --- a/VKernel/Kernel/intr/idt.vp +++ b/VKernel/Kernel/intr/idt.vp @@ -198,16 +198,7 @@ export def isr31_handler(): isrCommonhandler(31) export def isr32_handler(): timer.timer_handler() export def isr33_handler(): - global _kbd_irq_seen handler: irq_handler_t = irqGetHandler(1) - # 键盘验证:IRQ1 是否真的到达 IDT,以及 handler 是否已安装。 - # handler=0x0 → 到达了但没装上(键盘仍不可用,问题在安装侧); - # 整行不出现 → IRQ1 根本没到(问题在控制器/设备侧)。只打前 8 次。 - if _kbd_irq_seen < 8: - _kbd_irq_seen += 1 - _kb: [64]char - viperlib.snprintf(&_kb, 64, "[kbd] irq1 handler=%lx\n", uint64(handler)) - serial.puts(&_kb) if handler: __asm__(f"""call {asminp(handler, ASM_DESCR.REG_ANY)}""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, @@ -418,9 +409,7 @@ def init_irqs(): setGate(46, uint64(isr46), CODE_SEG, 0x8E) setGate(47, uint64(isr47), CODE_SEG, 0x8E) -irq_handlers: static [16]irq_handler_t = [None] -# 键盘验证:IRQ1 到达次数(仅用于打印前若干次) -_kbd_irq_seen: static int = 0 +irq_handlers: static [16]irq_handler_t = [None] def irqInstallHandler(irq: int, handler: irq_handler_t, name: str) -> int: global irq_handlers diff --git a/VKernel/Kernel/main.vp b/VKernel/Kernel/main.vp index 4feb5df..da61a7e 100644 --- a/VKernel/Kernel/main.vp +++ b/VKernel/Kernel/main.vp @@ -98,43 +98,9 @@ class ViperKernel: apic.early_init() serial.puts("[main] apic init done\n") - # [DBG] 综合诊断:判定 LAPIC MMIO 是否真正可访问/可写(用后移除) - _dbg_base: uint64 = apic.get_base() - _dbg_ver: uint32 = apic.read(0x030) # LAPIC_VERSION:真 LAPIC 应为 0x50014 - _dbg_svr: uint32 = apic.read(0x0F0) # LAPIC_SVR:init 已写 0x1FF - apic.write(0x350, 0x700) # 再次写 LINT0,验证写粘性 - _dbg_lint0: uint32 = apic.read(0x350) - _dbg_feat: uint32 = cpu.detect_features_static() - _dbg_buf: [160]char - viperlib.snprintf(&_dbg_buf, 160, - "[DBG] base=0x%lx ver=0x%x svr=0x%x lint0=0x%x feat=0x%x apic=%d\n", - _dbg_base, _dbg_ver, _dbg_svr, _dbg_lint0, _dbg_feat, - 1 if (_dbg_feat & cpu.CPU_FEATURE_APIC) else 0) - serial.puts(&_dbg_buf) - - # [DBG] 读回 IDT 门 32,验证 isr32 分发地址是否正确安装(用后移除) - _g32: uint64 = (uint64(intr.idt.idt[32].base_low) | (uint64(intr.idt.idt[32].base_middle) << 16) | (uint64(intr.idt.idt[32].base_high) << 32)) - _g32buf: [96]char - viperlib.snprintf(&_g32buf, 96, "[DBG] idt32 base=0x%lx sel=0x%x type=0x%x\n", _g32, intr.idt.idt[32].selector, intr.idt.idt[32].type_attr) - serial.puts(&_g32buf) - asm.sti() serial.puts("[main] sti done\n") - # [DBG] 软件触发 int 0x20:门 32 有效则分发到 isr32→timer_handler 打印 H 并返回; - # 门无效则 #GP(isrCommonhandler 打印 [GP] err=0x20),一次性区分分发路径(用后移除) - serial.puts("[DBG] before swint\n") - __asm__("int 0x20", op=[ASM_DESCR.CLOBBER_MEMORY]) - serial.puts("[DBG] after swint\n") - - # [DBG] 读 PIC1 ISR:bit0 置位说明 PIT IRQ0 已到达 PIC 并被响应(用后移除) - asm.outb(0x20, 0x0B) # OCW3 读 ISR - asm.io_wait() - _pic_isr: uint8 = asm.inb(0x20) - _isrbuf: [48]char - viperlib.snprintf(&_isrbuf, 48, "[DBG] pic_isr=0x%x\n", _pic_isr) - serial.puts(&_isrbuf) - timer.timer_msleep(100) serial.puts("[main] msleep done\n") rtc.rtc_init() @@ -154,22 +120,11 @@ class ViperKernel: fb_format: int = int(BootInfo.framebuffer_format) fb_size: uint64 = BootInfo.framebuffer_size _SetFrameShadow(fb, width, height, pitch) - # [DBG] 打印 BootInfo 指针与 memmap 原始字段,定位全 0 根因(用后移除) _dbbuf: [140]char viperlib.snprintf(&_dbbuf, 140, "[main] BootInfo=0x%lx mmap=0x%lx mmsz=%lu mmd=%lu fb=0x%lx\n", uint64(BootInfo), BootInfo.MemmapAddr, BootInfo.MemmapSize, BootInfo.MemmapDescSize, BootInfo.framebuffer_addr) serial.puts(&_dbbuf) - # [DBG] 转储 BootInfo 地址处原始 88 字节(用后移除) - _dbraw: [16]char - for _dbi in range(88): - if (_dbi % 16) == 0: - viperlib.snprintf(&_dbraw, 16, "\n[DB] %04x:", _dbi) - serial.puts(&_dbraw) - _dbv: uint8 = *(uint8*(uint64(BootInfo) + uint64(_dbi))) - viperlib.snprintf(&_dbraw, 16, " %02x", _dbv) - serial.puts(&_dbraw) - serial.puts("\n[DB] dump end\n") # 内核镜像末端(含 .bss;__bss_end 见 VKernel/linker.ld):传给 mm.init 使其在挑选 # 堆 arena 时排除整块内核镜像 —— .bss 不占文件大小,bootloader 只按文件大小预留页, diff --git a/VKernel/Kernel/platform/pch/timer.vp b/VKernel/Kernel/platform/pch/timer.vp index 1cf164d..f90e8f0 100644 --- a/VKernel/Kernel/platform/pch/timer.vp +++ b/VKernel/Kernel/platform/pch/timer.vp @@ -2,8 +2,6 @@ import platform.pch.pic as pic import platform.pch.pit as pit import sched.sched as sched import drivers.core.cpu.apic as apic -import drivers.serial.uart.serial as serial -import viperlib import asm @@ -15,7 +13,6 @@ static ticks: volatile uint64 = 0 static seconds: volatile uint32 = 0 static milliseconds: volatile uint32 = 0 static timer_running: volatile bool = False -_dbg_once: int = 0 def timer_init(): global ticks, seconds, milliseconds, timer_running @@ -26,21 +23,9 @@ def timer_init(): timer_running = True __asm__("mfence", op=[ASM_DESCR.CLOBBER_MEMORY]) pit.pit_init(TIMER_FREQUENCY) - # [DBG] 读 PIT 计数器验证是否在走(用后移除) - asm.outb(0x43, 0x00) # latch channel 0 - _lo: uint8 = asm.inb(0x40) - _hi: uint8 = asm.inb(0x40) - _cnt: int = int(_lo) | (int(_hi) << 8) - _cb: [40]char - viperlib.snprintf(&_cb, 40, "[PIT] cnt=%d\n", _cnt) - serial.puts(&_cb) def timer_handler(): - global ticks, seconds, milliseconds, timer_running, _dbg_once - # [DBG] 一次性打印:'H' 出现说明定时 ISR 被调用(用后移除) - if _dbg_once == 0: - _dbg_once = 1 - serial.puts("H") + global ticks, seconds, milliseconds, timer_running if not timer_running: pic.eoi(0) apic.eoi() @@ -110,13 +95,7 @@ def timer_get_time(out_seconds: uint32*, out_milliseconds: uint32*): def timer_msleep(ms: uint32): target: uint64 = ticks + (ms + MILLISECONDS_PER_TICK - 1) / MILLISECONDS_PER_TICK - _spin: uint64 = 0 while ticks < target: - # [DBG] 自旋计数:每 1M 次迭代打印 s——hlt 能唤醒说明中断触发 - # (用后移除) - _spin += 1 - if (_spin & 0xFFFFF) == 0: - serial.puts("s") asm.sti() asm.hlt() sched.Scheduler._yield() diff --git a/VKernel/Kernel/services/desktop.vp b/VKernel/Kernel/services/desktop.vp index 3777a63..7eed0eb 100644 --- a/VKernel/Kernel/services/desktop.vp +++ b/VKernel/Kernel/services/desktop.vp @@ -1957,9 +1957,6 @@ def start(fb_ptr: rawptr, w: int, h: int, pitch: int = 0) -> sched.Thread*: return None _back_fb = mm.malloc(uint64(w) * uint64(h) * 4) - _dbg_bf: [96]char - viperlib.snprintf(&_dbg_bf, 96, "[dbg] back_fb ret=0x%lx w=%d h=%d\n", uint64(_back_fb), w, h) - serial.puts(&_dbg_bf) if _back_fb: string.memset(_back_fb, 0, w * h * 4) else: @@ -1967,9 +1964,6 @@ def start(fb_ptr: rawptr, w: int, h: int, pitch: int = 0) -> sched.Thread*: return None _row_buf = mm.malloc(uint64(w) * 4) - _dbg_rb: [96]char - viperlib.snprintf(&_dbg_rb, 96, "[dbg] row_buf ret=0x%lx\n", uint64(_row_buf)) - serial.puts(&_dbg_rb) if _row_buf: string.memset(_row_buf, 0, w * 4) else: @@ -1978,9 +1972,6 @@ def start(fb_ptr: rawptr, w: int, h: int, pitch: int = 0) -> sched.Thread*: # mm.malloc(w*4) 走裸名回退生成 call @malloc(i32),形参 i64 不匹配(第 4 次 # 调用 RAX 返回值被破坏收到 0)。显式 CUInt64T 让参数按 i64 传递。 _bg_row_buf = mm.malloc(uint64(w) * 4) - _dbg_br: [96]char - viperlib.snprintf(&_dbg_br, 96, "[dbg] bg_row_buf ret=0x%lx\n", uint64(_bg_row_buf)) - serial.puts(&_dbg_br) if _bg_row_buf: desktop_init_col: uint32 = gfx.COLOR_RGB(25, 25, 35) for ri in range(w):