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。
401 lines
17 KiB
Plaintext
401 lines
17 KiB
Plaintext
from stdint import *
|
||
import bootinfo
|
||
import intr
|
||
import platform.pch as pch
|
||
import platform.pch.timer as timer
|
||
import platform.pch.rtc as rtc
|
||
import drivers.serial.uart.su8250 as su8250
|
||
import drivers.serial.uart.serial as serial
|
||
import drivers.devfs.devfs as devfs
|
||
import paging.paging as paging
|
||
import drivers.video.vesafb.vga as vga
|
||
import drivers.video.vesafb.gfx as gfx
|
||
import drivers.storage.ide.ide as ide
|
||
import drivers.fs.fat32.fat32 as fat32
|
||
import drivers.fs.fat32.fat32_types as fat32_types
|
||
import mm.mm as mm
|
||
import string
|
||
import sched.sched as sched
|
||
import sched.process as proc
|
||
import drivers.input.keyboard.keyboard as keyboard
|
||
import drivers.input.mouse.mouse as mouse
|
||
import drivers.usb.usb as usb
|
||
import drivers.core.cpu.cpu as cpu
|
||
import drivers.core.cpu.apic as apic
|
||
import viperlib
|
||
import services.keyboard as kbd_svc
|
||
import services.mouse as mse_svc
|
||
import services.desktop as desktop
|
||
import intr.syscall as syscall
|
||
import execrunner.elf as elf
|
||
import asm
|
||
|
||
|
||
kbd_tid: int = -1
|
||
mse_tid: int = -1
|
||
|
||
# 影子副本 + 校验:__init__ 的 fb/width/height/pitch 局部量曾被偶发写坏(被清零,
|
||
# 导致 desktop.start 拿到 fb=0 → 刷新把后备缓冲写到物理 0 起,覆盖内核自身)。
|
||
# 这里保存模块级影子并在关键节点比对,命中即 fail-fast 暴露写坏源,不再让损坏值被静默使用。
|
||
_ShadowFb: uint64 = 0
|
||
_ShadowW: int = 0
|
||
_ShadowH: int = 0
|
||
_ShadowPitch: int = 0
|
||
|
||
def _SetFrameShadow(fb: rawptr, w: int, h: int, pitch: int):
|
||
global _ShadowFb, _ShadowW, _ShadowH, _ShadowPitch
|
||
_ShadowFb = uint64(fb)
|
||
_ShadowW = w
|
||
_ShadowH = h
|
||
_ShadowPitch = pitch
|
||
|
||
def _CheckFrameLocals(tag: const str, fb: rawptr, w: int, h: int, pitch: int):
|
||
if uint64(fb) != _ShadowFb or w != _ShadowW or h != _ShadowH or pitch != _ShadowPitch:
|
||
b: [176]char
|
||
viperlib.snprintf(&b, 176,
|
||
"[FATAL] %s: frame locals corrupted fb=0x%lx w=%d h=%d pitch=%d (expect 0x%lx %d %d %d)\n",
|
||
tag, uint64(fb), w, h, pitch, _ShadowFb, _ShadowW, _ShadowH, _ShadowPitch)
|
||
serial.puts(&b)
|
||
while True:
|
||
sched.Scheduler._yield()
|
||
|
||
BootInfo: bootinfo.bootinfo*
|
||
|
||
class ViperKernel:
|
||
BootInfo: bootinfo.bootinfo*
|
||
VGA: vga._VGAScreenDriver*
|
||
|
||
def __init__(self):
|
||
global BootInfo
|
||
self.BootInfo = BootInfo
|
||
|
||
cpu.enable_sse()
|
||
|
||
serial.init()
|
||
serial.puts("===== ViperOS VKernel Test\n =====")
|
||
serial.puts("V2.3.8\n")
|
||
|
||
intr.gdt.init()
|
||
|
||
cpu.msr_write(cpu.IA32_KERNEL_GS_BASE, uint64(&cpu._per_cpu))
|
||
serial.puts("[main] gdt init done\n")
|
||
intr.idt.init()
|
||
syscall.init()
|
||
serial.puts("[main] syscall init done\n")
|
||
|
||
sched.Scheduler()
|
||
serial.puts("[main] sched init done\n")
|
||
|
||
timer.timer_init()
|
||
serial.puts("[main] timer init done\n")
|
||
|
||
#mouse.init()
|
||
#serial.puts("[main] mouse init done\n")
|
||
|
||
# 必须初始化 APIC:QEMU/OVMF(UEFI)下本地 APIC 已启用,PIC 外部中断
|
||
# 须经 LINT0 送达 CPU。apic.init() 将 LINT0 配为 EXTINT(取消屏蔽),
|
||
# 否则定时器中断被 APIC 吞掉 → sti 后 msleep 永远等不到 ticks 挂死。
|
||
apic.early_init()
|
||
serial.puts("[main] apic init done\n")
|
||
|
||
asm.sti()
|
||
serial.puts("[main] sti done\n")
|
||
|
||
timer.timer_msleep(100)
|
||
serial.puts("[main] msleep done\n")
|
||
rtc.rtc_init()
|
||
serial.puts("[main] rtc init done\n")
|
||
ncpu: uint32 = apic.get_cpu_count()
|
||
ncpu_cr: [32]char
|
||
viperlib.snprintf(&ncpu_cr, 32, "[main] cpu_count=%u\n", ncpu)
|
||
serial.puts(ncpu_cr)
|
||
if ncpu > 1:
|
||
apic.start_aps()
|
||
serial.puts("[main] kernel init done\n")
|
||
|
||
fb: rawptr = rawptr(BootInfo.framebuffer_addr)
|
||
width: int = int(BootInfo.framebuffer_width)
|
||
height: int = int(BootInfo.framebuffer_height)
|
||
pitch: int = int(BootInfo.framebuffer_pitch)
|
||
fb_format: int = int(BootInfo.framebuffer_format)
|
||
fb_size: uint64 = BootInfo.framebuffer_size
|
||
_SetFrameShadow(fb, width, height, pitch)
|
||
_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)
|
||
|
||
# 内核镜像末端(含 .bss;__bss_end 见 VKernel/linker.ld):传给 mm.init 使其在挑选
|
||
# 堆 arena 时排除整块内核镜像 —— .bss 不占文件大小,bootloader 只按文件大小预留页,
|
||
# 否则 .bss 尾部(ticks/_perf_* 等全局量)会落在 memmap 空闲区里被堆占用。
|
||
_kern_end: uint64 = 0
|
||
__asm__(f"lea {asmout(_kern_end, ASM_DESCR.OUTPUT_REG)}, [rip + __bss_end]",
|
||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||
if BootInfo is not None:
|
||
paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize)
|
||
fb_pages: uint64 = (fb_size + uint64(0xFFF)) >> uint64(12)
|
||
for fpi in range(fb_pages):
|
||
fva: uint64 = uint64(fb) + fpi * uint64(0x1000)
|
||
# 帧缓冲改用回写(去掉 PTE_PCD):此前按非缓存映射,每次写屏都直通设备,
|
||
# 全屏/大块刷新极慢。回写在 QEMU 下显示正确(TCG 不模拟缓存不一致);
|
||
# 真机若要严格正确应改为 write-combining(走 PAT)。
|
||
paging.MapPage(fva, fva, paging.PTE_WRITABLE)
|
||
mm.init(BootInfo.MemmapAddr, BootInfo.MemmapSize, BootInfo.MemmapDescSize, BootInfo.framebuffer_addr, fb_size, BootInfo.kernel_PhysAddr, _kern_end)
|
||
pt_pool_buf: rawptr = mm.malloc(uint64(513) * uint64(4096))
|
||
paging.init_pool(pt_pool_buf, uint64(513) * uint64(4096))
|
||
else:
|
||
paging.init(0, 0)
|
||
mm.init(0, 0, 0, 0, 0)
|
||
pt_pool_buf2: rawptr = mm.malloc(uint64(513) * uint64(4096))
|
||
paging.init_pool(pt_pool_buf2, uint64(513) * uint64(4096))
|
||
serial.puts("[main] paging and mm init done\n")
|
||
# 把 .rodata(字符串常量/vtable)映射只读 + CR0.WP=1:
|
||
# 越界写立即 #PF 暴露 cr2/rip(此前静默破坏格式串/路径/符号名 → P2/P3/P4/P5 连锁)
|
||
paging.ProtectKernelRO()
|
||
# [PG] 页表诊断:确认 .rodata 是否真正只读(2MB 大页会令 MakeRangeRO 失效)
|
||
paging.DumpMapping(0x100000, "kern")
|
||
paging.DumpMapping(0x60000, "bstk")
|
||
paging.DumpMapping(0x80000000, "fb")
|
||
|
||
proc.ProcessManager()
|
||
serial.puts("[main] process manager init done\n")
|
||
|
||
ide.init()
|
||
serial.puts("[main] ide init done\n")
|
||
|
||
serial.puts("[main] initializing FAT32 filesystem\n")
|
||
scan_res: int = fat32.scan_drives()
|
||
ndrives: int = fat32.get_drive_count()
|
||
ndrives_cr: [64]char
|
||
viperlib.snprintf(&ndrives_cr, 64, "[fat32] scan_res=%d found %d drives\n", scan_res, ndrives)
|
||
serial.puts(&ndrives_cr)
|
||
|
||
if ndrives > 0:
|
||
mount_res: int = fat32.mount()
|
||
if mount_res == 0:
|
||
dp: fat32_types.dirobj
|
||
res: int = fat32.opendir("/", &dp)
|
||
if res == 0:
|
||
serial.puts("[fat32] listing 0:/\n")
|
||
for count in range(20):
|
||
info: fat32_types.fileinfo
|
||
res2: int = fat32.readdir(&dp, &info)
|
||
if res2 != 0: break
|
||
if info.fname[0] == 0: break
|
||
is_dir: int = 1 if (info.attr & fat32_types.AM_DIR) else 0
|
||
entry_cr: [300]char
|
||
viperlib.snprintf(&entry_cr, 300, " %s size=%lu dir=%d\n", &info.fname[0], info.file_size, is_dir)
|
||
serial.puts(&entry_cr)
|
||
fat32.closedir(&dp)
|
||
serial.puts("[fat32] directory listing done\n")
|
||
else:
|
||
serial.puts("[fat32] failed to open root directory\n")
|
||
|
||
# Load ELF apps BEFORE running destructive FAT32 tests
|
||
serial.puts("[main] loading ELF apps...\n")
|
||
dp_apps: fat32_types.dirobj
|
||
apps_res: int = fat32.opendir("/APPS", &dp_apps)
|
||
if apps_res == 0:
|
||
serial.puts("[main] APPS dir listing:\n")
|
||
for ac in range(20):
|
||
ai: fat32_types.fileinfo
|
||
ar: int = fat32.readdir(&dp_apps, &ai)
|
||
if ar != 0: break
|
||
if ai.fname[0] == 0: break
|
||
ae: [300]char
|
||
viperlib.snprintf(&ae, 300, " %s size=%lu\n", &ai.fname[0], ai.file_size)
|
||
serial.puts(&ae)
|
||
fat32.closedir(&dp_apps)
|
||
else:
|
||
serial.puts("[main] APPS dir not found\n")
|
||
|
||
# 还原 elf 自启动(初版 6813947 的 main.py,改写为新语法):sched 暂停 →
|
||
# 逐个 spawn .elf(hello / terminal / gargantua / scene3d)→ 加载 slog.so 做一次
|
||
# log_info 冒烟调用 → sched 恢复。spawn 要在 FAT32 破坏性测试之前完成。
|
||
sched.Scheduler.disable()
|
||
|
||
hello_pid: int = elf.spawn_elf("/APPS/hello.elf")
|
||
if hello_pid < 0:
|
||
serial.puts("[main] Hello spawn failed\n")
|
||
else:
|
||
hlog: [64]char
|
||
viperlib.snprintf(&hlog, 64, "[main] Hello spawned as pid=%d\n", hello_pid)
|
||
serial.puts(&hlog)
|
||
|
||
term_pid: int = elf.spawn_elf("/APPS/terminal.elf")
|
||
if term_pid < 0:
|
||
serial.puts("[main] Terminal spawn failed\n")
|
||
else:
|
||
tlog: [64]char
|
||
viperlib.snprintf(&tlog, 64, "[main] Terminal spawned as pid=%d\n", term_pid)
|
||
serial.puts(&tlog)
|
||
|
||
garg_pid: int = elf.spawn_elf("/APPS/gargantua.elf")
|
||
if garg_pid < 0:
|
||
serial.puts("[main] Gargantua spawn failed\n")
|
||
else:
|
||
glog: [64]char
|
||
viperlib.snprintf(&glog, 64, "[main] Gargantua spawned as pid=%d\n", garg_pid)
|
||
serial.puts(&glog)
|
||
|
||
s3d_pid: int = elf.spawn_elf("/APPS/scene3d.elf")
|
||
if s3d_pid < 0:
|
||
serial.puts("[main] Scene3D spawn failed\n")
|
||
else:
|
||
s3d_log: [64]char
|
||
viperlib.snprintf(&s3d_log, 64, "[main] Scene3D spawned as pid=%d\n", s3d_pid)
|
||
serial.puts(&s3d_log)
|
||
|
||
so_base: rawptr = elf.load_so("/LIBS/SLOG.SO")
|
||
if so_base is not None:
|
||
serial.puts("[main] slog.so loaded, calling log_info...\n")
|
||
log_info_fn: rawptr = elf.get_so_symbol(so_base, "log_info", "/LIBS/SLOG.SO")
|
||
if log_info_fn is not None:
|
||
msg: const str = "Hello from shared library!"
|
||
# 与 intr/syscall.vp 的 SO_CALL1 同款:按 SysV 把第一个实参放 rdi,
|
||
# 再 call 符号地址(日志函数不需要返回值)。
|
||
__asm__(f"""mov rdi, {asminp(msg, ASM_DESCR.REG_ANY)}
|
||
call {asminp(log_info_fn, ASM_DESCR.REG_ANY)}""",
|
||
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
|
||
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
|
||
ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_RSI,
|
||
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
|
||
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
|
||
else:
|
||
serial.puts("[main] log_info symbol not found\n")
|
||
else:
|
||
serial.puts("[main] failed to load slog.so\n")
|
||
|
||
sched.Scheduler.enable()
|
||
|
||
# 破坏性 FAT32 测试已删除(write/rename/delete/mkdir/rmdir 会永久改坏 disk.img)。
|
||
# **不 unmount 根文件系统**:app 是 spawn 之后异步运行的,运行期还要读盘
|
||
# (load_so / 资产文件等),一旦卸载根 FS,它们的 fat32.open 会直接失败
|
||
# (现场:app 第一次 LOAD_SO 得到 "[so] failed to open file")。
|
||
# 卸载只应服务于非根挂载点(U 盘/多分区),并配挂载点表 + 引用计数。
|
||
else:
|
||
serial.puts("[fat32] mount failed\n")
|
||
else:
|
||
serial.puts("[fat32] no drives found\n")
|
||
|
||
devfs.devfs_init()
|
||
devfs.devfs_register_serial0()
|
||
devfs.devfs_register_serial1()
|
||
devfs.devfs_register_keyboard()
|
||
devfs.devfs_register_mouse()
|
||
|
||
serial.puts("[main] sched started\n")
|
||
|
||
fd: int = devfs.devfs_open("/dev/serial0", 0)
|
||
buf: [64]char
|
||
string.memset(&buf, 0, 64)
|
||
devfs.devfs_write(fd, "hello from devfs!", 17)
|
||
devfs.devfs_read(fd, &buf, 64)
|
||
devfs.devfs_close(fd)
|
||
|
||
cr: [30]char
|
||
string.memset(&cr, 0, 30)
|
||
viperlib.snprintf(&cr, 30, "hello %d %.2f", 42, float(0.24))
|
||
serial.puts(cr)
|
||
serial.puts("\n")
|
||
|
||
bootinfo_cr: [256]char
|
||
string.memset(&bootinfo_cr, 0, 256)
|
||
viperlib.snprintf(&bootinfo_cr, 256, "Bootinfo 0x%016lx %lu %lu %lu %lu %lu map 0x%lx+%lu descsize=%lu",
|
||
BootInfo.framebuffer_addr,
|
||
BootInfo.framebuffer_format,
|
||
BootInfo.framebuffer_width,
|
||
BootInfo.framebuffer_height,
|
||
BootInfo.framebuffer_pitch,
|
||
BootInfo.framebuffer_size,
|
||
BootInfo.MemmapAddr,
|
||
BootInfo.MemmapSize,
|
||
BootInfo.MemmapDescSize)
|
||
serial.puts(bootinfo_cr)
|
||
serial.puts("\n")
|
||
|
||
|
||
a: const str = "Hello"
|
||
serial.puts(&a[4]) # o
|
||
serial.puts("\n")
|
||
d: const int = 42
|
||
b: const int* = &d
|
||
string.memset(&cr, 0, 4)
|
||
viperlib.snprintf(&cr, 30, "%d", b[0]) # 42
|
||
serial.puts(cr)
|
||
serial.puts("\n")
|
||
|
||
# paging and mm already initialized before FAT32
|
||
|
||
usb.init()
|
||
_CheckFrameLocals("after_usb", fb, width, height, pitch)
|
||
|
||
serial.puts("[main] starting keyboard service\n")
|
||
kbd_svc.start()
|
||
serial.puts("[main] starting mouse service\n")
|
||
mse_svc.start()
|
||
serial.puts("[main] services started\n")
|
||
|
||
VGA_drv = vga._VGAScreenDriver(fb, width, height, pitch, fb_format)
|
||
self.VGA = &VGA_drv
|
||
self.VGA.Print(100, 100, "ViperOS VKernel Test")
|
||
_CheckFrameLocals("after_vga", fb, width, height, pitch)
|
||
serial.puts("你好\n")
|
||
|
||
serial.puts("[main] starting desktop\n")
|
||
_CheckFrameLocals("pre_desktop", fb, width, height, pitch)
|
||
desktop.start(fb, width, height, pitch)
|
||
serial.puts("[main] desktop started\n")
|
||
|
||
# ELF apps already loaded before FAT32 tests
|
||
|
||
serial.puts("[main] entering main loop\n")
|
||
# sched.sched_dump()
|
||
last_sec: uint32 = 0
|
||
loop_cnt: uint32 = 0
|
||
while True:
|
||
cur_sec: uint32 = timer.timer_get_seconds()
|
||
if cur_sec != last_sec:
|
||
last_sec = cur_sec
|
||
#if cur_sec % 5 == 0:
|
||
# sched.sched_dump()
|
||
loop_cnt += 1
|
||
sched.Scheduler._yield()
|
||
#while True:
|
||
# cube.RenderScene(back_fb, cube.SCREEN_W, cube.SCREEN_H, time)
|
||
# string.memcpy(fb, back_fb, width * height * 4)
|
||
# serial.puts("time")
|
||
# time += float(0.1)
|
||
|
||
# circle(width / 2, height / 2, 180, fb)
|
||
# circle(640, 400, 180, 0xFFFFFFFF)
|
||
|
||
# X 字符串遍历
|
||
# c.Cast 更换
|
||
# 时钟
|
||
# PCI
|
||
# SHA1 它者文件更新
|
||
# assets
|
||
# X attributes语法和谐
|
||
# 合成式类型转换
|
||
|
||
|
||
VKP: ViperKernel*
|
||
# stackrealign:_start 由 bootloader jmp 进入(rsp≡0 mod 16),llc 按 call 约定
|
||
# 假设入口 rsp≡8 → 帧偏 8 → 变参函数 movaps #GP。动态重对齐栈修复。
|
||
@__attribute__(section(".text.startup"), aligned(16), stackrealign)
|
||
def _start() -> int:
|
||
global VKP, BootInfo
|
||
saved_rdi: uint64
|
||
__asm__(f"mov {asmout(saved_rdi, ASM_DESCR.OUTPUT_REG)}, rdi",
|
||
op = [ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RDI])
|
||
asm.BSSClean()
|
||
BootInfo = bootinfo*(saved_rdi) # 转为 bootinfo*
|
||
VKP = &ViperKernel()
|
||
while True:
|
||
asm.sti()
|
||
asm.hlt()
|
||
sched.Scheduler._yield()
|
||
return 0
|