From 6813947181a4696c92ef833334435395482d3266 Mon Sep 17 00:00:00 2001 From: Viper Date: Sun, 19 Jul 2026 12:38:20 +0800 Subject: [PATCH] Initial import of ViperOS --- .gitignore | 93 + Apps/Gargantua/linker.ld | 39 + Apps/Gargantua/main.py | 316 ++ Apps/Gargantua/project.json | 31 + Apps/HelloWorld/linker.ld | 39 + Apps/HelloWorld/main.py | 51 + Apps/HelloWorld/project.json | 31 + Apps/Scene3D/gfx.py | 305 ++ Apps/Scene3D/linker.ld | 39 + Apps/Scene3D/main.py | 62 + Apps/Scene3D/project.json | 31 + Apps/Terminal/linker.ld | 39 + Apps/Terminal/main.py | 326 ++ Apps/Terminal/project.json | 31 + Libs/SerialLogger/linker.ld | 34 + Libs/SerialLogger/project.json | 34 + Libs/SerialLogger/serial_logger.py | 12 + Libs/VPUI64/linker.ld | 34 + Libs/VPUI64/project.json | 34 + Libs/VPUI64/vpui64.py | 80 + Scripts/check_disk.py | 91 + Scripts/disk.ps1 | 90 + Scripts/disk.sh | 40 + VKernel/Kernel/bootinfo.py | 36 + VKernel/Kernel/drivers/core/cpu/apic.py | 251 ++ VKernel/Kernel/drivers/core/cpu/cpu.py | 346 ++ VKernel/Kernel/drivers/core/cpu/fpu.py | 50 + VKernel/Kernel/drivers/devfs/devfs.py | 491 +++ VKernel/Kernel/drivers/fs/fat32/example_fs.py | 237 ++ VKernel/Kernel/drivers/fs/fat32/fat32.py | 208 + VKernel/Kernel/drivers/fs/fat32/fat32_core.py | 16 + VKernel/Kernel/drivers/fs/fat32/fat32_dir.py | 13 + .../Kernel/drivers/fs/fat32/fat32_diskio.py | 76 + VKernel/Kernel/drivers/fs/fat32/fat32_file.py | 17 + VKernel/Kernel/drivers/fs/fat32/fat32_mkfs.py | 143 + VKernel/Kernel/drivers/fs/fat32/fat32_part.py | 147 + VKernel/Kernel/drivers/fs/fat32/fat32_time.py | 64 + .../Kernel/drivers/fs/fat32/fat32_types.py | 1698 ++++++++ VKernel/Kernel/drivers/fs/fat32/fileio.py | 3 + .../Kernel/drivers/input/keyboard/keyboard.py | 146 + VKernel/Kernel/drivers/input/mouse/mouse.py | 134 + VKernel/Kernel/drivers/serial/uart/serial.py | 46 + VKernel/Kernel/drivers/serial/uart/su8250.py | 116 + VKernel/Kernel/drivers/storage/ide/ide.py | 161 + VKernel/Kernel/drivers/usb/hid/hid.py | 37 + VKernel/Kernel/drivers/usb/hid/usb_kbd.py | 111 + VKernel/Kernel/drivers/usb/hid/usb_mouse.py | 77 + VKernel/Kernel/drivers/usb/pci.py | 93 + VKernel/Kernel/drivers/usb/uhci.py | 415 ++ VKernel/Kernel/drivers/usb/usb.py | 330 ++ VKernel/Kernel/drivers/video/ui/sheet.py | 85 + VKernel/Kernel/drivers/video/ui/ui.py | 152 + VKernel/Kernel/drivers/video/vesafb/gfx.py | 305 ++ VKernel/Kernel/drivers/video/vesafb/vga.py | 504 +++ VKernel/Kernel/execrunner/elf.py | 757 ++++ VKernel/Kernel/intr/__init__.py | 2 + VKernel/Kernel/intr/gdt.py | 142 + VKernel/Kernel/intr/idt.py | 415 ++ VKernel/Kernel/intr/isr.s | 595 +++ VKernel/Kernel/intr/syscall.py | 470 +++ VKernel/Kernel/main.py | 347 ++ VKernel/Kernel/mm/mm.py | 762 ++++ VKernel/Kernel/paging/paging.py | 296 ++ VKernel/Kernel/platform/pch/__init__.py | 4 + VKernel/Kernel/platform/pch/pic.py | 120 + VKernel/Kernel/platform/pch/pit.py | 26 + VKernel/Kernel/platform/pch/rtc.py | 97 + VKernel/Kernel/platform/pch/timer.py | 102 + VKernel/Kernel/sched/coroutine.py | 202 + VKernel/Kernel/sched/process.py | 277 ++ VKernel/Kernel/sched/sched.py | 299 ++ VKernel/Kernel/services/desktop.py | 2714 +++++++++++++ VKernel/Kernel/services/keyboard.py | 50 + VKernel/Kernel/services/mouse.py | 13 + VKernel/linker.ld | 39 + VKernel/project.json | 31 + boot/Makefile | 60 + boot/boot.c | 586 +++ build.py | 116 + disk/EFI/BOOT/bootx64.efi | Bin 0 -> 77428 bytes disk/app/helloworld.asm | 33 + linker.ld | 38 + vpsdk/__init__.py | 5 + vpsdk/dynlib.py | 8 + vpsdk/fs.py | 141 + vpsdk/process.py | 65 + vpsdk/stdlib.py | 59 + vpsdk/syscall.py | 114 + vpsdk/vpui.py | 1556 ++++++++ vpsdk/wiki/01-overview.md | 357 ++ vpsdk/wiki/02-type-system.md | 665 +++ vpsdk/wiki/03-variables.md | 177 + vpsdk/wiki/04-functions.md | 254 ++ vpsdk/wiki/05-classes.md | 274 ++ vpsdk/wiki/06-oop.md | 692 ++++ vpsdk/wiki/07-control-flow.md | 256 ++ vpsdk/wiki/08-c-operations.md | 304 ++ vpsdk/wiki/09-exceptions.md | 186 + vpsdk/wiki/10-imports.md | 258 ++ vpsdk/wiki/11-builtins.md | 204 + vpsdk/wiki/12-project.md | 252 ++ vpsdk/wiki/13-bootstrapping.md | 252 ++ vpsdk/wiki/all.md | 3555 +++++++++++++++++ vpsdk/window.py | 93 + 104 files changed, 26710 insertions(+) create mode 100644 .gitignore create mode 100644 Apps/Gargantua/linker.ld create mode 100644 Apps/Gargantua/main.py create mode 100644 Apps/Gargantua/project.json create mode 100644 Apps/HelloWorld/linker.ld create mode 100644 Apps/HelloWorld/main.py create mode 100644 Apps/HelloWorld/project.json create mode 100644 Apps/Scene3D/gfx.py create mode 100644 Apps/Scene3D/linker.ld create mode 100644 Apps/Scene3D/main.py create mode 100644 Apps/Scene3D/project.json create mode 100644 Apps/Terminal/linker.ld create mode 100644 Apps/Terminal/main.py create mode 100644 Apps/Terminal/project.json create mode 100644 Libs/SerialLogger/linker.ld create mode 100644 Libs/SerialLogger/project.json create mode 100644 Libs/SerialLogger/serial_logger.py create mode 100644 Libs/VPUI64/linker.ld create mode 100644 Libs/VPUI64/project.json create mode 100644 Libs/VPUI64/vpui64.py create mode 100644 Scripts/check_disk.py create mode 100644 Scripts/disk.ps1 create mode 100644 Scripts/disk.sh create mode 100644 VKernel/Kernel/bootinfo.py create mode 100644 VKernel/Kernel/drivers/core/cpu/apic.py create mode 100644 VKernel/Kernel/drivers/core/cpu/cpu.py create mode 100644 VKernel/Kernel/drivers/core/cpu/fpu.py create mode 100644 VKernel/Kernel/drivers/devfs/devfs.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/example_fs.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_core.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_dir.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_diskio.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_file.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_mkfs.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_part.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_time.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fat32_types.py create mode 100644 VKernel/Kernel/drivers/fs/fat32/fileio.py create mode 100644 VKernel/Kernel/drivers/input/keyboard/keyboard.py create mode 100644 VKernel/Kernel/drivers/input/mouse/mouse.py create mode 100644 VKernel/Kernel/drivers/serial/uart/serial.py create mode 100644 VKernel/Kernel/drivers/serial/uart/su8250.py create mode 100644 VKernel/Kernel/drivers/storage/ide/ide.py create mode 100644 VKernel/Kernel/drivers/usb/hid/hid.py create mode 100644 VKernel/Kernel/drivers/usb/hid/usb_kbd.py create mode 100644 VKernel/Kernel/drivers/usb/hid/usb_mouse.py create mode 100644 VKernel/Kernel/drivers/usb/pci.py create mode 100644 VKernel/Kernel/drivers/usb/uhci.py create mode 100644 VKernel/Kernel/drivers/usb/usb.py create mode 100644 VKernel/Kernel/drivers/video/ui/sheet.py create mode 100644 VKernel/Kernel/drivers/video/ui/ui.py create mode 100644 VKernel/Kernel/drivers/video/vesafb/gfx.py create mode 100644 VKernel/Kernel/drivers/video/vesafb/vga.py create mode 100644 VKernel/Kernel/execrunner/elf.py create mode 100644 VKernel/Kernel/intr/__init__.py create mode 100644 VKernel/Kernel/intr/gdt.py create mode 100644 VKernel/Kernel/intr/idt.py create mode 100644 VKernel/Kernel/intr/isr.s create mode 100644 VKernel/Kernel/intr/syscall.py create mode 100644 VKernel/Kernel/main.py create mode 100644 VKernel/Kernel/mm/mm.py create mode 100644 VKernel/Kernel/paging/paging.py create mode 100644 VKernel/Kernel/platform/pch/__init__.py create mode 100644 VKernel/Kernel/platform/pch/pic.py create mode 100644 VKernel/Kernel/platform/pch/pit.py create mode 100644 VKernel/Kernel/platform/pch/rtc.py create mode 100644 VKernel/Kernel/platform/pch/timer.py create mode 100644 VKernel/Kernel/sched/coroutine.py create mode 100644 VKernel/Kernel/sched/process.py create mode 100644 VKernel/Kernel/sched/sched.py create mode 100644 VKernel/Kernel/services/desktop.py create mode 100644 VKernel/Kernel/services/keyboard.py create mode 100644 VKernel/Kernel/services/mouse.py create mode 100644 VKernel/linker.ld create mode 100644 VKernel/project.json create mode 100644 boot/Makefile create mode 100644 boot/boot.c create mode 100644 build.py create mode 100644 disk/EFI/BOOT/bootx64.efi create mode 100644 disk/app/helloworld.asm create mode 100644 linker.ld create mode 100644 vpsdk/__init__.py create mode 100644 vpsdk/dynlib.py create mode 100644 vpsdk/fs.py create mode 100644 vpsdk/process.py create mode 100644 vpsdk/stdlib.py create mode 100644 vpsdk/syscall.py create mode 100644 vpsdk/vpui.py create mode 100644 vpsdk/wiki/01-overview.md create mode 100644 vpsdk/wiki/02-type-system.md create mode 100644 vpsdk/wiki/03-variables.md create mode 100644 vpsdk/wiki/04-functions.md create mode 100644 vpsdk/wiki/05-classes.md create mode 100644 vpsdk/wiki/06-oop.md create mode 100644 vpsdk/wiki/07-control-flow.md create mode 100644 vpsdk/wiki/08-c-operations.md create mode 100644 vpsdk/wiki/09-exceptions.md create mode 100644 vpsdk/wiki/10-imports.md create mode 100644 vpsdk/wiki/11-builtins.md create mode 100644 vpsdk/wiki/12-project.md create mode 100644 vpsdk/wiki/13-bootstrapping.md create mode 100644 vpsdk/wiki/all.md create mode 100644 vpsdk/window.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..724a980 --- /dev/null +++ b/.gitignore @@ -0,0 +1,93 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# 编译输出 +*.exe +*.out +*.o +*.obj +*.a +*.lib +*.dll +*.so +.transpyc_cache/ +includes.binary/ + + +# 调试文件 +*.p2c +debug.txt +*.ll + +# 备份文件 +* copy.py +* copy.c + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# 测试输出 +test.exe +test_split.exe + +# 排除独立仓库的目录 +ViperTemplateProject/ +StandardTemplateProject/ + +# ViperOS 编译产物(每个子项目内的 output/ 和 temp/) +**/output/ +**/temp/ +**/*.deps.json +**/*.doc.json +**/*.pyi +**/*.elf +**/kernel.bin +**/isr.ver +**/boot copy *.c +**/boot.c.txt +**/Scripts/disk - Copy.ps1.txt + + +# 磁盘镜像 +*.img +*.vmdk +*.7z +*.fd + +# 日志文件 +*.log + +# 字体文件 +*.ttc +*.ttf + +# VMware +Undo/vmware/ + +# Trae AI +.trae/ + +# 编译链 +_UNCADK/ \ No newline at end of file diff --git a/Apps/Gargantua/linker.ld b/Apps/Gargantua/linker.ld new file mode 100644 index 0000000..0d5a4d5 --- /dev/null +++ b/Apps/Gargantua/linker.ld @@ -0,0 +1,39 @@ +ENTRY(__entry) + +SECTIONS { + . = 0x400000; + + .text : { + __entry = .; + *(.text.startup) + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + __bss_start = .; + *(.bss) + *(.bss.*) + __bss_end = .; + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.rela) + *(.debug*) + } +} diff --git a/Apps/Gargantua/main.py b/Apps/Gargantua/main.py new file mode 100644 index 0000000..5b281ce --- /dev/null +++ b/Apps/Gargantua/main.py @@ -0,0 +1,316 @@ +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.vpui as vpui +import string +import vipermath as vm +import viperlib +from stdint import * +import memhub +import t, c + +GW: t.CDefine = 160 +GH: t.CDefine = 90 +GW_F: t.CDefine = 160.0 +GH_F: t.CDefine = 90.0 +MAX_STEPS: t.CDefine = 100 +ESCAPE_R: t.CDefine = 60.0 +DISK_IN: t.CDefine = 2.6 +DISK_OUT: t.CDefine = 11.0 +CAM_DIST: t.CDefine = 22.0 +CAM_ELEV: t.CDefine = 9.0 +FOVF: t.CDefine = 0.55 +YIELD_ROWS: t.CDefine = 4 +V3_POOL_SIZE: t.CDefine = 8192 +V3_POOL_BYTES: t.CDefine = V3_POOL_SIZE * 24 + 512 + +_v3_mem: t.CArray[t.CUInt8T, V3_POOL_BYTES] +_v3_pool: memhub.MemPool | t.CPtr +_fb_mem: t.CArray[t.CUInt32T, GW * GH] + +class V3: + x: t.CFloat64T + y: t.CFloat64T + z: t.CFloat64T + + def __new__() -> 'V3' | t.CPtr: + return t.CVoid(t.CUInt64T(_v3_pool.alloc(24)), t.CPtr) + + def __init__(self, x: t.CFloat64T, y: t.CFloat64T, z: t.CFloat64T): + self.x = x + self.y = y + self.z = z + + def __add__(self, b: 'V3' | t.CPtr) -> 'V3' | t.CPtr: + return V3(self.x + b.x, self.y + b.y, self.z + b.z) + + def __sub__(self, b: 'V3' | t.CPtr) -> 'V3' | t.CPtr: + return V3(self.x - b.x, self.y - b.y, self.z - b.z) + + def __mul__(self, s: t.CFloat64T) -> 'V3' | t.CPtr: + return V3(self.x * s, self.y * s, self.z * s) + + def __neg__(self) -> 'V3' | t.CPtr: + return V3(-self.x, -self.y, -self.z) + + def dot(self, b: 'V3' | t.CPtr) -> t.CFloat64T: + return self.x * b.x + self.y * b.y + self.z * b.z + + def cross(self, b: 'V3' | t.CPtr) -> 'V3' | t.CPtr: + return V3(self.y * b.z - self.z * b.y, self.z * b.x - self.x * b.z, self.x * b.y - self.y * b.x) + + def nrm(self) -> 'V3' | t.CPtr: + l: t.CFloat64T = vm.sqrt(self.dot(self)) + if l > 1e-12: + return self * (1.0 / l) + return V3(0.0, 0.0, 0.0) + + def lerp(self, b: 'V3' | t.CPtr, t_val: t.CFloat64T) -> 'V3' | t.CPtr: + return self * (1.0 - t_val) + b * t_val + +_g_cam: t.CArray[t.CFloat64T, 3] +_g_fwd: t.CArray[t.CFloat64T, 3] +_g_right: t.CArray[t.CFloat64T, 3] +_g_up: t.CArray[t.CFloat64T, 3] + +CAM: V3 | t.CPtr +FWD: V3 | t.CPtr +RIGHT: V3 | t.CPtr +UP: V3 | t.CPtr + +def v3_pool_reset(): + _v3_pool.reset() + +def clmp(x: t.CFloat64T, a: t.CFloat64T, b: t.CFloat64T) -> t.CFloat64T: + if x < a: return a + if x > b: return b + return x + +def smoothstep(a: t.CFloat64T, b: t.CFloat64T, x: t.CFloat64T) -> t.CFloat64T: + t_val: t.CFloat64T = clmp((x - a) / (b - a), 0.0, 1.0) + return t_val * t_val * (3.0 - 2.0 * t_val) + +def setup_camera(): + global CAM, FWD, RIGHT, UP + e: t.CFloat64T = CAM_ELEV * 3.14159265358979323846 / 180.0 + CAM = t.CVoid(c.Addr(_g_cam), t.CPtr) + CAM.x = CAM_DIST * vm.cos(e) + CAM.y = CAM_DIST * vm.sin(e) + CAM.z = 0.0 + tmp0: V3 | t.CPtr = V3(0.0, 0.0, 0.0) + FWD = t.CVoid(c.Addr(_g_fwd), t.CPtr) + tmp1: V3 | t.CPtr = tmp0 - CAM + n1: V3 | t.CPtr = tmp1.nrm() + FWD.x = n1.x + FWD.y = n1.y + FWD.z = n1.z + tmp2: V3 | t.CPtr = V3(0.0, 1.0, 0.0) + RIGHT = t.CVoid(c.Addr(_g_right), t.CPtr) + tmp3: V3 | t.CPtr = FWD.cross(tmp2) + tmp4: V3 | t.CPtr = tmp3.nrm() + RIGHT.x = tmp4.x + RIGHT.y = tmp4.y + RIGHT.z = tmp4.z + UP = t.CVoid(c.Addr(_g_up), t.CPtr) + tmp5: V3 | t.CPtr = RIGHT.cross(FWD) + UP.x = tmp5.x + UP.y = tmp5.y + UP.z = tmp5.z + +def hash3(x: t.CFloat64T, y: t.CFloat64T, z: t.CFloat64T) -> t.CFloat64T: + n: t.CFloat64T = vm.sin(x * 127.1 + y * 311.7 + z * 74.7) * 43758.5453 + return n - vm.floor(n) + +def stars(d: V3 | t.CPtr) -> V3 | t.CPtr: + col: V3 | t.CPtr = V3(0.0, 0.0, 0.0) + i: t.CInt = 0 + while i < 3: + s: t.CFloat64T = 80.0 + t.CFloat64T(i) * 90.0 + px: t.CFloat64T = d.x * s + py: t.CFloat64T = d.y * s + pz: t.CFloat64T = d.z * s + ix: t.CFloat64T = vm.floor(px) + iy: t.CFloat64T = vm.floor(py) + iz: t.CFloat64T = vm.floor(pz) + h: t.CFloat64T = hash3(ix, iy, iz) + if h > 0.991: + fx: t.CFloat64T = px - ix - 0.5 + fy: t.CFloat64T = py - iy - 0.5 + fz: t.CFloat64T = pz - iz - 0.5 + d2: t.CFloat64T = fx * fx + fy * fy + fz * fz + br: t.CFloat64T = (h - 0.991) / 0.009 * vm.exp(-d2 * 9.0) * 1.4 + tv: t.CFloat64T = hash3(iy, iz, ix) + tint: V3 | t.CPtr = V3(0.0, 0.0, 0.0) + if tv < 0.3: + tint = V3(0.7, 0.8, 1.0) + elif tv > 0.8: + tint = V3(1.0, 0.8, 0.6) + else: + tint = V3(1.0, 1.0, 1.0) + col = col + tint * br + i += 1 + return col + +def disk_shade(cp: V3 | t.CPtr, rr: t.CFloat64T, gT: t.CFloat64T) -> V3 | t.CPtr: + tt: t.CFloat64T = (rr - DISK_IN) / (DISK_OUT - DISK_IN) + edge: t.CFloat64T = smoothstep(0.0, 0.07, tt) * smoothstep(0.0, 0.18, 1.0 - tt) + ang: t.CFloat64T = vm.atan2(cp.z, cp.x) + w: t.CFloat64T = 1.0 / vm.pow(rr, 1.5) + ph: t.CFloat64T = ang - gT * 0.0011 * w * 9.0 + n: t.CFloat64T = 0.5 + 0.5 * vm.sin(ph * 5.0 - rr * 1.6) + n2: t.CFloat64T = 0.5 + 0.5 * vm.sin(ph * 13.0 + rr * 0.8 + 1.7) + dens: t.CFloat64T = (0.35 + 0.65 * n) * (0.55 + 0.45 * n2) + bright: t.CFloat64T = (0.45 + 1.7 * vm.pow(1.0 - tt, 1.8)) * edge + vdir: V3 | t.CPtr = V3(-cp.z, 0.0, cp.x).nrm() + beta: t.CFloat64T = 0.46 / vm.sqrt(rr) + to_cam: V3 | t.CPtr = (CAM - cp).nrm() + mu: t.CFloat64T = vdir.dot(to_cam) + dop: t.CFloat64T = 1.0 / (1.0 - beta * mu) + beam: t.CFloat64T = vm.pow(dop, 3.0) + c1: V3 | t.CPtr = V3(1.0, 0.92, 0.68) + c2: V3 | t.CPtr = V3(1.0, 0.42, 0.13) + base: V3 | t.CPtr = c1.lerp(c2, tt) + base.z += (dop - 1.0) * 0.35 + base.x -= (dop - 1.0) * 0.05 + inten: t.CFloat64T = bright * dens * beam * 1.25 + return base * inten + +def trace(dir_v: V3 | t.CPtr, gT: t.CFloat64T) -> V3 | t.CPtr: + v3_pool_reset() + pos: V3 | t.CPtr = V3(CAM.x, CAM.y, CAM.z) + vel: V3 | t.CPtr = V3(dir_v.x, dir_v.y, dir_v.z) + hvec: V3 | t.CPtr = pos.cross(vel) + h2: t.CFloat64T = hvec.dot(hvec) + T: t.CFloat64T = 1.0 + col: V3 | t.CPtr = V3(0.0, 0.0, 0.0) + + i: t.CInt = 0 + while i < MAX_STEPS: + r2: t.CFloat64T = pos.dot(pos) + r: t.CFloat64T = vm.sqrt(r2) + if r < 1.0: + break + if r > ESCAPE_R: + col = col + stars(vel.nrm()) * T + break + + dt: t.CFloat64T = clmp(r * 0.08, 0.03, 2.2) + r5: t.CFloat64T = vm.pow(r2, 2.5) + coeff: t.CFloat64T = -1.5 * h2 / r5 + + k1v: V3 | t.CPtr = pos * coeff + k2p_a: V3 | t.CPtr = vel + k1v * (dt * 0.5) + k2v_a: V3 | t.CPtr = pos + vel * (dt * 0.5) + r2_a: t.CFloat64T = k2v_a.dot(k2v_a) + coeff_a: t.CFloat64T = -1.5 * h2 / vm.pow(r2_a, 2.5) + k2v: V3 | t.CPtr = k2v_a * coeff_a + + k3p_a: V3 | t.CPtr = vel + k2v * (dt * 0.5) + k3v_a: V3 | t.CPtr = pos + k2p_a * (dt * 0.5) + r2_b: t.CFloat64T = k3v_a.dot(k3v_a) + coeff_b: t.CFloat64T = -1.5 * h2 / vm.pow(r2_b, 2.5) + k3v: V3 | t.CPtr = k3v_a * coeff_b + + k4p_a: V3 | t.CPtr = vel + k3v * dt + k4v_a: V3 | t.CPtr = pos + k3p_a * dt + r2_c: t.CFloat64T = k4v_a.dot(k4v_a) + coeff_c: t.CFloat64T = -1.5 * h2 / vm.pow(r2_c, 2.5) + k4v: V3 | t.CPtr = k4v_a * coeff_c + + npos: V3 | t.CPtr = pos + (vel + k2p_a * 2.0 + k3p_a * 2.0 + k4p_a) * (dt / 6.0) + nvel: V3 | t.CPtr = vel + (k1v + k2v * 2.0 + k3v * 2.0 + k4v) * (dt / 6.0) + + if pos.y * npos.y < 0.0: + f: t.CFloat64T = pos.y / (pos.y - npos.y) + cp: V3 | t.CPtr = pos.lerp(npos, f) + rr: t.CFloat64T = vm.sqrt(cp.x * cp.x + cp.z * cp.z) + if rr > DISK_IN and rr < DISK_OUT: + emit: V3 | t.CPtr = disk_shade(cp, rr, gT) + col = col + emit * T + a: t.CFloat64T = clmp(smoothstep(0.0, 0.07, (rr - DISK_IN) / (DISK_OUT - DISK_IN)) * smoothstep(0.0, 0.18, 1.0 - (rr - DISK_IN) / (DISK_OUT - DISK_IN)) * 0.92, 0.0, 1.0) + T = T * (1.0 - a) + if T < 0.01: + break + + pos = npos + vel = nvel + i += 1 + return col + +def shade_pixel(px: t.CInt, py: t.CInt, gT: t.CFloat64T) -> t.CUInt32T: + aspect: t.CFloat64T = GW_F / GH_F + sx: t.CFloat64T = (2.0 * (t.CFloat64T(px) + 0.5) / GW_F - 1.0) * aspect * FOVF + sy: t.CFloat64T = (1.0 - 2.0 * (t.CFloat64T(py) + 0.5) / GH_F) * FOVF + dir_v: V3 | t.CPtr = (FWD + RIGHT * sx + UP * sy).nrm() + c: V3 | t.CPtr = trace(dir_v, gT) + ex: t.CFloat64T = 1.15 + c = c * ex + c.x = c.x / (1.0 + c.x) + c.y = c.y / (1.0 + c.y) + c.z = c.z / (1.0 + c.z) + c.x = vm.pow(c.x, 1.0 / 2.2) + c.y = vm.pow(c.y, 1.0 / 2.2) + c.z = vm.pow(c.z, 1.0 / 2.2) + R: t.CInt = t.CInt(clmp(c.x, 0.0, 1.0) * 255.0) + G: t.CInt = t.CInt(clmp(c.y, 0.0, 1.0) * 255.0) + B: t.CInt = t.CInt(clmp(c.z, 0.0, 1.0) * 255.0) + return t.CUInt32T((R << 16) | (G << 8) | B) + +@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16)) +def _start() -> t.CInt | t.CExport: + _v3_pool = memhub.MemPool(c.Addr(_v3_mem), V3_POOL_BYTES) + setup_camera() + + root: vpui.Tk = vpui.Tk() + root.title("Gargantua") + root.set_style(window.STYLE_WIN7) + root.bg_color = t.CUInt32T(0x000000) + master: vpui.Tk | t.CPtr = c.Addr(root) + + canvas: vpui.FBCanvas = vpui.FBCanvas(master, GW, GH) + root.pack(0, vpui.PACK_TOP, vpui.FILL_BOTH, 1, 0, 0) + + fb: UINT32PTR = c.Addr(_fb_mem) + string.memset(fb, 0, GW * GH * 4) + canvas.set_fb(fb, GW, GH) + + root.geometry(100, 30, GW, GH + 32) + + gT: t.CFloat64T = 0.0 + rendered: t.CInt = 0 + + evt: window.InputEvent + while window.is_active(root.win_id): + result: t.CInt = window.poll_event(c.Addr(evt)) + if result != 0: + if evt.type == window.EVENT_TYPE_PING: + window.flush(root.win_id) + elif evt.type == window.EVENT_TYPE_RESIZE: + sz: t.CInt = window.get_win_size(root.win_id) + new_w: t.CInt = sz >> 16 + new_h: t.CInt = sz & 0xFFFF + canvas.set_fb(fb, new_w, new_h) + + if rendered == 0: + y: t.CInt = 0 + while y < GH: + x: t.CInt = 0 + while x < GW: + pixel: t.CUInt32T = shade_pixel(x, y, gT) + fb[y * GW + x] = pixel + x += 1 + y += 1 + if y % YIELD_ROWS == 0: + root.redraw() + process.yield_cpu() + result2: t.CInt = window.poll_event(c.Addr(evt)) + if result2 != 0: + if evt.type == window.EVENT_TYPE_PING: + window.flush(root.win_id) + root.redraw() + rendered = 1 + gT += 50.0 + + process.yield_cpu() + + process.exit(0) + return 0 diff --git a/Apps/Gargantua/project.json b/Apps/Gargantua/project.json new file mode 100644 index 0000000..9ef9c98 --- /dev/null +++ b/Apps/Gargantua/project.json @@ -0,0 +1,31 @@ +{ + "name": "Gargantua", + "version": "1.0.0", + "source_dir": "./", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "--strip-all" + ], + "output": "gargantua.elf" + }, + "includes": ["../.."], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} diff --git a/Apps/HelloWorld/linker.ld b/Apps/HelloWorld/linker.ld new file mode 100644 index 0000000..0d5a4d5 --- /dev/null +++ b/Apps/HelloWorld/linker.ld @@ -0,0 +1,39 @@ +ENTRY(__entry) + +SECTIONS { + . = 0x400000; + + .text : { + __entry = .; + *(.text.startup) + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + __bss_start = .; + *(.bss) + *(.bss.*) + __bss_end = .; + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.rela) + *(.debug*) + } +} diff --git a/Apps/HelloWorld/main.py b/Apps/HelloWorld/main.py new file mode 100644 index 0000000..b862280 --- /dev/null +++ b/Apps/HelloWorld/main.py @@ -0,0 +1,51 @@ +import vpsdk.vpui as vpui +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.dynlib as dynlib +from stdint import * +import t, c + +@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16)) +def _start() -> t.CInt | t.CExport: + base: t.CUInt64T = dynlib.load_so("/LIBS/SLOG.SO") + if base != 0: + dynlib.so_call1("log_info", "Hello from dynamic library!") + + root: vpui.Tk = vpui.Tk() + root.title("Input Demo") + root.set_style(window.STYLE_WIN7) + root.geometry(290, 30, 300, 200) + root.bg_color = t.CUInt32T(0x1E1E32) + master: vpui.Tk | t.CPtr = c.Addr(root) + + entry: vpui.Entry = vpui.Entry(master, 260, 28) + root.pack(0, vpui.PACK_TOP, vpui.FILL_X, 0, 20, 10) + result: vpui.Label = vpui.Label(master, "", t.CUInt32T(0x00FF00)) + root.pack(1, vpui.PACK_BOTTOM, 0, 0, 0, 10) + + def _on_submit() -> t.CInt: + text: t.CConst | str = entry.get() + result.config(text) + entry.delete(0, -1) + return 0 + + lbl: vpui.Label = vpui.Label(master, "Name:", 0xFFFFFFFF) + root.pack(2, vpui.PACK_TOP, 0, 0, 20, 0) + btn: vpui.Button = vpui.Button(master, 100, 36, "Submit", c.Addr(_on_submit)) + root.pack(3, vpui.PACK_TOP, 0, 0, 0, 10) + + root2: vpui.Tk = vpui.Tk() + root2.title("Test No-Resp") + root2.set_style(window.STYLE_WIN10) + root2.geometry(200, 50, 300, 150) + root2.bg_color = t.CUInt32T(0x1E1E32) + master2: vpui.Tk | t.CPtr = c.Addr(root2) + + lbl2: vpui.Label = vpui.Label(master2, "This window will NOT respond!", 0xFFFFFFFF) + lbl2.place(10, 10) + + root2.update() + + root.mainloop() + process.exit(0) + return 0 diff --git a/Apps/HelloWorld/project.json b/Apps/HelloWorld/project.json new file mode 100644 index 0000000..64f57e1 --- /dev/null +++ b/Apps/HelloWorld/project.json @@ -0,0 +1,31 @@ +{ + "name": "HelloWorld", + "version": "1.0.0", + "source_dir": "./", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "--strip-all" + ], + "output": "helloworld.elf" + }, + "includes": ["../.."], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} diff --git a/Apps/Scene3D/gfx.py b/Apps/Scene3D/gfx.py new file mode 100644 index 0000000..e89591b --- /dev/null +++ b/Apps/Scene3D/gfx.py @@ -0,0 +1,305 @@ +from stdint import * +import vipermath +import string +import t, c + + +PI: t.CDefine = float(3.14159265358979323846) + +SPHERE_SLICES: t.CDefine = (24 * 1) +SPHERE_STACKS: t.CDefine = (16 * 1) +SIN_WAVE_GRID: t.CDefine = (40 * 1) + +def COLOR_RGB(r: int, g: int, b: int) -> t.CInline | t.CInt: + return (255 << 24) | (r << 16) | (g << 8) | (b) + +@t.Object +class Vec3: + x: float + y: float + z: float + def __init__(self, x: float, y: float, z: float): + self.x = x + self.y = y + self.z = z + def Sub(self, b: 'Vec3') -> 'Vec3': + return Vec3(self.x - b.x, self.y - b.y, self.z - b.z) + def Cross(self, b: 'Vec3') -> 'Vec3': + return Vec3(self.y * b.z - self.z * b.y, self.z * b.x - self.x * b.z, self.x * b.y - self.y * b.x) + def Dot(self, b: 'Vec3') -> float: + return self.x * b.x + self.y * b.y + self.z * b.z + def Norm(self) -> 'Vec3': + l = vipermath.sqrtf(self.Dot(self)) + return Vec3(self.x / l, self.y / l, self.z / l) + +class Mat4: + m: t.CArray[t.CArray[float, 4], 4] + +def Mat4_Identity() -> Mat4: + m: Mat4 = Mat4() + string.memset(c.Addr(m), 0, Mat4.__sizeof__()) + m.m[0][0] = 1; m.m[1][1] = 1; m.m[2][2] = 1; m.m[3][3] = 1 + return m + +def Mat4_Mul(a: Mat4, b: Mat4) -> Mat4: + res: Mat4 = Mat4() + string.memset(c.Addr(res), 0, Mat4.__sizeof__()) + for i in range(4): + for j in range(4): + for k in range(4): + res.m[i][j] += a.m[i][k] * b.m[k][j] + return res + +def Mat4_MulVec(m: Mat4, v: Vec3) -> Vec3: + x: float = m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z + m.m[0][3] + y: float = m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z + m.m[1][3] + z: float = m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z + m.m[2][3] + w: float = m.m[3][0] * v.x + m.m[3][1] * v.y + m.m[3][2] * v.z + m.m[3][3] + if w != 0: + x /= w; y /= w; z /= w + return Vec3(x, y, z) + +def Mat4_Perspective(fov: float, aspect: float, zn: float, zf: float) -> Mat4: + m: Mat4 = Mat4() + string.memset(c.Addr(m), 0, Mat4.__sizeof__()) + f: float = float(1.0) / vipermath.tanf(fov / float(2.0)) + m.m[0][0] = f / aspect; m.m[1][1] = f + m.m[2][2] = zf / (zn - zf); m.m[2][3] = (zf * zn) / (zn - zf); m.m[3][2] = -1 + return m + +def Mat4_LookAt(eye: Vec3, target: Vec3, up: Vec3) -> Mat4: + zaxis: Vec3 = eye.Sub(target).Norm() + xaxis: Vec3 = up.Cross(zaxis).Norm() + yaxis: Vec3 = zaxis.Cross(xaxis) + m: Mat4 = Mat4_Identity() + m.m[0][0]=xaxis.x; m.m[0][1]=xaxis.y; m.m[0][2]=xaxis.z; m.m[0][3]= -xaxis.Dot(eye) + m.m[1][0]=yaxis.x; m.m[1][1]=yaxis.y; m.m[1][2]=yaxis.z; m.m[1][3]= -yaxis.Dot(eye) + m.m[2][0]=zaxis.x; m.m[2][1]=zaxis.y; m.m[2][2]=zaxis.z; m.m[2][3]= -zaxis.Dot(eye) + m.m[3][3] = 1 + return m + +def Mat4_Translate(x: float, y: float, z: float) -> Mat4: + m: Mat4 = Mat4_Identity() + m.m[0][3] = x; m.m[1][3] = y; m.m[2][3] = z + return m + +def Mat4_RotateY(a: float) -> Mat4: + m: Mat4 = Mat4_Identity() + m.m[0][0] = vipermath.cosf(a); m.m[0][2] = vipermath.sinf(a) + m.m[2][0] = -vipermath.sinf(a); m.m[2][2] = vipermath.cosf(a) + return m + + +@t.Object +class Surface: + fb: UINT32PTR + zb: float | t.CPtr + w: int + h: int + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int): + self.fb = fb + self.zb = zb + self.w = w + self.h = h + +@t.Object +class Renderer: + _fb: UINT32PTR + _zb: float | t.CPtr + _w: int + _h: int + + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int): + self._fb = fb + self._zb = zb + self._w = w + self._h = h + + def Clear(self, color: t.CUInt32T): + fb: UINT32PTR = self._fb + zb: float | t.CPtr = self._zb + size: int = self._w * self._h + for i in range(size): + fb[i] = color + zb[i] = float(1.0) + + def DrawLine2D(self, x0: int, y0: int, x1: int, y1: int, col: t.CUInt32T): + fb: UINT32PTR = self._fb + w: int = self._w; h: int = self._h + dx: int = abs(x1-x0); dy: int = abs(y1-y0) + sx: int = 1 if x0 < x1 else -1; sy: int = 1 if y0 < y1 else -1 + err: int = dx - dy + while True: + if x0 >= 0 and x0 < w and y0 >= 0 and y0 < h: fb[y0 * w + x0] = col + if x0 == x1 and y0 == y1: break + e2: int = 2 * err + if e2 > -dy: err -= dy; x0 += sx + if e2 < dx: err += dx; y0 += sy + + def DrawTriangle(self, v0: Vec3, v1: Vec3, v2: Vec3, col: t.CUInt32T): + if v0.z < float(0.0) or v1.z < float(0.0) or v2.z < float(0.0): return + if (v0.x > float(10.0) or v0.x < float(-10.0) or v0.y > float(10.0) or v0.y < float(-10.0) or + v1.x > float(10.0) or v1.x < float(-10.0) or v1.y > float(10.0) or v1.y < float(-10.0) or + v2.x > float(10.0) or v2.x < float(-10.0) or v2.y > float(10.0) or v2.y < float(-10.0)): return + + fb: UINT32PTR = self._fb + zb: float | t.CPtr = self._zb + w: int = self._w + h: int = self._h + + sx0: float = (v0.x + float(1.0)) * float(0.5) * w; sy0: float = (float(1.0) - v0.y) * float(0.5) * h + sx1: float = (v1.x + float(1.0)) * float(0.5) * w; sy1: float = (float(1.0) - v1.y) * float(0.5) * h + sx2: float = (v2.x + float(1.0)) * float(0.5) * w; sy2: float = (float(1.0) - v2.y) * float(0.5) * h + + minX: int = max(0, int(vipermath.floorf(min(min(sx0, sx1), sx2)))) + maxX: int = min(w - 1, int(vipermath.ceilf(max(max(sx0, sx1), sx2)))) + minY: int = max(0, int(vipermath.floorf(min(min(sy0, sy1), sy2)))) + maxY: int = min(h - 1, int(vipermath.ceilf(max(max(sy0, sy1), sy2)))) + + dx12: float = sx1 - sx2; dy12: float = sy1 - sy2 + dx02: float = sx0 - sx2; dy02: float = sy0 - sy2 + denom: float = dx12 * dy02 - dy12 * dx02 + if denom < float(0.001) and denom > float(-0.001): return + invDenom: float = float(1.0) / denom + + row_dxP_start: float = float(minX) - sx2 + for y in range(minY, maxY + 1): + dyP: float = float(y) - sy2 + dxP: float = row_dxP_start + row_base: int = y * w + for x in range(minX, maxX + 1): + w0: float = (dx12 * dyP - dy12 * dxP) * invDenom + w1: float = (dxP * dy02 - dyP * dx02) * invDenom + w2: float = float(1.0) - w0 - w1 + if w0 >= 0 and w1 >= 0 and w2 >= 0: + z: float = w0 * v0.z + w1 * v1.z + w2 * v2.z + idx: int = row_base + x + if z < zb[idx]: + zb[idx] = z + fb[idx] = col + dxP += float(1.0) + + def DrawGlowDot(self, x: float, y: float, radius: float, core_col: t.CUInt32T, intensity: float): + fb: UINT32PTR = self._fb; w: int = self._w; h: int = self._h + r: int = int(radius) + 4 + ix: int = int(x); iy: int = int(y) + for dy in range(-r, r + 1): + py: int = iy + dy + if py < 0 or py >= h: continue + for dx in range(-r, r + 1): + px: int = ix + dx + if px < 0 or px >= w: continue + dist: float = vipermath.sqrtf(float(dx*dx + dy*dy)) + if dist < radius: + self.BlendPixel(px, py, core_col, intensity) + elif dist < float(r): + fade: float = 1.0 - (dist - radius) / 4.0 + self.BlendPixel(px, py, core_col, intensity * fade * 0.5) + + def BlendPixel(self, x: int, y: int, col: t.CUInt32T, alpha: float): + if alpha <= 0.0: return + fb: UINT32PTR = self._fb; w: int = self._w; h: int = self._h + if x < 0 or x >= w or y < 0 or y >= h: return + + idx: int = y * w + x + bg: t.CUInt32T = fb[idx] + bg_b: int = bg & 0xFF; bg_g: int = (bg >> 8) & 0xFF; bg_r: int = (bg >> 16) & 0xFF + f_b: int = col & 0xFF; f_g: int = (col >> 8) & 0xFF; f_r: int = (col >> 16) & 0xFF + + a: int = int(alpha * 255.0) + inv_a: int = 255 - a + nr: int = (f_r * a + bg_r * inv_a) >> 8 + ng: int = (f_g * a + bg_g * inv_a) >> 8 + nb: int = (f_b * a + bg_b * inv_a) >> 8 + + fb[idx] = COLOR_RGB(255 if (nr > 255) else nr, + 255 if (ng > 255) else ng, + 255 if (nb > 255) else nb) + + +@t.Object +class Scene3D: + renderer: Renderer + + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int): + self.renderer = Renderer(fb, zb, w, h) + + def Render(self, time: float): + rw: int = self.renderer._w + rh: int = self.renderer._h + self.renderer.Clear(COLOR_RGB(15, 15, 25)) + + yaw: float = float(45.0) * PI / float(180.0); pitch: float = float(45.0) * PI / float(180.0); radius: float = float(10.0) + eye: Vec3 = Vec3(radius*vipermath.cosf(pitch)*vipermath.cosf(yaw), radius*vipermath.sinf(pitch), radius*vipermath.cosf(pitch)*vipermath.sinf(yaw)) + target: Vec3 = Vec3(0, 0, 0); up: Vec3 = Vec3(0, 1, 0) + view: Mat4 = Mat4_LookAt(eye, target, up) + proj: Mat4 = Mat4_Perspective(PI / float(3.0), float(rw)/float(rh), float(0.1), float(100.0)) + vp: Mat4 = Mat4_Mul(proj, view) + + cubeX: float = float(-3.5) + cubeMVP: Mat4 = Mat4_Mul(vp, Mat4_Translate(cubeX, 0, 0)) + self._DrawCube(cubeMVP, COLOR_RGB(180, 180, 180)) + + colors: t.CArray[t.CUInt32T, 4] = [COLOR_RGB(255,50,50), COLOR_RGB(50,50,255), COLOR_RGB(255,255,50), COLOR_RGB(50,255,255)] + for i in range(4): + a: float = time * float(1.5) + i * (PI / float(2.0)); r: float = float(3.0) + pos: Vec3 = Vec3(vipermath.cosf(a) * r + cubeX, 0, vipermath.sinf(a) * r) + sphereMVP: Mat4 = Mat4_Mul(vp, Mat4_Translate(pos.x, pos.y, pos.z)) + self._DrawSphere(sphereMVP, Vec3(0,0,0), float(0.5), colors[i]) + + self._Draw3DSinWave(vp, Vec3(float(3.5), 0, 0), time) + + self.renderer.DrawLine2D(50, rh - 150, rw - 50, rh - 150, COLOR_RGB(80, 80, 80)) + self.renderer.DrawLine2D(50, rh - 50, 50, rh - 250, COLOR_RGB(80, 80, 80)) + wave_shift: float = time * float(2.0) + prev_x: int = 50; prev_y: int = rh - 150 - int(vipermath.sinf(0 - wave_shift) * float(100.0)) + for i in range(1, rw - 100): + tr: float = float(i) / float(rw-100) * float(4.0) * PI + cx: int = 50 + i; cy: int = rh - 150 - int(vipermath.sinf(tr - wave_shift) * float(100.0)) + self.renderer.DrawLine2D(prev_x, prev_y, cx, cy, COLOR_RGB(0, 255, 100)) + prev_x = cx; prev_y = cy + + def _DrawCube(self, mvp: Mat4, col: t.CUInt32T): + v: t.CArray[Vec3, 8] = [ + Vec3(-1, -1, -1), Vec3(-1, -1, 1), Vec3(-1, 1, -1), Vec3(-1, 1, 1), + Vec3( 1, -1, -1), Vec3( 1, -1, 1), Vec3( 1, 1, -1), Vec3( 1, 1, 1) + ] + for i in range(8): v[i] = Mat4_MulVec(mvp, v[i]) + faces: t.CArray[t.CArray[int, 3], 12] = [ + [0, 2, 1], [1, 2, 3], [4, 1, 5], [4, 0, 1], [6, 3, 2], [6, 7, 3], + [4, 6, 0], [0, 6, 2], [5, 7, 4], [4, 7, 6], [1, 3, 5], [5, 3, 7] + ] + for i in range(12): self.renderer.DrawTriangle(v[faces[i][0]], v[faces[i][1]], v[faces[i][2]], col) + + def _DrawSphere(self, mvp: Mat4, center: Vec3, radius: float, col: t.CUInt32T): + step_pi: float = PI / SPHERE_STACKS; step_2pi: float = float(2.0) * PI / SPHERE_SLICES + for i in range(SPHERE_STACKS): + for j in range(SPHERE_SLICES): + theta1: float = i * step_pi; theta2: float = (i + 1) * step_pi + phi1: float = j * step_2pi; phi2: float = (j + 1) * step_2pi + v: t.CArray[Vec3, 4] + v[0] = Vec3(center.x+radius*vipermath.sinf(theta1)*vipermath.cosf(phi1), center.y+radius*vipermath.cosf(theta1), center.z+radius*vipermath.sinf(theta1)*vipermath.sinf(phi1)) + v[1] = Vec3(center.x+radius*vipermath.sinf(theta1)*vipermath.cosf(phi2), center.y+radius*vipermath.cosf(theta1), center.z+radius*vipermath.sinf(theta1)*vipermath.sinf(phi2)) + v[2] = Vec3(center.x+radius*vipermath.sinf(theta2)*vipermath.cosf(phi1), center.y+radius*vipermath.cosf(theta2), center.z+radius*vipermath.sinf(theta2)*vipermath.sinf(phi1)) + v[3] = Vec3(center.x+radius*vipermath.sinf(theta2)*vipermath.cosf(phi2), center.y+radius*vipermath.cosf(theta2), center.z+radius*vipermath.sinf(theta2)*vipermath.sinf(phi2)) + for k in range(4): v[k] = Mat4_MulVec(mvp, v[k]) + self.renderer.DrawTriangle(v[0], v[1], v[2], col) + self.renderer.DrawTriangle(v[1], v[3], v[2], col) + + def _Draw3DSinWave(self, vp: Mat4, pos: Vec3, time: float): + _range: float = float(2.0) * PI; step: float = _range / SIN_WAVE_GRID; scale: float = float(0.5) + mvp: Mat4 = Mat4_Mul(vp, Mat4_Mul(Mat4_Translate(pos.x, pos.y, pos.z), Mat4_RotateY(time * float(1.0)))) + for i in range(SIN_WAVE_GRID): + for j in range(SIN_WAVE_GRID): + x0: float = -PI + i * step; z0 = -PI + j * step; x1: float = x0 + step; z1 = z0 + step + y00: float = vipermath.sinf(x0) * vipermath.cosf(z0) * scale; y10: float = vipermath.sinf(x1) * vipermath.cosf(z0) * scale + y01: float = vipermath.sinf(x0) * vipermath.cosf(z1) * scale; y11: float = vipermath.sinf(x1) * vipermath.cosf(z1) * scale + + t1: float = (y00 / scale + float(1.0)) * float(0.5); r1: int = int(50 + 205 * t1); g1: int = int(80 + 120 * vipermath.sinf(t1 * PI)); b1: int = int(255 - 205 * t1) + col1: t.CUInt32T = COLOR_RGB(255 if (r1 > 255) else (0 if (r1 < 0) else r1), 255 if (g1 > 255) else (0 if (g1 < 0) else g1), 255 if (b1 > 255) else (0 if (b1 < 0) else b1)) + t2: float = (y11 / scale + float(1.0)) * float(0.5); r2: int = int(50 + 205 * t2); g2: int = int(80 + 120 * vipermath.sinf(t2 * PI)); b2: int = int(255 - 205 * t2) + col2: t.CUInt32T = COLOR_RGB(255 if (r2 > 255) else (0 if (r2 < 0) else r2), 255 if (g2 > 255) else (0 if (g2 < 0) else g2), 255 if (b2 > 255) else (0 if (b2 < 0) else b2)) + + v00: Vec3 = Mat4_MulVec(mvp, Vec3(x0, y00, z0)); v10: Vec3 = Mat4_MulVec(mvp, Vec3(x1, y10, z0)) + v01: Vec3 = Mat4_MulVec(mvp, Vec3(x0, y01, z1)); v11: Vec3 = Mat4_MulVec(mvp, Vec3(x1, y11, z1)) + self.renderer.DrawTriangle(v00, v10, v11, col1); self.renderer.DrawTriangle(v00, v11, v01, col2) diff --git a/Apps/Scene3D/linker.ld b/Apps/Scene3D/linker.ld new file mode 100644 index 0000000..0d5a4d5 --- /dev/null +++ b/Apps/Scene3D/linker.ld @@ -0,0 +1,39 @@ +ENTRY(__entry) + +SECTIONS { + . = 0x400000; + + .text : { + __entry = .; + *(.text.startup) + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + __bss_start = .; + *(.bss) + *(.bss.*) + __bss_end = .; + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.rela) + *(.debug*) + } +} diff --git a/Apps/Scene3D/main.py b/Apps/Scene3D/main.py new file mode 100644 index 0000000..fd29cc7 --- /dev/null +++ b/Apps/Scene3D/main.py @@ -0,0 +1,62 @@ +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.vpui as vpui +import viperstring as string +import vipermath +from stdint import * +import gfx +import t, c + +SCENE_W: t.CDefine = 640 +SCENE_H: t.CDefine = 360 + +_zbuf_mem: t.CArray[t.CUInt8T, SCENE_W * SCENE_H * 8] +_fb_mem: t.CArray[t.CUInt32T, SCENE_W * SCENE_H] + +_root_ptr: vpui.Tk | t.CPtr +_canvas_ptr: vpui.FBCanvas | t.CPtr +_s3d_ptr: gfx.Scene3D | t.CPtr +_time_val: float = float(0.0) + +def _render_thread(arg: t.CVoid | t.CPtr) -> t.CVoid: + root: vpui.Tk | t.CPtr = _root_ptr + canvas: vpui.FBCanvas | t.CPtr = _canvas_ptr + s3d: gfx.Scene3D | t.CPtr = _s3d_ptr + while root.winfo_exists(): + s3d.Render(_time_val) + _time_val += float(0.02) + root.update() + process.yield_cpu() + +@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16)) +def _start() -> t.CInt | t.CExport: + root: vpui.Tk = vpui.Tk() + root.title("3D Scene") + root.set_style(window.STYLE_WIN11) + root.bg_color = t.CUInt32T(0x0F0F19) + master: vpui.Tk | t.CPtr = c.Addr(root) + + canvas: vpui.FBCanvas = vpui.FBCanvas(master, SCENE_W, SCENE_H) + root.pack(0, vpui.PACK_TOP, vpui.FILL_BOTH, 1, 0, 0) + + zbuf: float | t.CPtr = t.CVoid(c.Addr(_zbuf_mem), t.CPtr) + string.memset(zbuf, 0, SCENE_W * SCENE_H * 8) + + fb: UINT32PTR = c.Addr(_fb_mem) + string.memset(fb, 0, SCENE_W * SCENE_H * 4) + canvas.set_fb(fb, SCENE_W, SCENE_H) + + s3d: gfx.Scene3D = gfx.Scene3D(fb, zbuf, SCENE_W, SCENE_H) + + root.geometry(100, 30, SCENE_W, SCENE_H + 32) + + _root_ptr = c.Addr(root) + _canvas_ptr = c.Addr(canvas) + _s3d_ptr = c.Addr(s3d) + + process.thread_create(_render_thread, None) + + root.mainloop() + + process.exit(0) + return 0 diff --git a/Apps/Scene3D/project.json b/Apps/Scene3D/project.json new file mode 100644 index 0000000..295756a --- /dev/null +++ b/Apps/Scene3D/project.json @@ -0,0 +1,31 @@ +{ + "name": "Scene3D", + "version": "1.0.0", + "source_dir": "./", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "--strip-all" + ], + "output": "scene3d.elf" + }, + "includes": ["../.."], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} diff --git a/Apps/Terminal/linker.ld b/Apps/Terminal/linker.ld new file mode 100644 index 0000000..0d5a4d5 --- /dev/null +++ b/Apps/Terminal/linker.ld @@ -0,0 +1,39 @@ +ENTRY(__entry) + +SECTIONS { + . = 0x400000; + + .text : { + __entry = .; + *(.text.startup) + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + __bss_start = .; + *(.bss) + *(.bss.*) + __bss_end = .; + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.rela) + *(.debug*) + } +} diff --git a/Apps/Terminal/main.py b/Apps/Terminal/main.py new file mode 100644 index 0000000..46626bf --- /dev/null +++ b/Apps/Terminal/main.py @@ -0,0 +1,326 @@ +import vpsdk.vpui as vpui +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.fs as fs +import vpsdk.syscall as _syscall +import vpsdk.stdlib as stdlib +import string +import viperlib +from stdint import * +import t, c + +@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16)) +def _start() -> t.CInt | t.CExport: + root: vpui.Tk = vpui.Tk() + root.title("Terminal") + root.set_style(window.STYLE_WIN7) + root.geometry(80, 30, 640, 480) + root.bg_color = t.CUInt32T(0x0C0C0C) + master: vpui.Tk | t.CPtr = c.Addr(root) + + def _sdbg(msg: str): + _syscall._syscall1(t.CUInt64T(18), t.CUInt64T(msg)) + + _sdbg("[term] start\n") + + _cwd: t.CArray[t.CChar, 64] + _cwd[0] = 47 + for i in range(1, 64): + _cwd[i] = 0 + + sbar: vpui.Scrollbar = vpui.Scrollbar(master, 16, 446, vpui.VERTICAL) + root.pack(0, vpui.PACK_RIGHT, vpui.FILL_Y, 0, 0, 0) + + term: vpui.Text = vpui.Text(master, 624, 446) + root.pack(1, vpui.PACK_LEFT, vpui.FILL_BOTH, 1, 0, 0) + term.insert("ViperOS Terminal v1.1") + term.insert("Type 'help' for commands.") + term.insert("") + + term.yscrollbar = t.CType(sbar, vpui.Widget, t.CPtr) + + def _on_scroll(): + act: t.CInt = sbar.action + if act == 1: + term.scroll_offset -= 1 + elif act == 2: + term.scroll_offset += 1 + elif act == 3: + vis: t.CInt = term._visible_lines() + term.scroll_offset -= vis + elif act == 4: + vis2: t.CInt = term._visible_lines() + term.scroll_offset += vis2 + elif act == 5: + aval: t.CInt = sbar.action_value + if term.line_count > 0: + term.scroll_offset = aval * term.line_count / 1000 + vis3: t.CInt = term._visible_lines() + max_off: t.CInt = term.line_count - vis3 + if max_off < 0: max_off = 0 + if term.scroll_offset < 0: term.scroll_offset = 0 + if term.scroll_offset > max_off: term.scroll_offset = max_off + term._update_scrollbar() + + sbar.command = c.Addr(_on_scroll) + + def _startswith(s: str, prefix: str) -> t.CInt: + plen: t.CSizeT = string.strlen(prefix) + if string.strncmp(s, prefix, plen) == 0: + return 1 + return 0 + + def _build_path(out: str, out_size: t.CInt, rel: str): + if c.Deref(rel) == 47: + string.strncpy(out, rel, out_size - 1) + else: + i: t.CInt = 0 + for i in range(63): + ch: t.CChar = _cwd[i] + c.DerefAs(out + i, ch) + if ch == 0: break + cwd_len: t.CInt = i + if cwd_len > 0 and _cwd[cwd_len - 1] != 47: + c.DerefAs(out + cwd_len, t.CChar(47)) + cwd_len += 1 + j: t.CInt = 0 + for j in range(out_size - 1 - cwd_len): + ch2: t.CChar = c.Deref(rel + j) + c.DerefAs(out + cwd_len + j, ch2) + if ch2 == 0: break + c.DerefAs(out + out_size - 1, t.CChar(0)) + + def _print_dir_entry(name: str, is_dir: t.CInt): + buf: t.CArray[t.CChar, 80] + j: t.CInt = 0 + for j in range(78): + ch: t.CChar = c.Deref(name + j) + buf[j] = ch + if ch == 0: break + if is_dir and j < 78: + buf[j] = 47 + j += 1 + buf[j] = 0 + term.insert_ptr(c.Addr(buf[0])) + + def _cmd_cd(path: str): + if path is None or c.Deref(path) == 0: + _cwd[0] = 47 + for i in range(1, 64): + _cwd[i] = 0 + return + full: t.CArray[t.CChar, 256] + _build_path(c.Addr(full[0]), 256, path) + dd: t.CInt = fs.opendir(c.Addr(full[0])) + if dd < 0: + term.insert("Error: dir not found") + return + fs.closedir(dd) + string.strncpy(c.Addr(_cwd[0]), c.Addr(full[0]), 63) + _cwd[63] = 0 + + def _cmd_ls(): + dd: t.CInt = fs.opendir(c.Addr(_cwd[0])) + if dd < 0: + term.insert("Error: cannot open dir") + return + info: t.CArray[t.CChar, 320] + string.memset(c.Addr(info[0]), 0, 320) + while True: + result: t.CInt = fs.readdir(dd, c.Addr(info[0])) + if result != 0: break + attr_val: t.CUInt8T = t.CUInt8T(c.Deref(c.Addr(info[4]))) + name_off: t.CInt = 16 + name_ptr: str = c.Addr(info[name_off]) + if c.Deref(name_ptr) == 0: break + if c.Deref(name_ptr) == 46: + if c.Deref(name_ptr + 1) == 0: + string.memset(c.Addr(info[0]), 0, 320) + continue + if c.Deref(name_ptr + 1) == 46 and c.Deref(name_ptr + 2) == 0: + string.memset(c.Addr(info[0]), 0, 320) + continue + if (attr_val & 0x10) != 0: + _print_dir_entry(name_ptr, 1) + else: + _print_dir_entry(name_ptr, 0) + string.memset(c.Addr(info[0]), 0, 320) + fs.closedir(dd) + + def _cmd_cat(path: str): + full: t.CArray[t.CChar, 256] + _build_path(c.Addr(full[0]), 256, path) + fd: t.CInt = fs.open(c.Addr(full[0]), fs.O_RDONLY) + if fd < 0: + term.insert("Error: file not found") + return + buf: t.CArray[t.CChar, 80] + while True: + string.memset(c.Addr(buf[0]), 0, 80) + n: t.CInt = fs.read(fd, c.Addr(buf[0]), 78) + if n <= 0: break + if n > 78: n = 78 + buf[n] = 0 + term.insert_ptr(c.Addr(buf[0])) + fs.close(fd) + + def _cmd_mkdir(path: str): + full: t.CArray[t.CChar, 256] + _build_path(c.Addr(full[0]), 256, path) + result: t.CInt = fs.mkdir(c.Addr(full[0])) + if result < 0: + term.insert("Error: mkdir failed") + else: + term.insert("Directory created") + + def _cmd_rm(path: str): + full: t.CArray[t.CChar, 256] + _build_path(c.Addr(full[0]), 256, path) + result: t.CInt = fs.remove(c.Addr(full[0])) + if result < 0: + term.insert("Error: remove failed") + else: + term.insert("Removed") + + def _cmd_touch(path: str): + full: t.CArray[t.CChar, 256] + _build_path(c.Addr(full[0]), 256, path) + fd: t.CInt = fs.open(c.Addr(full[0]), fs.O_WRONLY | fs.O_CREAT) + if fd < 0: + term.insert("Error: touch failed") + return + fs.close(fd) + term.insert("File created") + + def _cmd_sysinfo(): + term.insert("ViperOS v1.0 (x86_64)") + term.insert("Framebuffer: 32bpp") + term.insert("Compiler: TransPyC") + + def _cmd_hexdump(path: str): + full: t.CArray[t.CChar, 256] + _build_path(c.Addr(full[0]), 256, path) + fd: t.CInt = fs.open(c.Addr(full[0]), fs.O_RDONLY) + if fd < 0: + term.insert("Error: file not found") + return + offset: t.CInt = 0 + buf: t.CArray[t.CChar, 16] + while True: + string.memset(c.Addr(buf[0]), 0, 16) + n: t.CInt = fs.read(fd, c.Addr(buf[0]), 16) + if n <= 0: break + if n > 16: n = 16 + line: t.CArray[t.CChar, 80] + string.memset(c.Addr(line[0]), 0, 80) + viperlib.snprintf(c.Addr(line[0]), 80, "%08x: ", offset) + pos: t.CInt = 10 + j: t.CInt = 0 + for j in range(16): + if j < n: + ub: t.CUInt32T = t.CUInt32T(buf[j]) & 0xFF + hi: t.CInt = t.CInt(ub >> 4) + lo: t.CInt = t.CInt(ub & 0xF) + if hi < 10: + line[pos] = t.CChar(48 + hi) + else: + line[pos] = t.CChar(65 + hi - 10) + pos += 1 + if lo < 10: + line[pos] = t.CChar(48 + lo) + else: + line[pos] = t.CChar(65 + lo - 10) + pos += 1 + else: + line[pos] = 32 + pos += 1 + line[pos] = 32 + pos += 1 + line[pos] = 32 + pos += 1 + if j == 7: + line[pos] = 32 + pos += 1 + line[pos] = 124 + pos += 1 + for j in range(n): + b2: t.CUInt32T = t.CUInt32T(buf[j]) & 0xFF + if b2 >= 32 and b2 < 127: + line[pos] = t.CChar(b2) + else: + line[pos] = 46 + pos += 1 + line[pos] = 0 + term.insert_ptr(c.Addr(line[0])) + offset += n + fs.close(fd) + + def _cmd_run(path: str, blocking: t.CInt): + full: t.CArray[t.CChar, 256] + _build_path(c.Addr(full[0]), 256, path) + pid: t.CInt = process.spawn(c.Addr(full[0]), blocking) + if pid < 0: + term.insert("Error: cannot spawn") + elif blocking: + term.insert("Process exited") + else: + msg: t.CArray[t.CChar, 32] + viperlib.snprintf(c.Addr(msg[0]), 32, "Spawned pid=%d", pid) + term.insert_ptr(c.Addr(msg[0])) + + def _on_command() -> t.CInt: + _sdbg("[cmd] enter\n") + text: str = term.get() + _sdbg("[cmd] text=") + _sdbg(text) + _sdbg("\n") + if _startswith(text, "echo "): + term.insert_ptr(text + 5) + elif string.samestr(text, "echo"): + pass + elif string.samestr(text, "clear"): + term.delete(0, -1) + elif string.samestr(text, "help"): + term.insert("File: ls cd cat pwd mkdir rm touch") + term.insert("Sys: ver about sysinfo reboot") + term.insert("Util: echo clear help hexdump run runb") + elif string.samestr(text, "ver"): + term.insert("ViperOS Terminal v1.1") + elif string.samestr(text, "about"): + term.insert("ViperOS - A tiny OS in Viper lang") + elif string.samestr(text, "pwd"): + term.insert_ptr(c.Addr(_cwd[0])) + elif string.samestr(text, "ls"): + _cmd_ls() + elif string.samestr(text, "cd"): + _cmd_cd("") + elif _startswith(text, "cd "): + _cmd_cd(text + 3) + elif _startswith(text, "cat "): + _cmd_cat(text + 4) + elif _startswith(text, "mkdir "): + _cmd_mkdir(text + 6) + elif _startswith(text, "rm "): + _cmd_rm(text + 3) + elif _startswith(text, "touch "): + _cmd_touch(text + 6) + elif string.samestr(text, "sysinfo"): + _cmd_sysinfo() + elif string.samestr(text, "reboot"): + process.exit(0) + elif _startswith(text, "hexdump "): + _cmd_hexdump(text + 8) + elif _startswith(text, "run "): + _cmd_run(text + 4, 0) + elif _startswith(text, "runb "): + _cmd_run(text + 5, 1) + else: + if text is not None and c.Deref(text) != 0: + term.insert("Unknown command. Type 'help'.") + return 0 + + term.on_command = c.Addr(_on_command) + + root.mainloop() + process.exit(0) + return 0 diff --git a/Apps/Terminal/project.json b/Apps/Terminal/project.json new file mode 100644 index 0000000..6f139b0 --- /dev/null +++ b/Apps/Terminal/project.json @@ -0,0 +1,31 @@ +{ + "name": "Terminal", + "version": "1.0.0", + "source_dir": "./", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "--strip-all" + ], + "output": "terminal.elf" + }, + "includes": ["../.."], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} diff --git a/Libs/SerialLogger/linker.ld b/Libs/SerialLogger/linker.ld new file mode 100644 index 0000000..bd3258d --- /dev/null +++ b/Libs/SerialLogger/linker.ld @@ -0,0 +1,34 @@ +SECTIONS { + . = 0; + + .text : { + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + PROVIDE(__bss_start = .); + *(.bss) + *(.bss.*) + PROVIDE(__bss_end = .); + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.debug*) + } +} diff --git a/Libs/SerialLogger/project.json b/Libs/SerialLogger/project.json new file mode 100644 index 0000000..ae8a287 --- /dev/null +++ b/Libs/SerialLogger/project.json @@ -0,0 +1,34 @@ +{ + "name": "SerialLogger", + "version": "1.0.0", + "source_dir": "./", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=pic", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "-shared", + "-z", "notext", + "--no-relax", + "--strip-all" + ], + "output": "slog.so" + }, + "includes": ["../.."], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} diff --git a/Libs/SerialLogger/serial_logger.py b/Libs/SerialLogger/serial_logger.py new file mode 100644 index 0000000..235a4a7 --- /dev/null +++ b/Libs/SerialLogger/serial_logger.py @@ -0,0 +1,12 @@ +import vpsdk.syscall as syscall +import t, c + +def log_info(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CExport: + syscall._syscall1(t.CUInt64T(syscall.SERIAL_PUTS), t.CUInt64T("[SerialLogger] ")) + syscall._syscall1(t.CUInt64T(syscall.SERIAL_PUTS), t.CUInt64T(msg)) + syscall._syscall1(t.CUInt64T(syscall.SERIAL_PUTS), t.CUInt64T("\n")) + +def log_warn(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CExport: + syscall._syscall1(t.CUInt64T(syscall.SERIAL_PUTS), t.CUInt64T("[SerialLogger:WARN] ")) + syscall._syscall1(t.CUInt64T(syscall.SERIAL_PUTS), t.CUInt64T(msg)) + syscall._syscall1(t.CUInt64T(syscall.SERIAL_PUTS), t.CUInt64T("\n")) diff --git a/Libs/VPUI64/linker.ld b/Libs/VPUI64/linker.ld new file mode 100644 index 0000000..bd3258d --- /dev/null +++ b/Libs/VPUI64/linker.ld @@ -0,0 +1,34 @@ +SECTIONS { + . = 0; + + .text : { + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + PROVIDE(__bss_start = .); + *(.bss) + *(.bss.*) + PROVIDE(__bss_end = .); + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.debug*) + } +} diff --git a/Libs/VPUI64/project.json b/Libs/VPUI64/project.json new file mode 100644 index 0000000..8653f74 --- /dev/null +++ b/Libs/VPUI64/project.json @@ -0,0 +1,34 @@ +{ + "name": "VPUI64", + "version": "1.0.0", + "source_dir": "./", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=pic", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "-shared", + "-z", "notext", + "--no-relax", + "--strip-all" + ], + "output": "vpui64.so" + }, + "includes": ["../.."], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} diff --git a/Libs/VPUI64/vpui64.py b/Libs/VPUI64/vpui64.py new file mode 100644 index 0000000..ff544f1 --- /dev/null +++ b/Libs/VPUI64/vpui64.py @@ -0,0 +1,80 @@ +import t +import vpsdk.syscall as syscall + +UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr + +def color_rgb(r: t.CUInt8T, g: t.CUInt8T, b: t.CUInt8T) -> t.CUInt32T: + return t.CUInt32T(r) | (t.CUInt32T(g) << 8) | (t.CUInt32T(b) << 16) + +def get_winbuf(win_id: t.CInt) -> UINT32PTR: + return t.CVoid(syscall._syscall2(t.CUInt64T(syscall.GET_WINBUF), t.CUInt64T(win_id), 0), t.CPtr) + +def win_flush(win_id: t.CInt) -> t.CVoid: + syscall._syscall2(t.CUInt64T(syscall.WIN_FLUSH), t.CUInt64T(win_id), 0) + +def draw_pixel(buf: UINT32PTR, bw: t.CInt, x: t.CInt, y: t.CInt, color: t.CUInt32T) -> t.CVoid | t.CExport: + if x < 0 or y < 0: return + buf[y * bw + x] = color + +def draw_line(buf: UINT32PTR, bw: t.CInt, x0: t.CInt, y0: t.CInt, x1: t.CInt, y1: t.CInt, color: t.CUInt32T) -> t.CVoid | t.CExport: + dx: t.CInt = x1 - x0 + dy: t.CInt = y1 - y0 + sx: t.CInt = 1 + sy: t.CInt = 1 + if dx < 0: + dx = -dx + sx = -1 + if dy < 0: + dy = -dy + sy = -1 + err: t.CInt = dx - dy + while True: + if x0 >= 0 and y0 >= 0: + buf[y0 * bw + x0] = color + if x0 == x1 and y0 == y1: + break + e2: t.CInt = 2 * err + if e2 > -dy: + err -= dy + x0 += sx + if e2 < dx: + err += dx + y0 += sy + +def draw_rect(buf: UINT32PTR, bw: t.CInt, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T) -> t.CVoid | t.CExport: + draw_line(buf, bw, rx, ry, rx + rw - 1, ry, color) + draw_line(buf, bw, rx + rw - 1, ry, rx + rw - 1, ry + rh - 1, color) + draw_line(buf, bw, rx + rw - 1, ry + rh - 1, rx, ry + rh - 1, color) + draw_line(buf, bw, rx, ry + rh - 1, rx, ry, color) + +def fill_rect(buf: UINT32PTR, bw: t.CInt, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T) -> t.CVoid | t.CExport: + if rx < 0: rw += rx; rx = 0 + if ry < 0: rh += ry; ry = 0 + if rw <= 0 or rh <= 0: return + py: t.CInt = ry + while py < ry + rh: + px: t.CInt = rx + while px < rx + rw: + buf[py * bw + px] = color + px += 1 + py += 1 + +def draw_circle(buf: UINT32PTR, bw: t.CInt, cx: t.CInt, cy: t.CInt, r: t.CInt, color: t.CUInt32T) -> t.CVoid | t.CExport: + x: t.CInt = r + y: t.CInt = 0 + d: t.CInt = 1 - r + while x >= y: + if cx + x >= 0: buf[(cy + y) * bw + cx + x] = color + if cx + y >= 0: buf[(cy + x) * bw + cx + y] = color + if cx - y >= 0: buf[(cy + x) * bw + cx - y] = color + if cx - x >= 0: buf[(cy + y) * bw + cx - x] = color + if cx - x >= 0: buf[(cy - y) * bw + cx - x] = color + if cx - y >= 0: buf[(cy - x) * bw + cx - y] = color + if cx + y >= 0: buf[(cy - x) * bw + cx + y] = color + if cx + x >= 0: buf[(cy - y) * bw + cx + x] = color + y += 1 + if d <= 0: + d += 2 * y + 1 + else: + x -= 1 + d += 2 * (y - x) + 1 diff --git a/Scripts/check_disk.py b/Scripts/check_disk.py new file mode 100644 index 0000000..d9c8a65 --- /dev/null +++ b/Scripts/check_disk.py @@ -0,0 +1,91 @@ +import struct +f = open(r'd:\Users\TermiNexus\Desktop\TransPyC\ViperOS\disk.img', 'rb') +f.seek(0) +first_sector = f.read(512) +first_byte = first_sector[0] + +if first_byte == 0xEB or first_byte == 0xE9: + lba_start = 0 + bpb = first_sector +else: + ptype = struct.unpack_from(' 5: + return + cluster_sec = data_start + (cluster - 2) * sec_per_clus + f.seek((lba_start + cluster_sec) * 512) + dir_data = f.read(sec_per_clus * bytes_per_sec) + off = 0 + lfn_parts = {} + while off < len(dir_data): + first = dir_data[off] + if first == 0: + break + if first == 0xE5: + off += 32 + lfn_parts = {} + continue + attr = dir_data[off + 11] + if attr == 0x0F: # LFN entry + seq = first & 0x3F + lfn_chars = [] + for i in range(5): + c = struct.unpack_from('= 2: + # Skip . and .. entries + sfn_name = name_str.rstrip() + if sfn_name != '.' and sfn_name != '..': + read_dir(f, lba_start, data_start, sec_per_clus, bytes_per_sec, clus, indent + " ", depth + 1) + off += 32 + lfn_parts = {} + +read_dir(f, lba_start, data_start, sec_per_clus, bytes_per_sec, root_clus) +f.close() diff --git a/Scripts/disk.ps1 b/Scripts/disk.ps1 new file mode 100644 index 0000000..e0e344a --- /dev/null +++ b/Scripts/disk.ps1 @@ -0,0 +1,90 @@ +# Auto-elevate to administrator +if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + $maxRetries = 5 + $retryCount = 0 + $launched = $false + + while ($retryCount -lt $maxRetries -and -not $launched) { + $retryCount++ + try { + Write-Host "[Elevate $retryCount/$maxRetries] Requesting admin privileges..." -ForegroundColor Cyan + $proc = Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs -Wait -PassThru -ErrorAction Stop + if ($null -ne $proc) { + $launched = $true + } + } + catch { + Write-Host "Elevation failed: user cancelled or permission error" -ForegroundColor Yellow + if ($retryCount -ge $maxRetries) { + Write-Host "Max retries ($maxRetries) reached, exiting." -ForegroundColor Red + exit 1 + } + Start-Sleep -Milliseconds 300 + } + } + + if ($launched) { + exit 0 + } + exit 1 +} + +Write-Host "Running as administrator!" -ForegroundColor Green + +# ========================================================== +# ImDisk-based FAT32 disk image builder +# ========================================================== + +$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Definition +$PROJECT_DIR = Split-Path -Parent $SCRIPT_DIR +$DISK_IMG = Join-Path $PROJECT_DIR "disk.img" +$DISK_DIR = Join-Path $PROJECT_DIR "disk" +$MOUNT_LETTER = "Z:" + +$IMAGE_SIZE = 64 * 1024 * 1024 + +Write-Host "=== Creating disk image ===" -ForegroundColor Cyan + +if (-not (Get-Command "imdisk.exe" -ErrorAction SilentlyContinue)) { + Write-Host "Error: Cannot find imdisk.exe. Please ensure ImDisk is installed and added to system PATH." -ForegroundColor Red + exit 1 +} + +if (-not (Test-Path $DISK_DIR)) { + Write-Host "Error: Source directory not found $DISK_DIR" -ForegroundColor Red + exit 1 +} + +if (Test-Path $DISK_IMG) { + Write-Host "Removing existing disk image..." + Remove-Item -Force $DISK_IMG +} + +Write-Host "Creating $IMAGE_SIZE byte disk image..." +$fsutilFile = [System.IO.File]::Create($DISK_IMG) +$fsutilFile.SetLength($IMAGE_SIZE) +$fsutilFile.Close() + +if (Test-Path $MOUNT_LETTER) { + Write-Host "Warning: $MOUNT_LETTER already exists. Attempting to detach..." -ForegroundColor Yellow + imdisk -D -m $MOUNT_LETTER + Start-Sleep -Seconds 1 +} + +Write-Host "Mounting and formatting disk image to $MOUNT_LETTER ..." +imdisk -a -f $DISK_IMG -m $MOUNT_LETTER -o hd --% -p "/fs:fat32 /q /y" + +Start-Sleep -Seconds 2 + +Write-Host "Copying files to disk image..." +robocopy $DISK_DIR "$MOUNT_LETTER\" /E /NJH /NJS /np +if ($LASTEXITCODE -le 7) { + Write-Host "Files copied successfully." -ForegroundColor Green +} else { + Write-Host "Warning: File copy encountered an error. Robocopy exit code: $LASTEXITCODE" -ForegroundColor Yellow +} + +Write-Host "Unmounting disk image..." +imdisk -D -m $MOUNT_LETTER + +Write-Host "Disk image created: $DISK_IMG" -ForegroundColor Green diff --git a/Scripts/disk.sh b/Scripts/disk.sh new file mode 100644 index 0000000..92af10d --- /dev/null +++ b/Scripts/disk.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +DISK_IMG="$PROJECT_DIR/disk.img" +DISK_DIR="$PROJECT_DIR/disk" +MOUNT_POINT="/tmp/diskimg" + +SUDO_PASSWORD="198314qwe" + +echo "=== Creating disk image ===" + +cd "$PROJECT_DIR" + +IMAGE_SIZE=$((64 * 1024 * 1024)) + +if [ -f "$DISK_IMG" ]; then + echo "Removing existing disk image..." + rm -f "$DISK_IMG" +fi + +echo "Creating $IMAGE_SIZE byte disk image..." +dd if=/dev/zero of="$DISK_IMG" bs=1M count=64 status=none + +echo "Formatting disk image as FAT32..." +mkfs.vfat -F 32 "$DISK_IMG" > /dev/null + +echo "Mounting disk image..." +mkdir -p "$MOUNT_POINT" +echo "$SUDO_PASSWORD" | sudo -S mount -o loop "$DISK_IMG" "$MOUNT_POINT" + +echo "Copying files to disk image..." +echo "$SUDO_PASSWORD" | sudo -S cp -r "$DISK_DIR"/* "$MOUNT_POINT/" + +echo "Unmounting disk image..." +echo "$SUDO_PASSWORD" | sudo -S umount "$MOUNT_POINT" +rmdir "$MOUNT_POINT" + +echo "Disk image created: $DISK_IMG" diff --git a/VKernel/Kernel/bootinfo.py b/VKernel/Kernel/bootinfo.py new file mode 100644 index 0000000..ccf06c6 --- /dev/null +++ b/VKernel/Kernel/bootinfo.py @@ -0,0 +1,36 @@ +import t, c + + +MEMORY_TYPE_AVAILABLE: t.CUInt64T = 7 +MEMORY_TYPE_RESERVED: t.CUInt64T = 0 +MEMORY_TYPE_LOADER_CODE: t.CUInt64T = 1 +MEMORY_TYPE_LOADER_DATA: t.CUInt64T = 2 +MEMORY_TYPE_BOOT_SERVICES_CODE: t.CUInt64T = 3 +MEMORY_TYPE_BOOT_SERVICES_DATA: t.CUInt64T = 4 +MEMORY_TYPE_RUNTIME_SERVICES_CODE: t.CUInt64T = 5 +MEMORY_TYPE_RUNTIME_SERVICES_DATA: t.CUInt64T = 6 +MEMORY_TYPE_ACPI_RECLAIMABLE: t.CUInt64T = 9 +MEMORY_TYPE_ACPI_NVS: t.CUInt64T = 10 +MEMORY_TYPE_BAD_MEMORY: t.CUInt64T = 8 + +@c.Attribute(t.attr.packed) +class memory_map_entry: + type: t.CUnsignedInt + _pad: t.CUnsignedInt + physical_start: t.CUnsignedLong + virtual_start: t.CUnsignedLong + num_pages: t.CUnsignedLong + attribute: t.CUnsignedLong + +class bootinfo: + MemmapAddr: t.CUnsignedLong + MemmapSize: t.CUnsignedLong + MemmapDescSize: t.CUnsignedLong + kernel_PhysAddr: t.CUnsignedLong + framebuffer_addr: t.CUnsignedLong + framebuffer_size: t.CUnsignedLong + framebuffer_width: t.CUnsignedLong + framebuffer_height: t.CUnsignedLong + framebuffer_pitch: t.CUnsignedLong + framebuffer_format: t.CUnsignedLong + system_table: t.CUnsignedLong diff --git a/VKernel/Kernel/drivers/core/cpu/apic.py b/VKernel/Kernel/drivers/core/cpu/apic.py new file mode 100644 index 0000000..6bbd49a --- /dev/null +++ b/VKernel/Kernel/drivers/core/cpu/apic.py @@ -0,0 +1,251 @@ +import asm +import intr.gdt as gdt +import intr.idt as idt +import drivers.serial.uart.serial as serial +import drivers.core.cpu.cpu as cpu +import mm.mm as mm +import platform.pch.timer as timer +import viperlib +import t, c + + +IA32_APIC_BASE: t.CDefine = 0x1B +APIC_BASE_MSR_ENABLE: t.CDefine = 0x800 +APIC_BASE_ADDR_MASK: t.CDefine = 0xFFFFF000 + +LAPIC_ID: t.CDefine = 0x020 +LAPIC_VERSION: t.CDefine = 0x030 +LAPIC_TPR: t.CDefine = 0x080 +LAPIC_EOI: t.CDefine = 0x0B0 +LAPIC_LDR: t.CDefine = 0x0D0 +LAPIC_SVR: t.CDefine = 0x0F0 +LAPIC_ISR0: t.CDefine = 0x100 +LAPIC_ICR_LOW: t.CDefine = 0x300 +LAPIC_ICR_HIGH: t.CDefine = 0x310 +LAPIC_LVT_TIMER: t.CDefine = 0x320 +LAPIC_LVT_LINT0: t.CDefine = 0x350 +LAPIC_LVT_LINT1: t.CDefine = 0x360 +LAPIC_LVT_ERROR: t.CDefine = 0x370 +LAPIC_TIMER_INIT: t.CDefine = 0x380 +LAPIC_TIMER_CURRENT: t.CDefine = 0x390 +LAPIC_TIMER_DIVIDE: t.CDefine = 0x3E0 + +LAPIC_SVR_ENABLE: t.CDefine = 0x100 +LAPIC_LVT_MASKED: t.CDefine = 0x10000 +LAPIC_LVT_DELIVERY_EXTINT: t.CDefine = 0x700 +LAPIC_LVT_DELIVERY_NMI: t.CDefine = 0x400 +LAPIC_LVT_TIMER_PERIODIC: t.CDefine = 0x20000 + +LAPIC_ICR_INIT: t.CDefine = 0x500 +LAPIC_ICR_STARTUP: t.CDefine = 0x600 +LAPIC_ICR_ASSERT: t.CDefine = 0x4000 +LAPIC_ICR_DEASSERT: t.CDefine = 0x8000 +LAPIC_ICR_LEVEL: t.CDefine = 0x8000 + +MAX_CPUS: t.CDefine = 8 +AP_TRAMPOLINE_ADDR: t.CDefine = 0x7000 + +_lapic_base: t.CStatic | t.CUInt64T = 0 +_cpu_count: t.CStatic | t.CUInt32T = 1 +_bsp_lapic_id: t.CStatic | t.CUInt32T = 0 +_ap_ready: t.CStatic | t.CUInt32T = 0 + +def read(offset: t.CUInt32T) -> t.CUInt32T: + addr: t.CUInt64T = _lapic_base + t.CUInt64T(offset) + return c.Deref(t.CUInt32T(addr, t.CPtr)) + +def write(offset: t.CUInt32T, value: t.CUInt32T): + addr: t.CUInt64T = _lapic_base + t.CUInt64T(offset) + c.Set(c.Deref(t.CUInt32T(addr, t.CPtr)), value) + +def eoi(): + write(LAPIC_EOI, 0) + +def get_id() -> t.CUInt32T: + return read(LAPIC_ID) >> 24 + +@c.Attribute(t.attr.noinline) +def init(): + global _lapic_base, _bsp_lapic_id + msr_val: t.CUInt64T = cpu.msr_read(IA32_APIC_BASE) + _lapic_base = msr_val & APIC_BASE_ADDR_MASK + cpu.msr_write(IA32_APIC_BASE, msr_val | APIC_BASE_MSR_ENABLE) + _bsp_lapic_id = read(LAPIC_ID) >> 24 + write(LAPIC_SVR, LAPIC_SVR_ENABLE | 0xFF) + write(LAPIC_TPR, 0) + write(LAPIC_LVT_TIMER, LAPIC_LVT_MASKED) + write(LAPIC_LVT_LINT0, LAPIC_LVT_DELIVERY_EXTINT) + write(LAPIC_LVT_LINT1, LAPIC_LVT_DELIVERY_NMI) + write(LAPIC_LVT_ERROR, LAPIC_LVT_MASKED) + write(LAPIC_TIMER_DIVIDE, 0x0B) + write(LAPIC_LDR, 0x01000000) + eoi() + +@c.Attribute(t.attr.noinline) +def detect_cpu_count() -> t.CUInt32T: + res: cpu.cpuid_result + cpu.cpuid(0, c.Addr(res)) + max_leaf: t.CUInt32T = res.eax + + if max_leaf >= 0x0B: + cpu.cpuid_sub(0x0B, 0, c.Addr(res)) + num_threads: t.CUInt32T = res.ebx & 0xFFFF + if num_threads > 0 and num_threads <= MAX_CPUS: + return num_threads + + cpu.cpuid(1, c.Addr(res)) + if res.edx & cpu.CPU_FEATURE_HTT: + count: t.CUInt32T = (res.ebx >> 16) & 0xFF + if count > 0 and count <= MAX_CPUS: + return count + return 1 + +def _wait_icr(): + timeout: t.CUInt32T = 1000000 + while timeout > 0: + val: t.CUInt32T = read(LAPIC_ICR_LOW) + if (val & (1 << 12)) == 0: + return + timeout -= 1 + +def _send_ipi(apic_id: t.CUInt32T, vector: t.CUInt32T, delivery_mode: t.CUInt32T): + _wait_icr() + write(LAPIC_ICR_HIGH, apic_id << 24) + write(LAPIC_ICR_LOW, vector | delivery_mode | LAPIC_ICR_LEVEL | LAPIC_ICR_ASSERT) + _wait_icr() + +def ap_entry(): + global _ap_ready + my_id: t.CUInt32T = get_id() + buf: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(buf), 64, "[AP] core %u started (lapic=%u)\n", my_id, my_id) + serial.puts(buf) + _ap_ready = 1 + while True: + asm.sti() + asm.hlt() + +_trampoline_bin: t.CStatic | t.CArray[t.CUInt8T, 128] = [ + 0xFA, # cli + 0x31, 0xC0, # xor ax, ax + 0x8E, 0xD8, # mov ds, ax + 0x8E, 0xC0, # mov es, ax + 0x0F, 0x01, 0x16, 0x00, 0x7E, # lgdt [0x7E00] + 0x0F, 0x20, 0xC0, # mov eax, cr0 + 0x83, 0xC8, 0x01, # or eax, 1 + 0x0F, 0x22, 0xC0, # mov cr0, eax + 0xEA, 0x1A, 0x07, 0x00, 0x00, 0x08, 0x00, # jmp 0x08:0x701A + + # 32-bit 保护模式开始 + 0xB8, 0x10, 0x00, 0x00, 0x00, # mov ax, 0x10 + 0x8E, 0xD8, # mov ds, ax + 0x8E, 0xC0, # mov es, ax + 0x8E, 0xE0, # mov fs, ax + 0x8E, 0xE8, # mov gs, ax + 0x8E, 0xD0, # mov ss, ax + 0x0F, 0x20, 0xE0, # mov eax, cr4 + 0x83, 0xC8, 0x20, # or eax, 0x20 (PAE) + 0x0F, 0x22, 0xE0, # mov cr4, eax + 0x8B, 0x04, 0x25, 0x00, 0x7F, 0x00, 0x00, # mov eax, [0x7F00] + 0x0F, 0x22, 0xD8, # mov cr3, eax + 0xB9, 0x80, 0x00, 0x00, 0xC0, # mov ecx, 0xC0000080 (EFER) + 0x0F, 0x32, # rdmsr + 0x83, 0xC8, 0x00, 0x01, 0x00, 0x00, # or eax, 0x100 (LME) + 0x0F, 0x79, # wrmsr + 0x0F, 0x20, 0xC0, # mov eax, cr0 + 0x0D, 0x00, 0x00, 0x00, 0x80, # or eax, 0x80000000 (PG) + 0x0F, 0x22, 0xC0, # mov cr0, eax + 0xEA, 0x5A, 0x07, 0x00, 0x00, 0x08, 0x00, # jmp 0x08:0x705A + + # 64-bit 长模式开始 + 0x48, 0x8B, 0x24, 0x25, 0x08, 0x7F, 0x00, 0x00, # mov rsp, [0x7F08] + 0x0F, 0x01, 0x16, 0x10, 0x7F, # lgdt [0x7F10] + 0x0F, 0x01, 0x1E, 0x20, 0x7F, # lidt [0x7F20] + 0xB8, 0x10, 0x00, 0x00, 0x00, # mov ax, 0x10 + 0x8E, 0xD8, # mov ds, ax + 0x8E, 0xC0, # mov es, ax + 0x8E, 0xE0, # mov fs, ax + 0x8E, 0xE8, # mov gs, ax + 0x8E, 0xD0, # mov ss, ax + 0x48, 0x8B, 0x04, 0x25, 0x28, 0x7F, 0x00, 0x00, # mov rax, [0x7F28] + 0xFF, 0xE0 # jmp rax +] + +def _setup_trampoline(entry_addr: t.CUInt64T, stack_addr: t.CUInt64T, cr3_val: t.CUInt64T): + dest: t.CUInt8T | t.CPtr = t.CUInt8T(AP_TRAMPOLINE_ADDR, t.CPtr) + src: t.CUInt8T | t.CPtr = c.Addr(_trampoline_bin) + i: t.CInt + for i in range(128): + c.Set(c.Deref(dest + t.CUInt64T(i)), c.Deref(src + t.CUInt64T(i))) + + gdt_ptr_addr: t.CUInt64T = AP_TRAMPOLINE_ADDR + 0x0E00 + c.Set(c.Deref(t.CUInt16T(gdt_ptr_addr, t.CPtr)), t.CUInt16T(23)) + c.Set(c.Deref(t.CUInt64T(gdt_ptr_addr + 2, t.CPtr)), t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0E10)) + + gdt_addr: t.CUInt64T = AP_TRAMPOLINE_ADDR + 0x0E10 + c.Set(c.Deref(t.CUInt64T(gdt_addr, t.CPtr)), t.CUInt64T(0)) + c.Set(c.Deref(t.CUInt64T(gdt_addr + 8, t.CPtr)), t.CUInt64T(0x00AF9A000000FFFF)) + c.Set(c.Deref(t.CUInt64T(gdt_addr + 16, t.CPtr)), t.CUInt64T(0x00CF92000000FFFF)) + + c.Set(c.Deref(t.CUInt32T(AP_TRAMPOLINE_ADDR + 0x0F00, t.CPtr)), t.CUInt32T(cr3_val)) + c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F08, t.CPtr)), stack_addr) + + gp_addr: t.CUInt64T = t.CUInt64T(c.Addr(gdt.gp)) + gdt_base: t.CUInt64T = c.Deref(t.CUInt64T(gp_addr + 2, t.CPtr)) + gdt_limit: t.CUInt16T = c.Deref(t.CUInt16T(gp_addr, t.CPtr)) + c.Set(c.Deref(t.CUInt16T(AP_TRAMPOLINE_ADDR + 0x0F10, t.CPtr)), gdt_limit) + c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F12, t.CPtr)), gdt_base) + + idt_addr: t.CUInt64T = t.CUInt64T(c.Addr(idt.idp)) + idt_base: t.CUInt64T = c.Deref(t.CUInt64T(idt_addr + 2, t.CPtr)) + idt_limit: t.CUInt16T = c.Deref(t.CUInt16T(idt_addr, t.CPtr)) + c.Set(c.Deref(t.CUInt16T(AP_TRAMPOLINE_ADDR + 0x0F20, t.CPtr)), idt_limit) + c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F22, t.CPtr)), idt_base) + + c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F28, t.CPtr)), entry_addr) + +def start_aps(): + global _cpu_count, _ap_ready + if _cpu_count <= 1: return + + cr3_val: t.CUInt64T = 0 + c.Asm(f"""mov rax, cr3 + mov {c.AsmOut(cr3_val, t.ASM_DESCR.OUTPUT_REG)}, rax""", + [t.ASM_DESCR.CLOBBER_RAX]) + + stack_size: t.CUInt64T = 8192 + i: t.CUInt32T + for i in range(1, _cpu_count): + _ap_ready = 0 + stack: t.CVoid | t.CPtr = mm.malloc(stack_size) + if stack is None: continue + stack_top: t.CUInt64T = t.CUInt64T(stack) + stack_size + stack_top = stack_top & ~t.CUInt64T(15) + + _setup_trampoline(t.CUInt64T(ap_entry), stack_top, cr3_val) + + _send_ipi(i, 0, LAPIC_ICR_INIT) + timer.timer_msleep(10) + _send_ipi(i, (AP_TRAMPOLINE_ADDR >> 12) & 0xFF, LAPIC_ICR_STARTUP) + timer.timer_msleep(1) + _send_ipi(i, (AP_TRAMPOLINE_ADDR >> 12) & 0xFF, LAPIC_ICR_STARTUP) + + wait: t.CUInt32T = 0 + while _ap_ready == 0 and wait < 5000: + timer.timer_msleep(1) + wait += 1 + +def early_init(): + global _cpu_count, _bsp_lapic_id + features: t.CUInt32T = cpu.detect_features_static() + if features & cpu.CPU_FEATURE_APIC: + init() + _cpu_count = detect_cpu_count() + else: + _cpu_count = 1 + +def get_cpu_count() -> t.CUInt32T: + return _cpu_count + +def get_lapic_id() -> t.CUInt32T: + return get_id() diff --git a/VKernel/Kernel/drivers/core/cpu/cpu.py b/VKernel/Kernel/drivers/core/cpu/cpu.py new file mode 100644 index 0000000..650bfc3 --- /dev/null +++ b/VKernel/Kernel/drivers/core/cpu/cpu.py @@ -0,0 +1,346 @@ +import fpu +import asm +import intr.gdt as gdt +import string +import t, c + +IA32_KERNEL_GS_BASE: t.CDefine = 0xC0000102 +KERN_STACK_SIZE: t.CDefine = 8192 + + +CPU_FEATURE_FPU: t.CDefine = (1 << 0) +CPU_FEATURE_PSE: t.CDefine = (1 << 1) +CPU_FEATURE_MSR: t.CDefine = (1 << 2) +CPU_FEATURE_PAE: t.CDefine = (1 << 3) +CPU_FEATURE_MCE: t.CDefine = (1 << 4) +CPU_FEATURE_CX8: t.CDefine = (1 << 5) +CPU_FEATURE_APIC: t.CDefine = (1 << 6) +CPU_FEATURE_SEP: t.CDefine = (1 << 7) +CPU_FEATURE_MTRR: t.CDefine = (1 << 8) +CPU_FEATURE_PGE: t.CDefine = (1 << 9) +CPU_FEATURE_MCA: t.CDefine = (1 << 10) +CPU_FEATURE_CMOV: t.CDefine = (1 << 11) +CPU_FEATURE_PAT: t.CDefine = (1 << 12) +CPU_FEATURE_PSE36: t.CDefine = (1 << 13) +CPU_FEATURE_PSN: t.CDefine = (1 << 14) +CPU_FEATURE_CLFSH: t.CDefine = (1 << 15) +CPU_FEATURE_DS: t.CDefine = (1 << 16) +CPU_FEATURE_ACPI: t.CDefine = (1 << 17) +CPU_FEATURE_MMX: t.CDefine = (1 << 18) +CPU_FEATURE_FXSR: t.CDefine = (1 << 19) +CPU_FEATURE_SSE: t.CDefine = (1 << 20) +CPU_FEATURE_SSE2: t.CDefine = (1 << 21) +CPU_FEATURE_SS: t.CDefine = (1 << 22) +CPU_FEATURE_HTT: t.CDefine = (1 << 23) +CPU_FEATURE_TM: t.CDefine = (1 << 24) +CPU_FEATURE_PBE: t.CDefine = (1 << 25) + +class cpu_info(t.CStruct): + vendor_id: t.CArray[t.CChar, 13] + brand_string: t.CArray[t.CChar, 48] + features: t.CUInt32T + family: t.CUInt32T + model: t.CUInt32T + stepping: t.CUInt32T + cpu_type: t.CUInt32T + ext_model: t.CUInt32T + ext_family: t.CUInt32T + cpu_freq: t.CUInt64T + +@c.Attribute(t.attr.packed) +class _tss: + reserved0: t.CUInt64T + rsp0: t.CUInt64T + rsp1: t.CUInt64T + rsp2: t.CUInt64T + reserved1: t.CUInt64T + ist1: t.CUInt64T + ist2: t.CUInt64T + ist3: t.CUInt64T + ist4: t.CUInt64T + ist5: t.CUInt64T + ist6: t.CUInt64T + ist7: t.CUInt64T + reserved2: t.CUInt64T + reserved3: t.CUInt16T + io_map_base: t.CUInt16T + +class cpuid_result(t.CStruct): + eax: t.CUInt32T + ebx: t.CUInt32T + ecx: t.CUInt32T + edx: t.CUInt32T + +@c.Attribute(t.attr.naked) +def _rdmsr_naked(): + c.Asm(f"""rdmsr + shl rdx, 32 + or rax, rdx + ret""", + op=[t.ASM_DESCR.CLOBBER_RDX]) + +@c.Attribute(t.attr.naked) +def _wrmsr_naked(): + # msr_write sets ecx=msr, r8=value. wrmsr needs ECX=msr, EDX:EAX=value. + # Old code did `mov rax,rcx` (rax=msr) which wrote (value>>32):msr to MSR, + # corrupting IA32_KERNEL_GS_BASE -> swapgs -> #GP on gs:[0]. Fixed below. + c.Asm(f"""mov rax, r8 + mov rdx, r8 + shr rdx, 32 + wrmsr + ret""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RDX]) + +def msr_read(msr: t.CUInt32T) -> t.CUInt64T: + result: t.CUInt64T = 0 + func: t.CVoid | t.CPtr = _rdmsr_naked + c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)} + mov {c.AsmOut(result, t.ASM_DESCR.OUTPUT_REG)}, rax""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX]) + return result + +def msr_write(msr: t.CUInt32T, value: t.CUInt64T): + func: t.CVoid | t.CPtr = _wrmsr_naked + c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)} + mov r8, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R8]) + +@c.Attribute(t.attr.naked) +def _cpuid_naked(): + c.Asm(f"""push rbx + mov eax, ecx + cpuid + mov dword ptr [rdx], eax + mov dword ptr [rdx + 4], ebx + mov dword ptr [rdx + 8], ecx + mov dword ptr [rdx + 12], edx + pop rbx + ret""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_RDX]) + +@c.Attribute(t.attr.naked) +def _cpuid_sub_naked(): + c.Asm(f"""push rbx + mov eax, ecx + mov ecx, r8d + cpuid + mov dword ptr [rdx], eax + mov dword ptr [rdx + 4], ebx + mov dword ptr [rdx + 8], ecx + mov dword ptr [rdx + 12], edx + pop rbx + ret""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_RDX]) + +@c.Attribute(t.attr.noinline) +def cpuid(leaf: t.CUInt32T, out: cpuid_result | t.CPtr): + func: t.CVoid | t.CPtr = _cpuid_naked + c.Asm(f"""mov ecx, {c.AsmInp(leaf, t.ASM_DESCR.REG_ANY)} + mov rdx, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R8, + t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10, + t.ASM_DESCR.CLOBBER_R11]) + +@c.Attribute(t.attr.noinline) +def cpuid_sub(leaf: t.CUInt32T, sub: t.CUInt32T, out: cpuid_result | t.CPtr): + func: t.CVoid | t.CPtr = _cpuid_sub_naked + c.Asm(f"""mov ecx, {c.AsmInp(leaf, t.ASM_DESCR.REG_ANY)} + mov r8d, {c.AsmInp(sub, t.ASM_DESCR.REG_ANY)} + mov rdx, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R8, + t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10, + t.ASM_DESCR.CLOBBER_R11]) + +@c.Attribute(t.attr.noinline) +def detect_features_static() -> t.CUInt32T: + features: t.CUInt32T = 0 + res: cpuid_result + cpuid(1, c.Addr(res)) + if res.edx & (1 << 0): features |= CPU_FEATURE_FPU + if res.edx & (1 << 3): features |= CPU_FEATURE_PSE + if res.edx & (1 << 5): features |= CPU_FEATURE_MSR + if res.edx & (1 << 6): features |= CPU_FEATURE_PAE + if res.edx & (1 << 7): features |= CPU_FEATURE_MCE + if res.edx & (1 << 8): features |= CPU_FEATURE_CX8 + if res.edx & (1 << 9): features |= CPU_FEATURE_APIC + if res.edx & (1 << 11): features |= CPU_FEATURE_SEP + if res.edx & (1 << 12): features |= CPU_FEATURE_MTRR + if res.edx & (1 << 13): features |= CPU_FEATURE_PGE + if res.edx & (1 << 14): features |= CPU_FEATURE_MCA + if res.edx & (1 << 15): features |= CPU_FEATURE_CMOV + if res.edx & (1 << 16): features |= CPU_FEATURE_PAT + if res.edx & (1 << 17): features |= CPU_FEATURE_PSE36 + if res.edx & (1 << 18): features |= CPU_FEATURE_PSN + if res.edx & (1 << 19): features |= CPU_FEATURE_CLFSH + if res.edx & (1 << 21): features |= CPU_FEATURE_DS + if res.edx & (1 << 22): features |= CPU_FEATURE_ACPI + if res.edx & (1 << 23): features |= CPU_FEATURE_MMX + if res.edx & (1 << 24): features |= CPU_FEATURE_FXSR + if res.edx & (1 << 25): features |= CPU_FEATURE_SSE + if res.edx & (1 << 26): features |= CPU_FEATURE_SSE2 + if res.edx & (1 << 27): features |= CPU_FEATURE_SS + if res.edx & (1 << 28): features |= CPU_FEATURE_HTT + if res.edx & (1 << 29): features |= CPU_FEATURE_TM + if res.edx & (1 << 30): features |= CPU_FEATURE_PBE + return features + +@c.Attribute(t.attr.packed) +class per_cpu_data: + user_rsp: t.CUInt64T + kernel_rsp0: t.CUInt64T + current_pid: t.CInt + _pad0: t.CUInt32T + _pad1: t.CUInt64T + +_per_cpu: per_cpu_data + +@t.Object +class _CPUObject: + tss: _tss + features: t.CUInt32T + def __init__(self): + self.__init_tss__() + + def __init_tss__(self): + global _per_cpu + string.memset(c.Addr(self.tss), 0, self.tss.__sizeof__()) + self.tss.io_map_base = self.tss.__sizeof__() + string.memset(c.Addr(_per_cpu), 0, per_cpu_data.__sizeof__()) + gdt.setTSS64(5, t.CUInt64T(c.Addr(self.tss)), self.tss.__sizeof__() - 1) + gdt.flush() + # IA32_KERNEL_GS_BASE holds the per_cpu address for swapgs. + # When entering from Ring 3, swapgs swaps IA32_GS_BASE (0) with + # IA32_KERNEL_GS_BASE (_per_cpu), making gs: accessible in kernel. + # When returning to Ring 3, swapgs swaps back, restoring GS base to 0. + msr_write(IA32_KERNEL_GS_BASE, t.CUInt64T(c.Addr(_per_cpu))) + + def getInfo(self, info: cpu_info | t.CPtr): + if not info: return + res: cpuid_result + cpuid(0, c.Addr(res)) + (t.CUInt32T(info.vendor_id, t.CPtr))[0] = res.ebx + (t.CUInt32T(info.vendor_id, t.CPtr))[1] = res.edx + (t.CUInt32T(info.vendor_id, t.CPtr))[2] = res.ecx + info.vendor_id[12] = '\0' + info.features = self.detectFeatures() + cpuid(1, c.Addr(res)) + info.stepping = (res.eax >> 0) & 0x0F + info.model = (res.eax >> 4) & 0x0F + info.family = (res.eax >> 8) & 0x0F + info.cpu_type = (res.eax >> 12) & 0x03 + info.ext_model = (res.eax >> 16) & 0x0F + info.ext_family = (res.eax >> 20) & 0xFF + for i in range(4): + cpuid(0x80000002 + i, c.Addr(res)) + base: t.CUInt32T | t.CPtr = t.CUInt32T(info.brand_string, t.CPtr) + base[i * 4 + 0] = res.eax + base[i * 4 + 1] = res.ebx + base[i * 4 + 2] = res.ecx + base[i * 4 + 3] = res.edx + info.brand_string[47] = '\0' + info.cpu_freq = 2000000000 + + def detectFeatures(self) -> t.CUInt32T: + features: t.CInt32T = 0 + res: cpuid_result + cpuid(1, c.Addr(res)) + if res.edx & (1 << 0): features |= CPU_FEATURE_FPU + if res.edx & (1 << 3): features |= CPU_FEATURE_PSE + if res.edx & (1 << 5): features |= CPU_FEATURE_MSR + if res.edx & (1 << 6): features |= CPU_FEATURE_PAE + if res.edx & (1 << 7): features |= CPU_FEATURE_MCE + if res.edx & (1 << 8): features |= CPU_FEATURE_CX8 + if res.edx & (1 << 9): features |= CPU_FEATURE_APIC + if res.edx & (1 << 11): features |= CPU_FEATURE_SEP + if res.edx & (1 << 12): features |= CPU_FEATURE_MTRR + if res.edx & (1 << 13): features |= CPU_FEATURE_PGE + if res.edx & (1 << 14): features |= CPU_FEATURE_MCA + if res.edx & (1 << 15): features |= CPU_FEATURE_CMOV + if res.edx & (1 << 16): features |= CPU_FEATURE_PAT + if res.edx & (1 << 17): features |= CPU_FEATURE_PSE36 + if res.edx & (1 << 18): features |= CPU_FEATURE_PSN + if res.edx & (1 << 19): features |= CPU_FEATURE_CLFSH + if res.edx & (1 << 21): features |= CPU_FEATURE_DS + if res.edx & (1 << 22): features |= CPU_FEATURE_ACPI + if res.edx & (1 << 23): features |= CPU_FEATURE_MMX + if res.edx & (1 << 24): features |= CPU_FEATURE_FXSR + if res.edx & (1 << 25): features |= CPU_FEATURE_SSE + if res.edx & (1 << 26): features |= CPU_FEATURE_SSE2 + if res.edx & (1 << 27): features |= CPU_FEATURE_SS + if res.edx & (1 << 28): features |= CPU_FEATURE_HTT + if res.edx & (1 << 29): features |= CPU_FEATURE_TM + if res.edx & (1 << 30): features |= CPU_FEATURE_PBE + return features + + def enableInterrupts(self): + c.Asm("sti", [t.ASM_DESCR.CLOBBER_MEMORY]) + + def disableInterrupts(self): + c.Asm("cli", [t.ASM_DESCR.CLOBBER_MEMORY]) + + def enableNmis(self): + current: t.CUInt8T = asm.inb(0x70) + asm.outb(0x70, current & 0x7F) + + def disableNmis(self): + current: t.CUInt8T = asm.inb(0x70) + asm.outb(0x70, current | 0x80) + + def readCR0(self) -> t.CUInt64T: + cr0: t.CUInt64T + c.Asm(f"""mov rax, cr0 + mov {c.AsmOut(cr0, t.ASM_DESCR.OUTPUT_REG)}, rax""", + [t.ASM_DESCR.CLOBBER_RAX]) + return cr0 + + def writeCR0(self, value: t.CUInt64T): + c.Asm(f"""mov rax, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)} + mov cr0, rax""", + [t.ASM_DESCR.CLOBBER_RAX]) + + def readCR3(self) -> t.CUInt64T: + cr3: t.CUInt64T + c.Asm(f"""mov rax, cr3 + mov {c.AsmOut(cr3, t.ASM_DESCR.OUTPUT_REG)}, rax""", + [t.ASM_DESCR.CLOBBER_RAX]) + return cr3 + + def writeCR3(self, value: t.CUInt64T): + c.Asm(f"""mov rax, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)} + mov cr3, rax""", + [t.ASM_DESCR.CLOBBER_RAX]) + + def readCR4(self) -> t.CUInt64T: + cr4: t.CUInt64T + c.Asm(f"""mov rax, cr4 + mov {c.AsmOut(cr4, t.ASM_DESCR.OUTPUT_REG)}, rax""", + [t.ASM_DESCR.CLOBBER_RAX]) + return cr4 + + def writeCR4(self, value: t.CUInt64T): + c.Asm(f"""mov rax, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)} + mov cr4, rax""", + [t.ASM_DESCR.CLOBBER_RAX]) + + def readMSR(self, msr: t.CUInt32T) -> t.CUInt64T: + return msr_read(msr) + + def writeMSR(self, msr: t.CUInt32T, value: t.CUInt64T): + msr_write(msr, value) + + def setTSSRSP0(self, rsp0: t.CUInt64T): + global _per_cpu + self.tss.rsp0 = rsp0 + _per_cpu.kernel_rsp0 = rsp0 + +def set_kernel_rsp0(rsp0: t.CUInt64T): + global _per_cpu + _per_cpu.kernel_rsp0 = rsp0 diff --git a/VKernel/Kernel/drivers/core/cpu/fpu.py b/VKernel/Kernel/drivers/core/cpu/fpu.py new file mode 100644 index 0000000..f326fcf --- /dev/null +++ b/VKernel/Kernel/drivers/core/cpu/fpu.py @@ -0,0 +1,50 @@ +import cpu +import t, c + + +# FPU上下文结构体 +@c.Attribute(t.attr.aligned(16)) +class fpu_context: + data: t.CUInt8T[512] # FXSAVE区域大小 + + +@t.Object +class _FPUObject: + CPUObject: cpu._CPUObject | t.CPtr + # 初始化FPU + def __init__(self, CPUObject: cpu._CPUObject | t.CPtr): + self.enable() + self.CPUObject = CPUObject + c.Asm("fninit", [t.ASM_DESCR.CLOBBER_MEMORY]) + # 启用FPU + def enable(self): + cr0: t.CUInt64T = self.CPUObject.readCR0() + # 清除EM位(禁用仿真),设置MP位(监控协处理器) + cr0 &= ~(1 << 2); # 清除EM位 + cr0 |= (1 << 1); # 设置MP位 + # 写入CR0寄存器 + self.CPUObject.writeCR0(cr0) + # 读取CR4寄存器 + cr4: t.CUInt64T = self.CPUObject.readCR4() + # 设置OSFXSR位和OSXMMEXCPT位,启用SSE + cr4 |= (1 << 9) | (1 << 10) + # 写入CR4寄存器 + self.CPUObject.writeCR4(cr4) + # 初始化FPU + c.Asm("fninit", [t.ASM_DESCR.CLOBBER_MEMORY]) + # 禁用FPU + def disable(self): + cr0: t.CUInt64T = self.CPUObject.readCR0() + # 设置EM位(启用仿真),清除MP位(禁用监控协处理器) + cr0 |= (1 << 2) # 设置EM位 + cr0 &= ~(1 << 1) # 清除MP位 + # 写入CR0寄存器 + self.CPUObject.writeCR0(cr0) + # 保存FPU上下文 + def save_context(self, context: fpu_context | t.CPtr): + # 保存FPU状态到内存 + c.Asm(f"fxsave [{c.AsmInp(context, t.ASM_DESCR.REG_ANY)}]", [t.ASM_DESCR.CLOBBER_MEMORY]) + # 恢复FPU上下文 + def restore_context(self, context: t.CConst | fpu_context | t.CPtr): + # 从内存恢复FPU状态 + c.Asm(f"fxrstor [{c.AsmInp(context, t.ASM_DESCR.REG_ANY)}]", [t.ASM_DESCR.CLOBBER_MEMORY]) diff --git a/VKernel/Kernel/drivers/devfs/devfs.py b/VKernel/Kernel/drivers/devfs/devfs.py new file mode 100644 index 0000000..1fd9fb6 --- /dev/null +++ b/VKernel/Kernel/drivers/devfs/devfs.py @@ -0,0 +1,491 @@ +import drivers.serial.uart.serial as serial +import viperstring as string +import t, c + + +MAX_FILE_DESCRIPTORS: t.CDefine = 64 +MAX_DEVICE_FILES: t.CDefine = 32 +MAX_DIRECTORIES: t.CDefine = 16 +PATH_BUFFER_SIZE: t.CDefine = 256 + +directories: t.CArray[devfs_directory, MAX_DIRECTORIES] = [0] +directory_count: t.CStatic | t.CInt = 0 + +files: t.CArray[devfs_file, MAX_DEVICE_FILES] = [0] +file_count: t.CStatic | t.CInt = 0 + + +class _fd_table: + file: devfs_file | t.CPtr + flags: t.CInt + +fd_table: t.CArray[_fd_table, MAX_FILE_DESCRIPTORS] = [0] + +@t.Object +class devfs_directory: + name: t.CArray[t.CChar, 64] + parent: 'devfs_directory' | t.CPtr + + @staticmethod + def find(path: t.CConst | str, + root_directory: devfs_directory | t.CPtr) -> devfs_directory | t.CPtr: + if not path or string.strchr(path, '/') != path: return None + if not root_directory: return None + + current_dir: devfs_directory | t.CPtr = root_directory + result: t.CArray[str, PATH_BUFFER_SIZE] + token = string.split(path, "/", result) + + for i in result: + found: bool = False + for d in range(directory_count): + dirp: devfs_directory = c.Addr(directories[d]) + if dirp.parent == current_dir and string.samestr(dirp.name, i): + current_dir = dirp + found = True + break + if not found: return None + + return current_dir + + @staticmethod + def find_child(name: t.CConst | str, + parent: devfs_directory | t.CPtr) -> devfs_directory | t.CPtr: + for d in range(directory_count): + dirp: devfs_directory = c.Addr(directories[d]) + if dirp.parent == parent and string.samestr(dirp.name, name): + return dirp + return None + + @staticmethod + def list_subdirs(dir_ptr: devfs_directory | t.CPtr, + buffer: str, + size: t.CUInt32T, + pos: t.CInt) -> t.CInt: + for d in range(directory_count): + dirp: devfs_directory = c.Addr(directories[d]) + if dirp.parent == dir_ptr: + name_len: t.CInt = string.strlen(dirp.name) + if pos + name_len + 1 > size: + break + string.strcpy(buffer + pos, dirp.name) + pos += name_len + buffer[pos] = '\n' + pos += 1 + return pos + +@t.Object +@t.CVTable +class devfs_file: + name: t.CArray[t.CChar, 64] + private_data: t.CVoid | t.CPtr + parent_dir: 'devfs_directory' | t.CPtr + + @staticmethod + def find_in_dir(file_name: t.CConst | str, + dir_ptr: devfs_directory | t.CPtr) -> devfs_file | t.CPtr: + for f in range(file_count): + fp: devfs_file | t.CPtr = c.Addr(files[f]) + if fp.parent_dir == dir_ptr and string.samestr(fp.name, file_name): + return fp + return None + + @staticmethod + def list_in_dir(dir_ptr: devfs_directory | t.CPtr, + buffer: str, + size: t.CUInt32T, + pos: t.CInt) -> t.CInt: + for f in range(file_count): + filep: devfs_file | t.CPtr = c.Addr(files[f]) + if filep.parent_dir == dir_ptr: + name_len: t.CInt = string.strlen(filep.name) + if pos + name_len + 1 > size: + break + string.strcpy(buffer + pos, filep.name) + pos += name_len + buffer[pos] = '\n' + pos += 1 + return pos + + def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt: + return 0 + + def close(self, fd: t.CInt) -> t.CInt: + return 0 + + def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + return 0 + + def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + return 0 + + def ioctl(self, fd: t.CInt, request: t.CInt, arg: t.CVoid | t.CPtr) -> t.CInt: + return 0 + +@t.Object +class _DevFSObject: + root_directory: devfs_directory | t.CPtr + + def __init__(self): + global directory_count, file_count + self.root_directory = None + directory_count = 0 + file_count = 0 + string.memset(c.Addr(directories), 0, devfs_directory.__sizeof__() * MAX_DIRECTORIES) + string.memset(c.Addr(files), 0, devfs_file.__sizeof__() * MAX_DEVICE_FILES) + string.memset(c.Addr(fd_table), 0, _fd_table.__sizeof__() * MAX_FILE_DESCRIPTORS) + + def create_single_dir(self, parent: devfs_directory | t.CPtr, + dir_name: t.CConst | str) -> devfs_directory | t.CPtr: + global directory_count + if not parent or not dir_name: return None + if directory_count >= MAX_DIRECTORIES: return None + if string.strlen(dir_name) >= 64: return None + + new_dir: devfs_directory | t.CPtr = c.Addr(directories[directory_count]) + directory_count += 1 + + string.strcpy(new_dir.name, dir_name) + new_dir.parent = parent + + return new_dir + + def find_or_create_directory(self, path: t.CConst | str) -> devfs_directory | t.CPtr: + global directory_count + if not path or string.samestr(path, "/"): return None + + serial.puts("A") + if not self.root_directory: + self.root_directory = c.Addr(directories[directory_count]) + directory_count += 1 + string.strcpy(self.root_directory.name, "") + self.root_directory.parent = None + + serial.puts("B") + current: devfs_directory | t.CPtr = self.root_directory + buf: t.CArray[t.CChar, PATH_BUFFER_SIZE] + string.memset(c.Addr(buf), 0, PATH_BUFFER_SIZE) + buf_idx: t.CInt = 0 + p: t.CInt = 0 + if path[p] == '/': + p += 1 + serial.puts("C") + while path[p] != '\0': + if path[p] == '/': + if buf_idx > 0: + found_child: devfs_directory | t.CPtr = devfs_directory.find_child(buf, current) + if not found_child: + found_child = self.create_single_dir(current, buf) + if not found_child: return None + current = found_child + string.memset(c.Addr(buf), 0, PATH_BUFFER_SIZE) + buf_idx = 0 + p += 1 + else: + buf[buf_idx] = path[p] + buf_idx += 1 + p += 1 + serial.puts("D") + if buf_idx > 0: + found_child: devfs_directory | t.CPtr = devfs_directory.find_child(buf, current) + if not found_child: + found_child = self.create_single_dir(current, buf) + if not found_child: return None + current = found_child + serial.puts("E") + + return current + + def init(self) -> t.CInt: + self.create_directory("/dev") + self.create_directory("/dev/input") + return 0 + + def create_directory(self, path: t.CConst | str) -> t.CInt: + serial.puts("T1") + dir_ptr: devfs_directory | t.CPtr = self.find_or_create_directory(path) + serial.puts("T2") + return 0 if dir_ptr else -1 + + def create_file(self, path: t.CConst | str, + file_ptr: devfs_file | t.CPtr, + private_data: t.CVoid | t.CPtr) -> t.CInt: + global file_count + path_copy: t.CArray[t.CChar, PATH_BUFFER_SIZE] + string.memset(c.Addr(path_copy), 0, PATH_BUFFER_SIZE) + string.strcpy(path_copy, path) + + last_slash: str | t.CPtr = string.strrchr(path_copy, '/') + + if not last_slash: return -1 + + last_slash[0] = '\0' + dir_path = path_copy + file_name: str | t.CPtr = last_slash + 1 + + dir_ptr: devfs_directory | t.CPtr = self.find_or_create_directory(dir_path) + if not dir_ptr: return -1 + + if devfs_file.find_in_dir(file_name, dir_ptr): + return -1 + + if file_count >= MAX_DEVICE_FILES: return -1 + + if file_ptr: + string.strcpy(file_ptr.name, file_name) + file_ptr.private_data = private_data + file_ptr.parent_dir = dir_ptr + files[file_count] = file_ptr + else: + new_file: devfs_file | t.CPtr = c.Addr(files[file_count]) + string.strcpy(new_file.name, file_name) + new_file.private_data = private_data + new_file.parent_dir = dir_ptr + + file_count += 1 + return 0 + + def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt: + path_copy: t.CArray[t.CChar, PATH_BUFFER_SIZE] + string.memset(c.Addr(path_copy), 0, PATH_BUFFER_SIZE) + string.strcpy(path_copy, path) + + last_slash: str | t.CPtr = string.strrchr(path_copy, '/') + + if not last_slash: return -1 + + last_slash[0] = '\0' + dir_path = path_copy + file_name: str | t.CPtr = last_slash + 1 + + dir_ptr: devfs_directory | t.CPtr = devfs_directory.find(dir_path, self.root_directory) + if not dir_ptr: return -1 + + file_ptr: devfs_file | t.CPtr = devfs_file.find_in_dir(file_name, dir_ptr) + if not file_ptr: return -1 + + fd: t.CInt = -1 + for i in range(MAX_FILE_DESCRIPTORS): + if not fd_table[i].file: + fd = i + break + + if fd == -1: return -1 + + ret: t.CInt = file_ptr.open(path, flags) + if ret != 0: return ret + + fd_table[fd].file = file_ptr + fd_table[fd].flags = flags + return fd + + def close(self, fd: t.CInt) -> t.CInt: + if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file: + return -1 + file_ptr: devfs_file | t.CPtr = fd_table[fd].file + file_ptr.close(fd) + fd_table[fd].file = None + fd_table[fd].flags = 0 + return 0 + + def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file: + return -1 + file_ptr: devfs_file | t.CPtr = fd_table[fd].file + return file_ptr.read(fd, buffer, size) + + def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file: + return -1 + file_ptr: devfs_file | t.CPtr = fd_table[fd].file + return file_ptr.write(fd, buffer, size) + + def ioctl(self, fd: t.CInt, cmd: t.CUInt32T, arg: t.CVoid | t.CPtr) -> t.CInt: + if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file: + return -1 + file_ptr: devfs_file | t.CPtr = fd_table[fd].file + return file_ptr.ioctl(fd, cmd, arg) + + def list_directory(self, path: t.CConst | str, + buffer: str, + size: t.CUInt32T) -> t.CInt: + dir_ptr: devfs_directory | t.CPtr = devfs_directory.find(path, self.root_directory) + if not dir_ptr or not buffer or size == 0: return -1 + + pos: t.CInt = devfs_directory.list_subdirs(dir_ptr, buffer, size, 0) + pos = devfs_file.list_in_dir(dir_ptr, buffer, size, pos) + + return pos + +DevFSObject: _DevFSObject | t.CPtr +_DevFSObject_instance: _DevFSObject + + +def devfs_init() -> t.CInt: + global DevFSObject, _DevFSObject_instance + DevFSObject = c.Addr(_DevFSObject_instance) + _DevFSObject_instance = _DevFSObject() + # _DevFSObject_instance.__init__() + DevFSObject.init() + return 0 + +def devfs_create_directory(path: t.CConst | str) -> t.CInt: + return DevFSObject.create_directory(path) + +def devfs_create_file(path: t.CConst | str, + file_ptr: devfs_file | t.CPtr, + private_data: t.CVoid | t.CPtr) -> t.CInt: + return DevFSObject.create_file(path, file_ptr, private_data) + +def devfs_open(path: t.CConst | str, flags: t.CInt) -> t.CInt: + return DevFSObject.open(path, flags) + +def devfs_close(fd: t.CInt) -> t.CInt: + return DevFSObject.close(fd) + +def devfs_read(fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + return DevFSObject.read(fd, buffer, size) + +def devfs_write(fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + return DevFSObject.write(fd, buffer, size) + +def devfs_ioctl(fd: t.CInt, cmd: t.CUInt32T, arg: t.CVoid | t.CPtr) -> t.CInt: + return DevFSObject.ioctl(fd, cmd, arg) + +def devfs_list_directory(path: t.CConst | str, + buffer: str, + size: t.CUInt32T) -> t.CInt: + return DevFSObject.list_directory(path, buffer, size) + +serial0_file: devfs_file + +def serial0_open(self: t.CVoid | t.CPtr, path: t.CConst | str, flags: t.CInt) -> t.CInt: + serial.puts("[serial0] open: ") + serial.puts(path) + serial.puts("\n") + return 0 + +def serial0_close(self: t.CVoid | t.CPtr, fd: t.CInt) -> t.CInt: + serial.puts("[serial0] close\n") + return 0 + +def serial0_read(self: t.CVoid | t.CPtr, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + serial.puts("[serial0] read\n") + return 0 + +def serial0_write(self: t.CVoid | t.CPtr, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + serial.puts("[serial0] write\n") + serial.puts(str(buffer)) + serial.puts("\n") + return size + +def devfs_register_serial0() -> t.CInt: + global serial0_file + serial0_file = devfs_file() + serial0_file.open = serial0_open + serial0_file.close = serial0_close + serial0_file.read = serial0_read + serial0_file.write = serial0_write + return devfs_create_file("/dev/serial0", serial0_file, None) + +@t.Object +@t.CVTable +class serial1_device(devfs_file): + def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt: + serial.puts("[serial1] open: ") + serial.puts(path) + serial.puts("\n") + return 0 + + def close(self, fd: t.CInt) -> t.CInt: + serial.puts("[serial1] close\n") + return 0 + + def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + serial.puts("[serial1] read\n") + return 0 + + def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + serial.puts("[serial1] write\n") + serial.puts(str(buffer)) + serial.puts("\n") + return size + +serial1_file: serial1_device + +def devfs_register_serial1() -> t.CInt: + global serial1_file + serial1_file = serial1_device() + return devfs_create_file("/dev/serial1", serial1_file, None) + +import drivers.input.keyboard.keyboard as keyboard +import drivers.input.mouse.mouse as mouse + +@t.Object +@t.CVTable +class keyboard_device(devfs_file): + def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt: + return 0 + + def close(self, fd: t.CInt) -> t.CInt: + return 0 + + def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + count: t.CUInt32T = 0 + while count < size and keyboard.has_data(): + sc: t.CUInt8T = keyboard.read_scancode() + ch: t.CChar = keyboard.scancode_to_ascii(sc) + if ch != '\0': + c.Set(t.CChar(buffer, t.CPtr)[count], ch) + count += 1 + return count + + def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + return -1 + + def ioctl(self, fd: t.CInt, request: t.CInt, arg: t.CVoid | t.CPtr) -> t.CInt: + return 0 + +kbd_file: keyboard_device + +def devfs_register_keyboard() -> t.CInt: + global kbd_file + kbd_file = keyboard_device() + keyboard.init() + return devfs_create_file("/dev/input/keyboard", kbd_file, None) + +@t.Object +@t.CVTable +class mouse_device(devfs_file): + def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt: + return 0 + + def close(self, fd: t.CInt) -> t.CInt: + return 0 + + def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + if not mouse.has_data(): return 0 + pkt: mouse.mouse_packet + mouse.read_packet(c.Addr(pkt)) + if size >= 1: + c.Set(t.CUInt8T(buffer, t.CPtr)[0], pkt.buttons) + if size >= 5: + buf: t.CUInt32T | t.CPtr = t.CVoid(t.CUInt64T(buffer) + 1, t.CPtr) + c.Set(t.CInt32T(buf, t.CPtr)[0], mouse.get_x()) + c.Set(t.CInt32T(buf, t.CPtr)[1], mouse.get_y()) + return 5 + return 1 + + def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt: + return -1 + + def ioctl(self, fd: t.CInt, request: t.CInt, arg: t.CVoid | t.CPtr) -> t.CInt: + return 0 + +mse_file: mouse_device + +def devfs_register_mouse() -> t.CInt: + global mse_file + mse_file = mouse_device() + mouse.init() + return devfs_create_file("/dev/input/mouse", mse_file, None) diff --git a/VKernel/Kernel/drivers/fs/fat32/example_fs.py b/VKernel/Kernel/drivers/fs/fat32/example_fs.py new file mode 100644 index 0000000..dac35e6 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/example_fs.py @@ -0,0 +1,237 @@ +from stdint import * +import fat32 as fs +import fat32_types as types +import fat32_part as part +import fat32_mkfs as mkfs_mod +import t, c + + +def print_fresult(res: types.FRESULT, msg: t.CConst) -> t.CInt: + if res == types.FRESULT.FR_OK: + print(f"[成功] {msg}") + return 0 + else: + print(f"[失败] {msg}: {res}") + return -1 + + +def print_fresult_void(res: types.FRESULT, msg: t.CConst): + print_fresult(res, msg) + + +def example_main() -> t.CInt: + print("=== FAT32 文件系统完整示例 ===") + print() + + # 1. 扫描可用驱动器 + print("--- 1. 扫描可用驱动器 ---") + res: types.FRESULT = fs.scan_drives() + print_fresult_void(res, "扫描驱动器") + + drive_count: t.CInt = fs.get_drive_count() + print(f"找到 {drive_count} 个驱动器") + i: t.CInt + for i in range(drive_count): + letter: t.CChar = fs.get_drive_letter(i) + part_count: t.CInt = fs.get_partition_count(letter) + print(f" 驱动器 {i}: {letter}") + for j in range(part_count): + is_fat32: t.CInt = fs.is_partition_fat32(letter, j) + print(f" 分区 {j}: FAT32={is_fat32}") + print() + + # 2. 挂载文件系统 + print("--- 2. 挂载文件系统 ---") + res = fs.mount() + if print_fresult(res, "挂载文件系统") != 0: + print("提示: 可能需要先格式化磁盘") + print() + + # 3. 文件系统信息 + print("--- 3. 文件系统信息 ---") + mounted: t.CInt = fs.is_mounted() + print(f"已挂载: {mounted}") + if mounted: + total_clusters: t.CUInt32T = fs.get_total() + free_clusters: t.CUInt32T = fs.get_free() + cluster_size: t.CUInt32T = fs.get_cluster_size() + total_bytes: t.CUInt64T = t.CUInt64T(total_clusters) * t.CUInt64T(cluster_size) + free_bytes: t.CUInt64T = t.CUInt64T(free_clusters) * t.CUInt64T(cluster_size) + print(f"总簇数: {total_clusters}") + print(f"空闲簇数: {free_clusters}") + print(f"簇大小: {cluster_size} 字节") + print(f"总大小: {total_bytes} 字节") + print(f"空闲大小: {free_bytes} 字节") + print() + + # 4. 元数据操作 + print("--- 4. 元数据操作 ---") + fs.set_metadata(0x12345678) + metadata: t.CUInt32T = fs.get_metadata() + print(f"设置的元数据: 0x{metadata:08X}") + print() + + # 5. 获取 FAT 时间 + print("--- 5. 获取 FAT 时间 ---") + fattime: t.CUInt32T = fs.get_fattime() + print(f"当前 FAT 时间: 0x{fattime:08X}") + print() + + # 6. 目录操作 + if fs.is_mounted(): + print("--- 6. 目录操作 ---") + + # 创建目录 + res = fs.mkdir("/test_dir_1") + print_fresult_void(res, "创建目录 /test_dir_1") + + res = fs.mkdir("/test_dir_1/subdir") + print_fresult_void(res, "创建子目录 /test_dir_1/subdir") + + res = fs.mkdir("/test_dir_2") + print_fresult_void(res, "创建目录 /test_dir_2") + print() + + # 7. 文件操作 + print("--- 7. 文件操作 ---") + + # 创建并写入文件 + fp1: types.fileobj | t.CPtr = fs.open("/test_dir_1/file1.txt", types.FA_CREATE_ALWAYS | types.FA_WRITE) + if fp1 is not None: + test_data1: t.CConst = "Hello, World!" + written: t.CUInt32T = fs.write(fp1, test_data1, 13) + print(f"写入文件 /test_dir_1/file1.txt: {written} 字节") + fs.close(fp1) + + # 打开追加 + fp2: types.fileobj | t.CPtr = fs.open("/test_dir_1/file1.txt", types.FA_OPEN_ALWAYS | types.FA_WRITE | types.FA_OPEN_APPEND) + if fp2 is not None: + test_data2: t.CConst = " Append data!" + written2: t.CUInt32T = fs.write(fp2, test_data2, 13) + print(f"追加写入: {written2} 字节") + fs.close(fp2) + + # 读取文件 + fp3: types.fileobj | t.CPtr = fs.open("/test_dir_1/file1.txt", types.FA_OPEN_EXISTING | types.FA_READ) + if fp3 is not None: + buf: t.CArray[t.CUInt8T, 256] + read_bytes: t.CUInt32T = fs.read(fp3, t.CVoid(c.Addr(buf[0]), t.CPtr), 256) + print(f"读取字节数: {read_bytes}") + print(f"文件内容: {t.CChar(c.Addr(buf[0]), t.CPtr)}") + + # seek 和 tell + fs.seek(fp3, 5) + pos: t.CUInt32T = fs.tell(fp3) + print(f"seek 到位置 5, 当前位置: {pos}") + + read_bytes2: t.CUInt32T = fs.read(fp3, t.CVoid(c.Addr(buf[0]), t.CPtr), 256) + print(f"从位置 5 读取: {t.CChar(c.Addr(buf[0]), t.CPtr)}") + + # 获取文件大小 + fsize: t.CUInt32T = fs.size(fp3) + print(f"文件大小: {fsize} 字节") + + fs.close(fp3) + + # 创建另一个文件 + fp4: types.fileobj | t.CPtr = fs.open("/test_dir_1/large_file.txt", types.FA_CREATE_ALWAYS | types.FA_WRITE) + if fp4 is not None: + large_data: t.CArray[t.CUInt8T, 1024] + k: t.CInt + for k in range(1024): + large_data[k] = t.CUInt8T(ord('A') + (k % 26)) + written3: t.CUInt32T = fs.write(fp4, t.CVoid(c.Addr(large_data[0]), t.CPtr), 1024) + print(f"创建大文件, 写入: {written3} 字节") + fs.close(fp4) + print() + + # 8. 文件状态和属性 + print("--- 8. 文件状态和属性 ---") + + info: types.fileinfo + res = fs.stat("/test_dir_1/file1.txt", info) + print_fresult_void(res, "获取文件信息") + if res == types.FRESULT.FR_OK: + print(f" 文件名: {info.fname}") + print(f" 大小: {info.file_size} 字节") + print(f" 属性: 0x{info.attr:02X}") + print(f" 创建时间: 0x{info.ctime:04X}") + print(f" 创建日期: 0x{info.cdate:04X}") + print(f" 修改时间: 0x{info.mtime:04X}") + print(f" 修改日期: 0x{info.mdate:04X}") + print(f" 访问日期: 0x{info.adate:04X}") + + # 修改属性 + res = fs.chmod("/test_dir_1/file1.txt", types.AM_RDO, types.AM_RDO) + print_fresult_void(res, "设置文件为只读") + print() + + # 9. 重命名和移动 + print("--- 9. 重命名和移动 ---") + + res = fs.rename("/test_dir_1/file1.txt", "/test_dir_1/renamed.txt") + print_fresult_void(res, "重命名文件") + + res = fs.move("/test_dir_1/renamed.txt", "/test_dir_2/moved.txt") + print_fresult_void(res, "移动文件") + + res = fs.rename("/test_dir_2", "/test_dir_renamed") + print_fresult_void(res, "重命名目录") + print() + + # 10. 列出目录 + print("--- 10. 列出目录 ---") + + dp: types.dirobj + res = fs.opendir("/", dp) + if res == types.FRESULT.FR_OK: + print("根目录内容:") + dir_info: types.fileinfo + while True: + res = fs.readdir(dp, dir_info) + if res != types.FRESULT.FR_OK: + break + attr_str: t.CConst = "" + if dir_info.attr & types.AM_DIR: + attr_str = attr_str + "D" + else: + attr_str = attr_str + "F" + if dir_info.attr & types.AM_RDO: + attr_str = attr_str + "R" + if dir_info.attr & types.AM_HID: + attr_str = attr_str + "H" + if dir_info.attr & types.AM_SYS: + attr_str = attr_str + "S" + if dir_info.attr & types.AM_ARC: + attr_str = attr_str + "A" + print(f" [{attr_str}] {dir_info.fname} ({dir_info.file_size} 字节)") + fs.closedir(dp) + print() + + # 11. 删除文件和目录 + print("--- 11. 删除文件和目录 ---") + + res = fs.remove("/test_dir_renamed/moved.txt") + print_fresult_void(res, "删除文件") + + res = fs.remove("/test_dir_1/large_file.txt") + print_fresult_void(res, "删除大文件") + + res = fs.rmdir("/test_dir_1/subdir") + print_fresult_void(res, "删除子目录") + + res = fs.rmdir("/test_dir_1") + print_fresult_void(res, "删除目录 /test_dir_1") + + res = fs.rmdir("/test_dir_renamed") + print_fresult_void(res, "删除目录 /test_dir_renamed") + print() + + # 12. 卸载文件系统 + print("--- 12. 卸载文件系统 ---") + res = fs.unmount() + print_fresult_void(res, "卸载文件系统") + print() + + print("=== 示例完成 ===") + return 0 diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32.py b/VKernel/Kernel/drivers/fs/fat32/fat32.py new file mode 100644 index 0000000..2787033 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32.py @@ -0,0 +1,208 @@ +from stdint import * +import string +import fat32_types as types +import fat32_time as ftime +import fat32_mkfs as mkfs_mod +import fat32_part as part +import fat32_diskio as diskio +import sched.sched as sched +import t, c + + +default_fs: types.volinfo = types.volinfo() +_fs_lock: t.CInt = 0 + +def lock(): + global _fs_lock + while True: + c.Asm("cli") + if _fs_lock == 0: + _fs_lock = 1 + c.Asm("sti") + return + c.Asm("sti") + sched.Scheduler._yield() + +def unlock(): + global _fs_lock + _fs_lock = 0 + +def mount() -> types.FRESULT: + default_fs.pdrv = 0 + default_fs.hidden_sectors = 0 + buf: t.CArray[t.CUInt8T, 512] + dres: types.DRESULT = diskio.read(0, c.Addr(buf[0]), 0, 1) + if dres == types.DRESULT.RES_OK: + if buf[0] == 0xEB or buf[0] == 0xE9: + default_fs.hidden_sectors = 0 + else: + sig: t.CUInt16T = t.CUInt16T(buf[510]) | (t.CUInt16T(buf[511]) << 8) + if sig == 0xAA55: + ptype: t.CUInt8T = buf[450] + if ptype != 0: + slba: t.CUInt32T = t.CUInt32T(buf[454]) | (t.CUInt32T(buf[455]) << 8) | (t.CUInt32T(buf[456]) << 16) | (t.CUInt32T(buf[457]) << 24) + default_fs.hidden_sectors = slba + if default_fs.mounted: return types.FRESULT.FR_OK + dsk_res: types.DRESULT = diskio.status(default_fs.pdrv) + if dsk_res != types.DRESULT.RES_OK: + dsk_res = diskio.init(default_fs.pdrv) + if dsk_res != types.DRESULT.RES_OK: return types.FRESULT.FR_NOT_READY + default_fs.winsect = 0xFFFFFFFFFFFFFFFF + default_fs.fat_winsect = 0xFFFFFFFFFFFFFFFF + wres: types.FRESULT = default_fs.win_read(0) + if wres != types.FRESULT.FR_OK: return types.FRESULT.FR_DISK_ERR + bps: t.CUInt16T = default_fs.win[11] | (t.CUInt16T(default_fs.win[12]) << 8) + spc: t.CUInt8T = default_fs.win[13] + rsc: t.CUInt16T = default_fs.win[14] | (t.CUInt16T(default_fs.win[15]) << 8) + nf: t.CUInt8T = default_fs.win[16] + if bps != 512: return types.FRESULT.FR_NO_FILESYSTEM + if spc == 0 or (spc & (spc - 1)) != 0: return types.FRESULT.FR_NO_FILESYSTEM + if rsc == 0: return types.FRESULT.FR_NO_FILESYSTEM + if nf == 0: return types.FRESULT.FR_NO_FILESYSTEM + root_ent: t.CUInt16T = default_fs.win[17] | (t.CUInt16T(default_fs.win[18]) << 8) + fat16sz: t.CUInt16T = default_fs.win[22] | (t.CUInt16T(default_fs.win[23]) << 8) + tot32: t.CUInt32T = t.CUInt32T(default_fs.win[32]) | (t.CUInt32T(default_fs.win[33]) << 8) | (t.CUInt32T(default_fs.win[34]) << 16) | (t.CUInt32T(default_fs.win[35]) << 24) + fat32sz: t.CUInt32T = t.CUInt32T(default_fs.win[36]) | (t.CUInt32T(default_fs.win[37]) << 8) | (t.CUInt32T(default_fs.win[38]) << 16) | (t.CUInt32T(default_fs.win[39]) << 24) + root_clus: t.CUInt32T = t.CUInt32T(default_fs.win[44]) | (t.CUInt32T(default_fs.win[45]) << 8) | (t.CUInt32T(default_fs.win[46]) << 16) | (t.CUInt32T(default_fs.win[47]) << 24) + if root_ent != 0 or fat16sz != 0: return types.FRESULT.FR_NO_FILESYSTEM + if fat32sz == 0: return types.FRESULT.FR_NO_FILESYSTEM + if tot32 == 0: return types.FRESULT.FR_NO_FILESYSTEM + if default_fs.win[510] != 0x55 or default_fs.win[511] != 0xAA: return types.FRESULT.FR_NO_FILESYSTEM + fsinfo_sec: t.CUInt16T = default_fs.win[48] | (t.CUInt16T(default_fs.win[49]) << 8) + bkboot_sec: t.CUInt16T = default_fs.win[50] | (t.CUInt16T(default_fs.win[51]) << 8) + default_fs.sector_size = 512 + default_fs.cluster_sectors = spc + default_fs.cluster_size = t.CUInt32T(spc) * 512 + default_fs.num_fats = nf + default_fs.reserved_sectors = rsc + default_fs.fat_size = fat32sz + default_fs.fat_start = t.CUInt64T(rsc) + default_fs.total_sectors = t.CUInt64T(tot32) + default_fs.root_cluster = root_clus + default_fs.fsinfo_sector = fsinfo_sec + default_fs.backup_sector = bkboot_sec + default_fs.vol_id = t.CUInt32T(default_fs.win[67]) | (t.CUInt32T(default_fs.win[68]) << 8) | (t.CUInt32T(default_fs.win[69]) << 16) | (t.CUInt32T(default_fs.win[70]) << 24) + data_sectors: t.CUInt64T = default_fs.total_sectors - t.CUInt64T(rsc) - t.CUInt64T(t.CUInt64T(nf) * t.CUInt64T(fat32sz)) + default_fs.total_clusters = t.CUInt32T(data_sectors / t.CUInt64T(spc)) + default_fs.data_start = default_fs.fat_start + t.CUInt64T(t.CUInt64T(nf) * t.CUInt64T(fat32sz)) + default_fs.last_alloc = 2 + default_fs.free_clusters = 0xFFFFFFFF + if fsinfo_sec != 0: + default_fs.fsinfo_read() + default_fs.mounted = 1 + return types.FRESULT.FR_OK + +def unmount() -> types.FRESULT: + return default_fs.unmount() + +def format(sec_per_clus: t.CUInt8T = 0, vol_id: t.CUInt32T = 0) -> types.FRESULT: + return mkfs_mod.mkfs(default_fs.pdrv, sec_per_clus, vol_id) + +def open(path: t.CConst | str, mode: t.CUInt8T) -> types.fileobj | t.CPtr: + return default_fs.file_open(path, mode) + +def close(fp: types.fileobj | t.CPtr) -> types.FRESULT: + return fp.close() + +def read(fp: types.fileobj | t.CPtr, buff: t.CVoid | t.CPtr, btr: t.CUInt32T) -> t.CUInt32T: + br: t.CUInt32T = 0 + fp.read(buff, btr, c.Addr(br)) + return br + +def write(fp: types.fileobj | t.CPtr, buff: t.CConst | t.CVoid | t.CPtr, btw: t.CUInt32T) -> t.CUInt32T: + bw: t.CUInt32T = 0 + fp.write(buff, btw, c.Addr(bw)) + return bw + +def seek(fp: types.fileobj | t.CPtr, offset: t.CUInt32T) -> types.FRESULT: + return fp.seek(offset) + +def tell(fp: types.fileobj | t.CPtr) -> t.CUInt32T: + return fp.tell() + +def size(fp: types.fileobj | t.CPtr) -> t.CUInt32T: + return fp.size() + +def truncate(fp: types.fileobj | t.CPtr) -> types.FRESULT: + return fp.truncate() + +def remove(path: t.CConst | str) -> types.FRESULT: + return default_fs.file_delete(path) + +def rename(old_path: t.CConst | str, new_path: t.CConst | str) -> types.FRESULT: + return default_fs.file_rename(old_path, new_path) + +def move(old_path: t.CConst | str, new_path: t.CConst | str) -> types.FRESULT: + return default_fs.file_rename(old_path, new_path) + +def stat(path: t.CConst | str, info: types.fileinfo | t.CPtr) -> types.FRESULT: + return default_fs.file_stat(path, info) + +def mkdir(path: t.CConst | str) -> types.FRESULT: + return default_fs.dir_make(path) + +def rmdir(path: t.CConst | str) -> types.FRESULT: + return default_fs.dir_remove(path) + +def opendir(path: t.CConst | str, dp: types.dirobj | t.CPtr) -> types.FRESULT: + return default_fs.dir_open_path(path, dp) + +def readdir(dp: types.dirobj | t.CPtr, info: types.fileinfo | t.CPtr) -> types.FRESULT: + return default_fs.dir_read(dp, info) + +def closedir(dp: types.dirobj | t.CPtr): + dp.is_open = 0 + +def chmod(path: t.CConst | str, attr: t.CUInt8T, mask: t.CUInt8T) -> types.FRESULT: + return default_fs.chmod(path, attr, mask) + +def utime(path: t.CConst | str, mtime: t.CUInt16T, mdate: t.CUInt16T) -> types.FRESULT: + return default_fs.utime(path, mtime, mdate) + +def get_free() -> t.CUInt32T: + if not default_fs.mounted: return 0 + if default_fs.free_clusters == 0xFFFFFFFF: + return default_fs.count_free_clusters() + return default_fs.free_clusters + +def get_total() -> t.CUInt32T: + if not default_fs.mounted: return 0 + return default_fs.total_clusters + +def get_cluster_size() -> t.CUInt32T: + if not default_fs.mounted: return 0 + return default_fs.cluster_size + +def is_mounted() -> t.CInt: + return default_fs.mounted + +def last_error() -> t.CUInt32T: + return default_fs.last_error + +def get_fattime() -> t.CUInt32T: + return ftime.get_fattime() + +def get_metadata() -> t.CUInt32T: + return default_fs.metadata + +def set_metadata(val: t.CUInt32T): + default_fs.metadata = val + +def scan_drives() -> types.FRESULT: + return part.scan_drives() + +def get_drive_count() -> t.CInt: + return part.get_drive_count() + +def get_drive_letter(idx: t.CInt) -> t.CChar: + return part.get_drive_letter(idx) + +def set_drive_letter(idx: t.CInt, letter: t.CChar) -> types.FRESULT: + return part.set_drive_letter(idx, letter) + +def get_partition_count(letter: t.CChar) -> t.CInt: + return part.get_partition_count(letter) + +def is_partition_fat32(letter: t.CChar, part_idx: t.CInt) -> t.CInt: + return part.is_partition_fat32(letter, part_idx) + diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_core.py b/VKernel/Kernel/drivers/fs/fat32/fat32_core.py new file mode 100644 index 0000000..eb59ae2 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_core.py @@ -0,0 +1,16 @@ +from stdint import * +import fat32_types as types +import t, c + + +_read32 = types._read32 +_read16 = types._read16 +_write32 = types._write32 +_write16 = types._write16 + + +def is_eoc(cluster: t.CUInt32T) -> t.CInt: + return 1 if (cluster >= types.CLUSTER_EOC_MIN and cluster <= types.CLUSTER_EOC_MAX) else 0 + +def is_free(cluster: t.CUInt32T) -> t.CInt: + return 1 if cluster == types.CLUSTER_FREE else 0 diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_dir.py b/VKernel/Kernel/drivers/fs/fat32/fat32_dir.py new file mode 100644 index 0000000..5f85280 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_dir.py @@ -0,0 +1,13 @@ +from stdint import * +import fat32_types as types +import t, c + + +sfn_checksum = types.sfn_checksum +uni2oem_placeholder = types.uni2oem_placeholder +lfn_get_char = types.lfn_get_char +lfn_set_char = types.lfn_set_char +lfn_extract = types.lfn_extract +sfn_from_lfn = types.sfn_from_lfn +sfn_to_str = types.sfn_to_str +_split_path = types._split_path diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_diskio.py b/VKernel/Kernel/drivers/fs/fat32/fat32_diskio.py new file mode 100644 index 0000000..fe9d00d --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_diskio.py @@ -0,0 +1,76 @@ +import drivers.storage.ide.ide as ide +import fat32_types as types +import spinlock +import string +import t, c + + +MAX_DRIVES: t.CDefine = 4 + +drive_initialized: t.CArray[t.CInt, MAX_DRIVES] +drive_lock: spinlock._spinlock = spinlock._spinlock() + +def status(pdrv: t.CUInt8T) -> types.DRESULT: + if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR + if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY + return types.DRESULT.RES_OK + +def init(pdrv: t.CUInt8T) -> types.DRESULT: + if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR + drive_lock.lock() + err: t.CInt = ide.init() + if err != 0: + drive_lock.unlock() + return types.DRESULT.RES_NOTRDY + drive_initialized[pdrv] = 1 + drive_lock.unlock() + return types.DRESULT.RES_OK + +def read(pdrv: t.CUInt8T, buff: t.CUInt8T | t.CPtr, sector: t.CUInt32T, count: t.CUInt32T) -> types.DRESULT: + if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR + if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY + drive_lock.lock() + i: t.CUInt32T + for i in range(count): + rd_res: t.CInt = ide.readSector(sector + i, buff + (i * 512)) + if rd_res != 0: + drive_lock.unlock() + return types.DRESULT.RES_ERROR + drive_lock.unlock() + return types.DRESULT.RES_OK + +def write(pdrv: t.CUInt8T, buff: t.CConst | t.CUInt8T | t.CPtr, sector: t.CUInt32T, count: t.CUInt32T) -> types.DRESULT: + if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR + if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY + drive_lock.lock() + i: t.CUInt32T + for i in range(count): + if ide.writeSector(sector + i, buff + (i * 512)) != 0: + drive_lock.unlock() + return types.DRESULT.RES_ERROR + drive_lock.unlock() + return types.DRESULT.RES_OK + +def ioctl(pdrv: t.CUInt8T, cmd: t.CUInt8T, buff: t.CVoid | t.CPtr) -> types.DRESULT: + if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR + if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY + match cmd: + case types.GET_SECTOR_COUNT: + disk_size: t.CUInt64T = ide.getDiskSize() + if disk_size == 0: + c.Set(t.CUInt32T(buff, t.CPtr), 131072) + elif disk_size > 0xFFFFFFFF: + c.Set(t.CUInt32T(buff, t.CPtr), 0xFFFFFFFF) + else: + c.Set(t.CUInt32T(buff, t.CPtr), t.CUInt32T(disk_size)) + return types.DRESULT.RES_OK + case types.GET_SECTOR_SIZE: + c.Set(t.CUInt16T(buff, t.CPtr), 512) + return types.DRESULT.RES_OK + case types.GET_BLOCK_SIZE: + c.Set(t.CUInt32T(buff, t.CPtr), 1) + return types.DRESULT.RES_OK + case types.CTRL_SYNC: + return types.DRESULT.RES_OK + case _: + return types.DRESULT.RES_PARERR diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_file.py b/VKernel/Kernel/drivers/fs/fat32/fat32_file.py new file mode 100644 index 0000000..49f8bd9 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_file.py @@ -0,0 +1,17 @@ +from stdint import * +import fat32_types as types +import t, c + + +handle_table = types.handle_table +lock_table = types.lock_table +handles_initialized = types.handles_initialized +handles_init = types.handles_init +handle_alloc = types.handle_alloc +lock_check = types.lock_check +lock_acquire = types.lock_acquire +lock_release = types.lock_release + +def handle_free(fp): + fp.is_open = 0 + fp.lock_count = 0 diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_mkfs.py b/VKernel/Kernel/drivers/fs/fat32/fat32_mkfs.py new file mode 100644 index 0000000..e34b950 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_mkfs.py @@ -0,0 +1,143 @@ +from stdint import * +import fat32_types as types +import fat32_diskio as diskio +import fat32_time as ftime +import string +import t, c + + +def _div_round_up(a: t.CUInt64T, b: t.CUInt64T) -> t.CUInt64T: + return (a + b - 1) / b + +def _calc_fat_size(total_sectors: t.CUInt64T, reserved: t.CUInt32T, sec_per_clus: t.CUInt8T, num_fats: t.CUInt8T) -> t.CUInt32T: + tmp: t.CUInt64T = total_sectors - t.CUInt64T(reserved) + fat_sz: t.CUInt32T = 1 + while True: + data_sectors: t.CUInt64T = tmp - t.CUInt64T(fat_sz) * t.CUInt64T(num_fats) + total_clusters: t.CUInt32T = t.CUInt32T(data_sectors / t.CUInt64T(sec_per_clus)) + needed: t.CUInt64T = _div_round_up(t.CUInt64T(total_clusters) * 4, 512) + if needed <= t.CUInt64T(fat_sz): break + fat_sz = t.CUInt32T(needed) + return fat_sz + +def _write_boot_sector(pdrv: t.CUInt8T, total_sectors: t.CUInt64T, sec_per_clus: t.CUInt8T, vol_id: t.CUInt32T, fat_sz: t.CUInt32T) -> types.DRESULT: + buf: t.CArray[t.CUInt8T, 512] + string.memset(c.Addr(buf[0]), 0, 512) + buf[0] = 0xEB; buf[1] = 0x58; buf[2] = 0x90 + buf[3] = 'M'; buf[4] = 'S'; buf[5] = 'W'; buf[6] = 'I'; buf[7] = 'N' + buf[8] = '4'; buf[9] = '.'; buf[10] = '1' + types._write16(c.Addr(buf[0]), 11, 512) + buf[13] = sec_per_clus + types._write16(c.Addr(buf[0]), 14, 32) + buf[16] = 2 + types._write16(c.Addr(buf[0]), 17, 0) + buf[21] = 0xF8 + types._write16(c.Addr(buf[0]), 22, 0) + types._write16(c.Addr(buf[0]), 24, 63) + types._write16(c.Addr(buf[0]), 26, 255) + types._write32(c.Addr(buf[0]), 28, 0) + types._write32(c.Addr(buf[0]), 32, t.CUInt32T(total_sectors)) + types._write32(c.Addr(buf[0]), 36, fat_sz) + types._write16(c.Addr(buf[0]), 40, 0) + types._write16(c.Addr(buf[0]), 42, 0) + types._write32(c.Addr(buf[0]), 44, 2) + types._write16(c.Addr(buf[0]), 48, 1) + types._write16(c.Addr(buf[0]), 50, 6) + buf[64] = 0x80 + buf[66] = 0x29 + types._write32(c.Addr(buf[0]), 67, vol_id) + vol_label: t.CArray[t.CChar, 11] + vol_label[0] = 'N'; vol_label[1] = 'O'; vol_label[2] = ' '; vol_label[3] = ' ' + vol_label[4] = ' '; vol_label[5] = ' '; vol_label[6] = ' '; vol_label[7] = ' ' + vol_label[8] = ' '; vol_label[9] = ' '; vol_label[10] = ' ' + i: t.CInt + for i in range(11): buf[71 + i] = t.CUInt8T(vol_label[i]) + buf[82] = 'F'; buf[83] = 'A'; buf[84] = 'T'; buf[85] = '3' + buf[86] = '2'; buf[87] = ' '; buf[88] = ' '; buf[89] = ' ' + buf[510] = 0x55; buf[511] = 0xAA + return diskio.write(pdrv, c.Addr(buf[0]), 0, 1) + +def _write_fsinfo(pdrv: t.CUInt8T, free_count: t.CUInt32T, next_free: t.CUInt32T) -> types.DRESULT: + buf: t.CArray[t.CUInt8T, 512] + string.memset(c.Addr(buf[0]), 0, 512) + types._write32(c.Addr(buf[0]), 0, 0x41615252) + types._write32(c.Addr(buf[0]), 484, 0x61417272) + types._write32(c.Addr(buf[0]), 488, free_count) + types._write32(c.Addr(buf[0]), 492, next_free) + types._write32(c.Addr(buf[0]), 508, 0xAA550000) + return diskio.write(pdrv, c.Addr(buf[0]), 1, 1) + +def _init_fat(pdrv: t.CUInt8T, fat_start: t.CUInt64T, fat_sz: t.CUInt32T, root_cluster: t.CUInt32T) -> types.DRESULT: + buf: t.CArray[t.CUInt8T, 512] + string.memset(c.Addr(buf[0]), 0, 512) + types._write32(c.Addr(buf[0]), 0, 0x0FFFFFF8) + types._write32(c.Addr(buf[0]), 4, 0x0FFFFFFF) + types._write32(c.Addr(buf[0]), 8, 0x0FFFFFFF) + if root_cluster >= 2: + cluster_off: t.CUInt32T = root_cluster * 4 + sector_off: t.CUInt32T = cluster_off / 512 + byte_off: t.CUInt32T = cluster_off % 512 + if sector_off == 0: + types._write32(c.Addr(buf[0]), byte_off, 0x0FFFFFFF) + res: types.DRESULT = diskio.write(pdrv, c.Addr(buf[0]), t.CUInt32T(fat_start), 1) + if res != types.DRESULT.RES_OK: return res + string.memset(c.Addr(buf[0]), 0, 512) + s: t.CUInt32T + for s in range(1, fat_sz): + res2: types.DRESULT = diskio.write(pdrv, c.Addr(buf[0]), t.CUInt32T(fat_start) + s, 1) + if res2 != types.DRESULT.RES_OK: return res2 + return types.DRESULT.RES_OK + +def _init_root_dir(pdrv: t.CUInt8T, root_sector: t.CUInt64T, sec_per_clus: t.CUInt8T) -> types.DRESULT: + buf: t.CArray[t.CUInt8T, 512] + string.memset(c.Addr(buf[0]), 0, 512) + s: t.CUInt8T + for s in range(sec_per_clus): + res: types.DRESULT = diskio.write(pdrv, c.Addr(buf[0]), t.CUInt32T(root_sector + t.CUInt64T(s)), 1) + if res != types.DRESULT.RES_OK: return res + return types.DRESULT.RES_OK + +def _calc_sec_per_clus(total_sectors: t.CUInt64T) -> t.CUInt8T: + ts: t.CUInt64T = total_sectors + if ts < 532480: return 1 + elif ts < 16777216: return 8 + elif ts < 33554432: return 16 + elif ts < 67108864: return 32 + else: return 64 + +def mkfs(pdrv: t.CUInt8T, sec_per_clus: t.CUInt8T, vol_id: t.CUInt32T) -> types.FRESULT: + if pdrv != types.DEV_DISK: return types.FRESULT.FR_INVALID_DRIVE + total_sectors_buf: t.CUInt32T = 0 + diskio.ioctl(pdrv, types.GET_SECTOR_COUNT, c.Addr(total_sectors_buf)) + total_sectors: t.CUInt64T = t.CUInt64T(total_sectors_buf) + if total_sectors < 65536: return types.FRESULT.FR_MKFS_ABORTED + if sec_per_clus == 0: sec_per_clus = _calc_sec_per_clus(total_sectors) + if sec_per_clus != 1 and sec_per_clus != 2 and sec_per_clus != 4 and sec_per_clus != 8 and sec_per_clus != 16 and sec_per_clus != 32 and sec_per_clus != 64 and sec_per_clus != 128: + return types.FRESULT.FR_INVALID_PARAMETER + if vol_id == 0: vol_id = ftime.get_fattime() + reserved: t.CUInt32T = 32 + num_fats: t.CUInt8T = 2 + fat_sz: t.CUInt32T = _calc_fat_size(total_sectors, reserved, sec_per_clus, num_fats) + data_sectors: t.CUInt64T = total_sectors - t.CUInt64T(reserved) - t.CUInt64T(fat_sz) * t.CUInt64T(num_fats) + total_clusters: t.CUInt32T = t.CUInt32T(data_sectors / t.CUInt64T(sec_per_clus)) + free_count: t.CUInt32T = total_clusters - 1 + res: types.DRESULT = _write_boot_sector(pdrv, total_sectors, sec_per_clus, vol_id, fat_sz) + if res != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR + res2: types.DRESULT = _write_fsinfo(pdrv, free_count, 3) + if res2 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR + boot_backup: t.CArray[t.CUInt8T, 512] + diskio.read(pdrv, c.Addr(boot_backup[0]), 0, 1) + diskio.write(pdrv, c.Addr(boot_backup[0]), 6, 1) + fsinfo_backup: t.CArray[t.CUInt8T, 512] + diskio.read(pdrv, c.Addr(fsinfo_backup[0]), 1, 1) + diskio.write(pdrv, c.Addr(fsinfo_backup[0]), 7, 1) + fat_start: t.CUInt64T = t.CUInt64T(reserved) + res3: types.DRESULT = _init_fat(pdrv, fat_start, fat_sz, 2) + if res3 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR + fat2_start: t.CUInt64T = fat_start + t.CUInt64T(fat_sz) + res4: types.DRESULT = _init_fat(pdrv, fat2_start, fat_sz, 2) + if res4 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR + root_sector: t.CUInt64T = fat2_start + t.CUInt64T(fat_sz) + res5: types.DRESULT = _init_root_dir(pdrv, root_sector, sec_per_clus) + if res5 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR + return types.FRESULT.FR_OK diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_part.py b/VKernel/Kernel/drivers/fs/fat32/fat32_part.py new file mode 100644 index 0000000..611270c --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_part.py @@ -0,0 +1,147 @@ +from stdint import * +import fat32_types as types +import fat32_diskio as diskio +import string +import t, c + + +MAX_VOLUMES: t.CDefine = 4 +MAX_PARTITIONS: t.CDefine = 4 + +MBR_SIGNATURE: t.CDefine = 0xAA55 +PART_TYPE_FAT32: t.CDefine = 0x0C +PART_TYPE_FAT32_LBA: t.CDefine = 0x0B +PART_TYPE_FAT16: t.CDefine = 0x06 +PART_TYPE_FAT16_LBA: t.CDefine = 0x0E + +class partition_entry: + boot_flag: t.CUInt8T + start_chs: t.CArray[t.CUInt8T, 3] + part_type: t.CUInt8T + end_chs: t.CArray[t.CUInt8T, 3] + start_lba: t.CUInt32T + total_sectors: t.CUInt32T + +drive_pdrv: t.CArray[t.CUInt8T, MAX_VOLUMES] +drive_num_parts: t.CArray[t.CInt, MAX_VOLUMES] +drive_letter_map: t.CArray[t.CChar, MAX_VOLUMES] +volume_mounted: t.CArray[t.CInt, MAX_VOLUMES] +part_types: t.CArray[t.CUInt8T, MAX_VOLUMES * MAX_PARTITIONS] +part_start_lbas: t.CArray[t.CUInt64T, MAX_VOLUMES * MAX_PARTITIONS] +part_totals: t.CArray[t.CUInt64T, MAX_VOLUMES * MAX_PARTITIONS] +part_is_fat32: t.CArray[t.CInt, MAX_VOLUMES * MAX_PARTITIONS] + +num_drives: t.CInt = 0 + +def _init(): + if num_drives > 0: return + i: t.CInt + for i in range(MAX_VOLUMES): + drive_letter_map[i] = 0 + volume_mounted[i] = 0 + drive_pdrv[i] = 0 + drive_num_parts[i] = 0 + j: t.CInt + for j in range(MAX_VOLUMES * MAX_PARTITIONS): + part_types[j] = 0 + part_start_lbas[j] = 0 + part_totals[j] = 0 + part_is_fat32[j] = 0 + +def _part_idx(drive: t.CInt, part: t.CInt) -> t.CInt: + return drive * MAX_PARTITIONS + part + +def parse_mbr(pdrv: t.CUInt8T, drive_idx: t.CInt) -> types.FRESULT: + buf: t.CArray[t.CUInt8T, 512] + res: types.DRESULT = diskio.read(pdrv, c.Addr(buf[0]), 0, 1) + if res != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR + if buf[0] == 0xEB or buf[0] == 0xE9: + return types.FRESULT.FR_NO_FILESYSTEM + sig: t.CUInt16T = t.CUInt16T(buf[510]) | (t.CUInt16T(buf[511]) << 8) + if sig != MBR_SIGNATURE: return types.FRESULT.FR_NO_FILESYSTEM + drive_pdrv[drive_idx] = pdrv + drive_num_parts[drive_idx] = 0 + i: t.CInt + for i in range(4): + base: t.CUInt32T = 446 + t.CUInt32T(i * 16) + ptype: t.CUInt8T = buf[base + 4] + slba: t.CUInt32T = t.CUInt32T(buf[base + 8]) | (t.CUInt32T(buf[base + 9]) << 8) | (t.CUInt32T(buf[base + 10]) << 16) | (t.CUInt32T(buf[base + 11]) << 24) + tsec: t.CUInt32T = t.CUInt32T(buf[base + 12]) | (t.CUInt32T(buf[base + 13]) << 8) | (t.CUInt32T(buf[base + 14]) << 16) | (t.CUInt32T(buf[base + 15]) << 24) + if ptype != 0 and tsec > 0: + pi: t.CInt = drive_num_parts[drive_idx] + if pi < MAX_PARTITIONS: + pidx: t.CInt = _part_idx(drive_idx, pi) + part_types[pidx] = ptype + part_start_lbas[pidx] = t.CUInt64T(slba) + part_totals[pidx] = t.CUInt64T(tsec) + part_is_fat32[pidx] = 1 if (ptype == PART_TYPE_FAT32 or ptype == PART_TYPE_FAT32_LBA) else 0 + drive_num_parts[drive_idx] = pi + 1 + return types.FRESULT.FR_OK + +def scan_drives() -> types.FRESULT: + _init() + global num_drives + num_drives = 0 + pdrv: t.CUInt8T + for pdrv in range(MAX_VOLUMES): + res: types.DRESULT = diskio.init(pdrv) + if res != types.DRESULT.RES_OK: continue + res2: types.FRESULT = parse_mbr(pdrv, num_drives) + if res2 == types.FRESULT.FR_OK and drive_num_parts[num_drives] > 0: + drive_letter_map[num_drives] = t.CChar(ord('C') + num_drives) + else: + drive_pdrv[num_drives] = pdrv + drive_num_parts[num_drives] = 1 + pidx: t.CInt = _part_idx(num_drives, 0) + part_types[pidx] = PART_TYPE_FAT32_LBA + part_start_lbas[pidx] = 0 + part_totals[pidx] = 0 + part_is_fat32[pidx] = 1 + drive_letter_map[num_drives] = t.CChar(ord('C') + num_drives) + num_drives = num_drives + 1 + if num_drives >= MAX_VOLUMES: break + return types.FRESULT.FR_OK + +def get_drive(letter: t.CChar) -> t.CInt: + _init() + i: t.CInt + for i in range(num_drives): + if drive_letter_map[i] == letter or drive_letter_map[i] == (letter - 32) or drive_letter_map[i] == (letter + 32): + return i + return -1 + +def get_pdrv(letter: t.CChar) -> t.CUInt8T: + idx: t.CInt = get_drive(letter) + if idx < 0: return 0 + return drive_pdrv[idx] + +def get_partition_start(letter: t.CChar, part_idx: t.CInt) -> t.CUInt64T: + idx: t.CInt = get_drive(letter) + if idx < 0: return 0 + if part_idx >= drive_num_parts[idx]: return 0 + pidx: t.CInt = _part_idx(idx, part_idx) + return part_start_lbas[pidx] + +def get_partition_count(letter: t.CChar) -> t.CInt: + idx: t.CInt = get_drive(letter) + if idx < 0: return 0 + return drive_num_parts[idx] + +def is_partition_fat32(letter: t.CChar, part_idx: t.CInt) -> t.CInt: + idx: t.CInt = get_drive(letter) + if idx < 0: return 0 + if part_idx >= drive_num_parts[idx]: return 0 + pidx: t.CInt = _part_idx(idx, part_idx) + return part_is_fat32[pidx] + +def get_drive_count() -> t.CInt: + return num_drives + +def get_drive_letter(idx: t.CInt) -> t.CChar: + if idx < 0 or idx >= num_drives: return 0 + return drive_letter_map[idx] + +def set_drive_letter(idx: t.CInt, letter: t.CChar) -> types.FRESULT: + if idx < 0 or idx >= num_drives: return types.FRESULT.FR_INVALID_PARAMETER + drive_letter_map[idx] = letter + return types.FRESULT.FR_OK diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_time.py b/VKernel/Kernel/drivers/fs/fat32/fat32_time.py new file mode 100644 index 0000000..30e1e7a --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_time.py @@ -0,0 +1,64 @@ +from stdint import * +import t, c +import asm + + +rtc_cmos_addr: t.CUInt16T = 0x70 +rtc_cmos_data: t.CUInt16T = 0x71 + +def cmos_read(reg: t.CUInt8T) -> t.CUInt8T: + asm.outb(rtc_cmos_addr, reg) + return asm.inb(rtc_cmos_data) + +def bcd_to_bin(bcd: t.CUInt8T) -> t.CUInt8T: + return (bcd >> 4) * 10 + (bcd & 0x0F) + +def get_rtc_time() -> t.CUInt32T: + while cmos_read(0x0A) & 0x80: pass + sec: t.CUInt8T = cmos_read(0x00) + min_: t.CUInt8T = cmos_read(0x02) + hour: t.CUInt8T = cmos_read(0x04) + day: t.CUInt8T = cmos_read(0x07) + mon: t.CUInt8T = cmos_read(0x08) + year: t.CUInt8T = cmos_read(0x09) + reg_b: t.CUInt8T = cmos_read(0x0B) + if not (reg_b & 0x04): + sec = bcd_to_bin(sec) + min_ = bcd_to_bin(min_) + hour = bcd_to_bin(hour) + day = bcd_to_bin(day) + mon = bcd_to_bin(mon) + year = bcd_to_bin(year) + fat_year: t.CUInt16T = t.CUInt16T(year) + 2000 - 1980 + if fat_year > 127: fat_year = 127 + fat_date: t.CUInt16T = (fat_year << 9) | (t.CUInt16T(mon) << 5) | t.CUInt16T(day) + fat_time: t.CUInt16T = (t.CUInt16T(hour) << 11) | (t.CUInt16T(min_) << 5) | (t.CUInt16T(sec) / 2) + return (t.CUInt32T(fat_date) << 16) | t.CUInt32T(fat_time) + +def get_fattime() -> t.CUInt32T: + return get_rtc_time() + +def to_year(ft: t.CUInt32T) -> t.CUInt16T: + return t.CUInt16T((ft >> 25) & 0x7F) + 1980 + +def to_month(ft: t.CUInt32T) -> t.CUInt8T: + return t.CUInt8T((ft >> 21) & 0x0F) + +def to_day(ft: t.CUInt32T) -> t.CUInt8T: + return t.CUInt8T((ft >> 16) & 0x1F) + +def to_hour(ft: t.CUInt32T) -> t.CUInt8T: + return t.CUInt8T((ft >> 11) & 0x1F) + +def to_minute(ft: t.CUInt32T) -> t.CUInt8T: + return t.CUInt8T((ft >> 5) & 0x3F) + +def to_second(ft: t.CUInt32T) -> t.CUInt8T: + return t.CUInt8T((ft & 0x1F) * 2) + +def make(year: t.CUInt16T, month: t.CUInt8T, day: t.CUInt8T, hour: t.CUInt8T, minute: t.CUInt8T, second: t.CUInt8T) -> t.CUInt32T: + fat_year: t.CUInt16T = year - 1980 + if fat_year > 127: fat_year = 127 + fat_date: t.CUInt16T = (fat_year << 9) | (t.CUInt16T(month) << 5) | t.CUInt16T(day) + fat_time: t.CUInt16T = (t.CUInt16T(hour) << 11) | (t.CUInt16T(minute) << 5) | (t.CUInt16T(second) / 2) + return (t.CUInt32T(fat_date) << 16) | t.CUInt32T(fat_time) diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_types.py b/VKernel/Kernel/drivers/fs/fat32/fat32_types.py new file mode 100644 index 0000000..b2a8788 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_types.py @@ -0,0 +1,1698 @@ +from stdint import * +import t, c +import spinlock +import string +import fat32_diskio as diskio +import fat32_time as ftime + + +def _read32(buf: t.CUInt8T | t.CPtr, off: t.CUInt32T) -> t.CUInt32T: + return t.CUInt32T(buf[off]) | (t.CUInt32T(buf[off + 1]) << 8) | (t.CUInt32T(buf[off + 2]) << 16) | (t.CUInt32T(buf[off + 3]) << 24) + +def _read16(buf: t.CUInt8T | t.CPtr, off: t.CUInt32T) -> t.CUInt16T: + return t.CUInt16T(buf[off]) | (t.CUInt16T(buf[off + 1]) << 8) + +def _write32(buf: t.CUInt8T | t.CPtr, off: t.CUInt32T, val: t.CUInt32T): + buf[off + 0] = t.CUInt8T(val & 0xFF) + buf[off + 1] = t.CUInt8T((val >> 8) & 0xFF) + buf[off + 2] = t.CUInt8T((val >> 16) & 0xFF) + buf[off + 3] = t.CUInt8T((val >> 24) & 0xFF) + +def _write16(buf: t.CUInt8T | t.CPtr, off: t.CUInt32T, val: t.CUInt16T): + buf[off + 0] = t.CUInt8T(val & 0xFF) + buf[off + 1] = t.CUInt8T((val >> 8) & 0xFF) + + +SECTOR_SIZE: t.CDefine = 512 +ENTRY_SIZE: t.CDefine = 32 +CLUSTER_FREE: t.CDefine = 0x00000000 +CLUSTER_EOC_MIN: t.CDefine = 0x0FFFFFF8 +CLUSTER_EOC_MAX: t.CDefine = 0x0FFFFFFF +CLUSTER_BAD: t.CDefine = 0x0FFFFFF7 +CLUSTER_ROOT: t.CDefine = 0x00000002 +FAT_MASK: t.CDefine = 0x0FFFFFFF +FAT_RESERVED: t.CDefine = 0xF0000000 + +MAX_LFN: t.CDefine = 255 +LFN_ENTRY_CHARS: t.CDefine = 13 +LFN_ATTR: t.CDefine = 0x0F +LFN_FLAG_LAST: t.CDefine = 0x40 +SFN_NAME_LEN: t.CDefine = 8 +SFN_EXT_LEN: t.CDefine = 3 +SFN_DELETED: t.CDefine = 0xE5 +SFN_DOT: t.CDefine = 0x05 +SFN_FREE: t.CDefine = 0x00 + +MAX_HANDLES: t.CDefine = 32 +MAX_LOCKS: t.CDefine = 32 +PATH_MAX: t.CDefine = 260 +NAME_MAX: t.CDefine = 255 +DEPTH_MAX: t.CDefine = 32 + +FA_READ: t.CDefine = 0x01 +FA_WRITE: t.CDefine = 0x02 +FA_OPEN_EXISTING: t.CDefine = 0x00 +FA_CREATE_NEW: t.CDefine = 0x04 +FA_CREATE_ALWAYS: t.CDefine = 0x08 +FA_OPEN_ALWAYS: t.CDefine = 0x10 +FA_OPEN_APPEND: t.CDefine = 0x30 +FA_READ_MASK: t.CDefine = 0x0F + +AM_RDO: t.CDefine = 0x01 +AM_HID: t.CDefine = 0x02 +AM_SYS: t.CDefine = 0x04 +AM_VOL: t.CDefine = 0x08 +AM_DIR: t.CDefine = 0x10 +AM_ARC: t.CDefine = 0x20 +AM_LFN: t.CDefine = 0x0F +AM_ANY: t.CDefine = 0x3F + +FS_FAT32: t.CDefine = 3 + +CTRL_SYNC: t.CDefine = 0 +GET_SECTOR_COUNT: t.CDefine = 1 +GET_SECTOR_SIZE: t.CDefine = 2 +GET_BLOCK_SIZE: t.CDefine = 3 +CTRL_TRIM: t.CDefine = 4 + +STA_NOINIT: t.CDefine = 0x01 +STA_NODISK: t.CDefine = 0x02 +STA_PROTECT: t.CDefine = 0x04 + +DEV_DISK: t.CDefine = 0 + +ENTRIES_PER_SECTOR: t.CDefine = 512 / 32 + + +class FRESULT(t.CEnum): + FR_OK: t.State = 0 + FR_DISK_ERR: t.State + FR_INT_ERR: t.State + FR_NOT_READY: t.State + FR_NO_FILE: t.State + FR_NO_PATH: t.State + FR_INVALID_NAME: t.State + FR_DENIED: t.State + FR_EXIST: t.State + FR_INVALID_OBJECT: t.State + FR_WRITE_PROTECTED: t.State + FR_INVALID_DRIVE: t.State + FR_NOT_ENABLED: t.State + FR_NO_FILESYSTEM: t.State + FR_MKFS_ABORTED: t.State + FR_TIMEOUT: t.State + FR_LOCKED: t.State + FR_NOT_ENOUGH_CORE: t.State + FR_TOO_MANY_OPEN_FILES: t.State + FR_INVALID_PARAMETER: t.State + +class DRESULT(t.CEnum): + RES_OK: t.State = 0 + RES_ERROR: t.State + RES_WRPRT: t.State + RES_NOTRDY: t.State + RES_PARERR: t.State + + +@c.Attribute(t.attr.packed) +class bpb: + BS_jmpBoot: t.CArray[t.CUInt8T, 3] + BS_OEMName: t.CArray[t.CChar, 8] + BPB_BytsPerSec: t.CUInt16T + BPB_SecPerClus: t.CUInt8T + BPB_RsvdSecCnt: t.CUInt16T + BPB_NumFATs: t.CUInt8T + BPB_RootEntCnt: t.CUInt16T + BPB_TotSec16: t.CUInt16T + BPB_Media: t.CUInt8T + BPB_FATSz16: t.CUInt16T + BPB_SecPerTrk: t.CUInt16T + BPB_NumHeads: t.CUInt16T + BPB_HiddSec: t.CUInt32T + BPB_TotSec32: t.CUInt32T + BPB_FATSz32: t.CUInt32T + BPB_ExtFlags: t.CUInt16T + BPB_FSVer: t.CUInt16T + BPB_RootClus: t.CUInt32T + BPB_FSInfo: t.CUInt16T + BPB_BkBootSec: t.CUInt16T + BPB_Reserved: t.CArray[t.CUInt8T, 12] + BS_DrvNum: t.CUInt8T + BS_Reserved1: t.CUInt8T + BS_BootSig: t.CUInt8T + BS_VolID: t.CUInt32T + BS_VolLab: t.CArray[t.CChar, 11] + BS_FilSysType: t.CArray[t.CChar, 8] + +@c.Attribute(t.attr.packed) +class fsinfo: + FSI_LeadSig: t.CUInt32T + FSI_Reserved1: t.CArray[t.CUInt8T, 480] + FSI_StrucSig: t.CUInt32T + FSI_Free_Count: t.CUInt32T + FSI_Nxt_Free: t.CUInt32T + FSI_Reserved2: t.CArray[t.CUInt8T, 12] + FSI_TrailSig: t.CUInt32T + +@c.Attribute(t.attr.packed) +class dirent: + DIR_Name: t.CArray[t.CChar, 11] + DIR_Attr: t.CUInt8T + DIR_NTRes: t.CUInt8T + DIR_CrtTimeTenth: t.CUInt8T + DIR_CrtTime: t.CUInt16T + DIR_CrtDate: t.CUInt16T + DIR_LstAccDate: t.CUInt16T + DIR_FstClusHI: t.CUInt16T + DIR_WrtTime: t.CUInt16T + DIR_WrtDate: t.CUInt16T + DIR_FstClusLO: t.CUInt16T + DIR_FileSize: t.CUInt32T + +@c.Attribute(t.attr.packed) +class lfn_entry: + LDIR_Ord: t.CUInt8T + LDIR_Name1: t.CArray[t.CUInt16T, 5] + LDIR_Attr: t.CUInt8T + LDIR_Type: t.CUInt8T + LDIR_Chksum: t.CUInt8T + LDIR_Name2: t.CArray[t.CUInt16T, 6] + LDIR_FstClusLO: t.CUInt16T + LDIR_Name3: t.CArray[t.CUInt16T, 2] + + +def sfn_checksum(name: t.CConst | t.CChar | t.CPtr) -> t.CUInt8T: + sum: t.CUInt8T = 0 + i: t.CInt + for i in range(11): + sum = ((sum & 1) << 7) + (sum >> 1) + name[i] + return sum + +def uni2oem_placeholder(uni: t.CUInt16T, cp: t.CUInt16T) -> t.CUInt16T: + if uni < 0x80: return uni + return t.CUInt16T(0x003F) + +def lfn_get_char(entry_bytes: t.CUInt8T | t.CPtr, idx: t.CInt) -> t.CUInt16T: + if idx < 5: + off: t.CInt = 1 + idx * 2 + elif idx < 11: + off = 14 + (idx - 5) * 2 + else: + off = 28 + (idx - 11) * 2 + lo: t.CUInt8T = c.Deref(t.CUInt8T(t.CUInt64T(entry_bytes) + off, t.CPtr)) + hi: t.CUInt8T = c.Deref(t.CUInt8T(t.CUInt64T(entry_bytes) + off + 1, t.CPtr)) + return t.CUInt16T(lo) | t.CUInt16T(t.CUInt16T(hi) << 8) + +def lfn_set_char(entry_bytes: t.CUInt8T | t.CPtr, idx: t.CInt, ch: t.CUInt16T): + if idx < 5: + off: t.CInt = 1 + idx * 2 + elif idx < 11: + off = 14 + (idx - 5) * 2 + else: + off = 28 + (idx - 11) * 2 + c.Set(t.CUInt8T(t.CUInt64T(entry_bytes) + off, t.CPtr), t.CUInt8T(ch & 0xFF)) + c.Set(t.CUInt8T(t.CUInt64T(entry_bytes) + off + 1, t.CPtr), t.CUInt8T(ch >> 8)) + +def lfn_extract(entry_bytes: t.CUInt8T | t.CPtr, lfn: t.CUInt16T | t.CPtr, ord_val: t.CUInt8T) -> t.CInt: + seq: t.CInt = (ord_val & 0x3F) - 1 + base: t.CInt = seq * LFN_ENTRY_CHARS + i: t.CInt + for i in range(LFN_ENTRY_CHARS): + ch: t.CUInt16T = lfn_get_char(entry_bytes, i) + if ch == 0x0000 or ch == 0xFFFF: + c.Set(t.CUInt16T(t.CUInt64T(lfn) + (base + i) * 2, t.CPtr), 0) + return base + i + c.Set(t.CUInt16T(t.CUInt64T(lfn) + (base + i) * 2, t.CPtr), ch) + if ord_val & LFN_FLAG_LAST: + c.Set(t.CUInt16T(t.CUInt64T(lfn) + (base + LFN_ENTRY_CHARS) * 2, t.CPtr), 0) + return base + LFN_ENTRY_CHARS + +def sfn_from_lfn(lfn: t.CConst | t.CChar | t.CPtr, sfn: t.CChar | t.CPtr): + string.memset(sfn, 0x20, 11) + if c.Deref(lfn) == '.' and c.Deref(lfn + 1) == 0: + sfn[0] = '.'; sfn[11] = 0; return + if c.Deref(lfn) == '.' and c.Deref(lfn + 1) == '.' and c.Deref(lfn + 2) == 0: + sfn[0] = '.'; sfn[1] = '.'; sfn[11] = 0; return + si: t.CInt = 0 + dot_pos: t.CInt = -1 + i: t.CInt = 0 + ext_i: t.CInt = 0 + for ch in lfn: + if ch == '.': + dot_pos = si; si += 1; ext_i = 0; continue + uc: t.CChar + if ch >= 'a' and ch <= 'z': uc = ch - 32 + elif ch == ' ': uc = '_' + elif ch == '+' or ch == ',' or ch == ';' or ch == '=' or ch == '[' or ch == ']': uc = '_' + else: uc = ch + if dot_pos < 0: + if i < 8: sfn[i] = uc; i += 1 + else: + if ext_i < 3: sfn[8 + ext_i] = uc; ext_i += 1 + si += 1 + sfn[11] = 0 + +def sfn_to_str(sfn: t.CConst | t.CChar | t.CPtr, out: t.CChar | t.CPtr): + i: t.CInt = 0 + j: t.CInt = 0 + for i in range(8): + if sfn[i] == 0x20: break + if sfn[i] == 0x05: out[j] = 0xE5 + else: out[j] = sfn[i] + j += 1 + if sfn[8] != 0x20: + out[j] = '.'; j += 1 + for i in range(8, 11): + if sfn[i] == 0x20: break + out[j] = sfn[i]; j += 1 + out[j] = 0 + +def _split_path(path: t.CConst | t.CChar | t.CPtr, parent_out: t.CChar | t.CPtr, name_out: t.CChar | t.CPtr): + pi: t.CInt = 0; last_sep: t.CInt = -1 + for ch in path: + parent_out[pi] = ch + if ch == '/' or ch == '\\': last_sep = pi + pi += 1 + parent_out[pi] = 0 + if last_sep >= 0: + fi: t.CInt + for fi in range(last_sep + 1, pi): name_out[fi - last_sep - 1] = path[fi] + name_out[pi - last_sep - 1] = 0 + parent_out[last_sep] = 0 + else: + fi2: t.CInt + for fi2 in range(pi): name_out[fi2] = path[fi2] + name_out[pi] = 0 + parent_out[0] = '/'; parent_out[1] = 0 + + +class fileinfo: + file_size: t.CUInt32T = 0 + attr: t.CUInt8T = 0 + ctime: t.CUInt16T = 0 + cdate: t.CUInt16T = 0 + mtime: t.CUInt16T = 0 + mdate: t.CUInt16T = 0 + adate: t.CUInt16T = 0 + fname: t.CArray[t.CChar, 256] + altname: t.CArray[t.CChar, 13] + +class filelock: + cluster: t.CUInt32T = 0 + dir_sector: t.CUInt64T = 0 + dir_offset: t.CUInt32T = 0 + in_use: t.CInt = 0 + shared_count: t.CInt = 0 + exclusive: t.CInt = 0 + + +@t.Object +class volinfo: + lock: spinlock._spinlock + mounted: t.CInt = 0 + pdrv: t.CUInt8T = 0 + sector_size: t.CUInt16T = 512 + cluster_size: t.CUInt32T = 0 + cluster_sectors: t.CUInt8T = 0 + fat_start: t.CUInt64T = 0 + fat_size: t.CUInt32T = 0 + data_start: t.CUInt64T = 0 + root_cluster: t.CUInt32T = 2 + total_sectors: t.CUInt64T = 0 + total_clusters: t.CUInt32T = 0 + free_clusters: t.CUInt32T = 4294967295 + last_alloc: t.CUInt32T = 2 + fsinfo_sector: t.CUInt16T = 0 + backup_sector: t.CUInt16T = 6 + vol_id: t.CUInt32T = 0 + vol_label: t.CArray[t.CChar, 12] + num_fats: t.CUInt8T = 2 + reserved_sectors: t.CUInt16T = 0 + hidden_sectors: t.CUInt32T = 0 + winsect: t.CUInt64T = 0xFFFFFFFFFFFFFFFF + win_dirty: t.CInt = 0 + win: t.CArray[t.CUInt8T, 512] + fat_winsect: t.CUInt64T = 0xFFFFFFFFFFFFFFFF + fat_win_dirty: t.CInt = 0 + fat_win: t.CArray[t.CUInt8T, 512] + metadata: t.CUInt32T = 0 + last_error: t.CUInt32T = 0 + + def win_read(self, sector: t.CUInt64T) -> FRESULT: + if self.winsect == sector: return FRESULT.FR_OK + if self.win_dirty: + res: FRESULT = self.win_flush() + if res != FRESULT.FR_OK: return res + abs_sector: t.CUInt64T = sector + t.CUInt64T(self.hidden_sectors) + self.lock.lock() + win_addr: t.CPtr = c.Addr(self.win[0]) + res2: DRESULT = diskio.read(self.pdrv, win_addr, t.CUInt32T(abs_sector), 1) + self.lock.unlock() + if res2 != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + self.winsect = sector + return FRESULT.FR_OK + + def win_flush(self) -> FRESULT: + if not self.win_dirty: return FRESULT.FR_OK + abs_sector: t.CUInt64T = self.winsect + t.CUInt64T(self.hidden_sectors) + self.lock.lock() + res: DRESULT = diskio.write(self.pdrv, c.Addr(self.win[0]), t.CUInt32T(abs_sector), 1) + self.lock.unlock() + if res != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + self.win_dirty = 0 + return FRESULT.FR_OK + + def fat_win_read(self, sector: t.CUInt64T) -> FRESULT: + if self.fat_winsect == sector: return FRESULT.FR_OK + if self.fat_win_dirty: + res: FRESULT = self.fat_win_flush() + if res != FRESULT.FR_OK: return res + abs_sector: t.CUInt64T = sector + t.CUInt64T(self.hidden_sectors) + self.lock.lock() + res2: DRESULT = diskio.read(self.pdrv, c.Addr(self.fat_win[0]), t.CUInt32T(abs_sector), 1) + self.lock.unlock() + if res2 != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + self.fat_winsect = sector + return FRESULT.FR_OK + + def fat_win_flush(self) -> FRESULT: + if not self.fat_win_dirty: return FRESULT.FR_OK + abs_sector: t.CUInt64T = self.fat_winsect + t.CUInt64T(self.hidden_sectors) + self.lock.lock() + res: DRESULT = diskio.write(self.pdrv, c.Addr(self.fat_win[0]), t.CUInt32T(abs_sector), 1) + self.lock.unlock() + if res != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + self.fat_win_dirty = 0 + return FRESULT.FR_OK + + def flush_all(self) -> FRESULT: + res: FRESULT = self.win_flush() + if res != FRESULT.FR_OK: return res + return self.fat_win_flush() + + def fat_read(self, cluster: t.CUInt32T) -> t.CUInt32T: + if cluster < 2 or cluster >= self.total_clusters + 2: return 0 + fat_offset: t.CUInt64T = t.CUInt64T(cluster) * 4 + fat_sector: t.CUInt64T = self.fat_start + (fat_offset / 512) + fat_offset_in_sector: t.CUInt32T = t.CUInt32T(fat_offset % 512) + res: FRESULT = self.fat_win_read(fat_sector) + if res != FRESULT.FR_OK: return 0 + return _read32(c.Addr(self.fat_win[0]), fat_offset_in_sector) & FAT_MASK + + def fat_write(self, cluster: t.CUInt32T, value: t.CUInt32T) -> FRESULT: + if cluster < 2 or cluster >= self.total_clusters + 2: return FRESULT.FR_INVALID_PARAMETER + fat_offset: t.CUInt64T = t.CUInt64T(cluster) * 4 + fat_sector: t.CUInt64T = self.fat_start + (fat_offset / 512) + fat_offset_in_sector: t.CUInt32T = t.CUInt32T(fat_offset % 512) + res: FRESULT = self.fat_win_read(fat_sector) + if res != FRESULT.FR_OK: return res + old: t.CUInt32T = _read32(c.Addr(self.fat_win[0]), fat_offset_in_sector) + new_val: t.CUInt32T = (old & FAT_RESERVED) | (value & FAT_MASK) + _write32(c.Addr(self.fat_win[0]), fat_offset_in_sector, new_val) + self.fat_win_dirty = 1 + res = self.fat_win_flush() + if res != FRESULT.FR_OK: return res + if self.num_fats >= 2: + mirror_sector: t.CUInt64T = fat_sector + t.CUInt64T(self.fat_size) + res2: FRESULT = self.fat_win_read(mirror_sector) + if res2 != FRESULT.FR_OK: return res2 + mirror_old: t.CUInt32T = _read32(c.Addr(self.fat_win[0]), fat_offset_in_sector) + mirror_new: t.CUInt32T = (mirror_old & FAT_RESERVED) | (value & FAT_MASK) + _write32(c.Addr(self.fat_win[0]), fat_offset_in_sector, mirror_new) + self.fat_win_dirty = 1 + res3: FRESULT = self.fat_win_flush() + if res3 != FRESULT.FR_OK: return res3 + return FRESULT.FR_OK + + def cluster_alloc(self) -> t.CUInt32T: + clust: t.CUInt32T = self.last_alloc + i: t.CUInt32T + for i in range(self.total_clusters): + clust += 1 + if clust >= self.total_clusters + 2: clust = 2 + entry: t.CUInt32T = self.fat_read(clust) + if entry == CLUSTER_FREE: + res: FRESULT = self.fat_write(clust, CLUSTER_EOC_MAX) + if res != FRESULT.FR_OK: return 0 + self.last_alloc = clust + if self.free_clusters != 0xFFFFFFFF: self.free_clusters -= 1 + return clust + return 0 + + def cluster_free_chain(self, start_cluster: t.CUInt32T) -> FRESULT: + if start_cluster < 2: return FRESULT.FR_OK + clust: t.CUInt32T = start_cluster + while clust >= 2 and clust < CLUSTER_EOC_MIN: + next_clust: t.CUInt32T = self.fat_read(clust) + if next_clust == 0: break + res: FRESULT = self.fat_write(clust, CLUSTER_FREE) + if res != FRESULT.FR_OK: return res + if self.free_clusters != 0xFFFFFFFF: self.free_clusters += 1 + clust = next_clust + return FRESULT.FR_OK + + def cluster_extend(self, cluster: t.CUInt32T) -> t.CUInt32T: + new_clust: t.CUInt32T = self.cluster_alloc() + if new_clust == 0: return 0 + if cluster >= 2: + res: FRESULT = self.fat_write(cluster, new_clust) + if res != FRESULT.FR_OK: + self.fat_write(new_clust, CLUSTER_FREE) + return 0 + return new_clust + + def cluster_get_next(self, cluster: t.CUInt32T) -> t.CUInt32T: + if cluster < 2: return 0 + entry: t.CUInt32T = self.fat_read(cluster) + if entry >= CLUSTER_EOC_MIN and entry <= CLUSTER_EOC_MAX: return 0 + if entry == CLUSTER_FREE or entry == CLUSTER_BAD: return 0 + return entry + + def cluster_to_sector(self, cluster: t.CUInt32T) -> t.CUInt64T: + if cluster < 2: return 0 + return self.data_start + t.CUInt64T(cluster - 2) * t.CUInt64T(self.cluster_sectors) + + def entries_per_cluster(self) -> t.CUInt32T: + return t.CUInt32T(self.cluster_sectors) * (512 / ENTRY_SIZE) + + def is_eoc(self, cluster: t.CUInt32T) -> t.CInt: + return 1 if (cluster >= CLUSTER_EOC_MIN and cluster <= CLUSTER_EOC_MAX) else 0 + + def fsinfo_read(self) -> FRESULT: + if self.fsinfo_sector == 0: return FRESULT.FR_OK + res: FRESULT = self.win_read(t.CUInt64T(self.fsinfo_sector)) + if res != FRESULT.FR_OK: return res + lead_sig: t.CUInt32T = _read32(c.Addr(self.win[0]), 0) + struc_sig: t.CUInt32T = _read32(c.Addr(self.win[0]), 484) + trail_sig: t.CUInt32T = _read32(c.Addr(self.win[0]), 508) + if lead_sig != 0x41615252 or struc_sig != 0x61417272 or trail_sig != 0xAA550000: + self.free_clusters = 0xFFFFFFFF + self.last_alloc = 2 + return FRESULT.FR_OK + free_count: t.CUInt32T = _read32(c.Addr(self.win[0]), 488) + nxt_free: t.CUInt32T = _read32(c.Addr(self.win[0]), 492) + if free_count <= self.total_clusters: self.free_clusters = free_count + if nxt_free >= 2 and nxt_free < self.total_clusters + 2: self.last_alloc = nxt_free + return FRESULT.FR_OK + + def fsinfo_write(self) -> FRESULT: + if self.fsinfo_sector == 0: return FRESULT.FR_OK + res: FRESULT = self.win_read(t.CUInt64T(self.fsinfo_sector)) + if res != FRESULT.FR_OK: return res + _write32(c.Addr(self.win[0]), 0, 0x41615252) + _write32(c.Addr(self.win[0]), 484, 0x61417272) + _write32(c.Addr(self.win[0]), 488, self.free_clusters) + _write32(c.Addr(self.win[0]), 492, self.last_alloc) + _write32(c.Addr(self.win[0]), 508, 0xAA550000) + self.win_dirty = 1 + return self.win_flush() + + def count_free_clusters(self) -> t.CUInt32T: + free_cnt: t.CUInt32T = 0 + clust: t.CUInt32T + for clust in range(2, self.total_clusters + 2): + entry: t.CUInt32T = self.fat_read(clust) + if entry == CLUSTER_FREE: free_cnt += 1 + self.free_clusters = free_cnt + return free_cnt + + def mount(self) -> FRESULT: + if self.mounted: return FRESULT.FR_OK + res: DRESULT = diskio.status(self.pdrv) + if res != DRESULT.RES_OK: + res = diskio.init(self.pdrv) + if res != DRESULT.RES_OK: return FRESULT.FR_NOT_READY + res2: FRESULT = self.win_read(0) + if res2 != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + bps: t.CUInt16T = _read16(c.Addr(self.win[0]), 11) + spc: t.CUInt8T = self.win[13] + rsc: t.CUInt16T = _read16(c.Addr(self.win[0]), 14) + nf: t.CUInt8T = self.win[16] + root_ent: t.CUInt16T = _read16(c.Addr(self.win[0]), 17) + fat16sz: t.CUInt16T = _read16(c.Addr(self.win[0]), 22) + tot32: t.CUInt32T = _read32(c.Addr(self.win[0]), 32) + fat32sz: t.CUInt32T = _read32(c.Addr(self.win[0]), 36) + root_clus: t.CUInt32T = _read32(c.Addr(self.win[0]), 44) + sig0: t.CUInt8T = self.win[510] + sig1: t.CUInt8T = self.win[511] + if bps != 512: return FRESULT.FR_NO_FILESYSTEM + if spc == 0 or (spc & (spc - 1)) != 0: return FRESULT.FR_NO_FILESYSTEM + if rsc == 0: return FRESULT.FR_NO_FILESYSTEM + if nf == 0: return FRESULT.FR_NO_FILESYSTEM + if root_ent != 0 or fat16sz != 0: return FRESULT.FR_NO_FILESYSTEM + if fat32sz == 0: return FRESULT.FR_NO_FILESYSTEM + if tot32 == 0: return FRESULT.FR_NO_FILESYSTEM + if sig0 != 0x55 or sig1 != 0xAA: return FRESULT.FR_NO_FILESYSTEM + fsinfo_sec: t.CUInt16T = _read16(c.Addr(self.win[0]), 48) + bkboot_sec: t.CUInt16T = _read16(c.Addr(self.win[0]), 50) + self.sector_size = 512 + self.cluster_sectors = spc + self.cluster_size = t.CUInt32T(spc) * 512 + self.num_fats = nf + self.reserved_sectors = rsc + self.fat_size = fat32sz + self.fat_start = t.CUInt64T(rsc) + self.total_sectors = t.CUInt64T(tot32) + self.root_cluster = root_clus + self.fsinfo_sector = fsinfo_sec + self.backup_sector = bkboot_sec + self.vol_id = _read32(c.Addr(self.win[0]), 67) + data_sectors: t.CUInt64T = self.total_sectors - t.CUInt64T(rsc) - t.CUInt64T(t.CUInt64T(nf) * t.CUInt64T(fat32sz)) + self.total_clusters = t.CUInt32T(data_sectors / t.CUInt64T(spc)) + self.data_start = self.fat_start + t.CUInt64T(t.CUInt64T(nf) * t.CUInt64T(fat32sz)) + self.last_alloc = 2 + self.free_clusters = 0xFFFFFFFF + self.fsinfo_read() + self.mounted = 1 + return FRESULT.FR_OK + + def unmount(self) -> FRESULT: + if not self.mounted: return FRESULT.FR_OK + res: FRESULT = self.flush_all() + if res != FRESULT.FR_OK: return res + self.fsinfo_write() + self.mounted = 0 + self.winsect = 0xFFFFFFFFFFFFFFFF + self.win_dirty = 0 + self.fat_winsect = 0xFFFFFFFFFFFFFFFF + self.fat_win_dirty = 0 + return FRESULT.FR_OK + + def _zero_cluster(self, cluster: t.CUInt32T) -> FRESULT: + sector: t.CUInt64T = self.cluster_to_sector(cluster) + s: t.CUInt32T + for s in range(self.cluster_sectors): + res: FRESULT = self.win_read(sector + t.CUInt64T(s)) + if res != FRESULT.FR_OK: return res + string.memset(c.Addr(self.win[0]), 0, 512) + self.win_dirty = 1 + res2: FRESULT = self.win_flush() + if res2 != FRESULT.FR_OK: return res2 + return FRESULT.FR_OK + + def file_cluster_seek(self, first_cluster: t.CUInt32T, pos: t.CUInt32T) -> t.CUInt32T: + if first_cluster < 2: return 0 + cluster: t.CUInt32T = first_cluster + cluster_num: t.CUInt32T = pos / self.cluster_size + i: t.CUInt32T + for i in range(cluster_num): + next_c: t.CUInt32T = self.cluster_get_next(cluster) + if next_c == 0: return 0 + cluster = next_c + return cluster + + def file_find_last_cluster(self, first_cluster: t.CUInt32T) -> t.CUInt32T: + if first_cluster < 2: return 0 + cluster: t.CUInt32T = first_cluster + while True: + next_c: t.CUInt32T = self.cluster_get_next(cluster) + if next_c == 0: return cluster + cluster = next_c + + def _read_entry_from_dir(self, dp: dirobj | t.CPtr) -> FRESULT: + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + return self.win_read(entry_sector) + + def _write_dirent_now(self, eo: t.CUInt32T): + now: t.CUInt32T = ftime.get_fattime() + fat_time: t.CUInt16T = t.CUInt16T(now & 0xFFFF) + fat_date: t.CUInt16T = t.CUInt16T((now >> 16) & 0xFFFF) + _write16(c.Addr(self.win[0]), eo + 14, fat_time) + _write16(c.Addr(self.win[0]), eo + 16, fat_date) + _write16(c.Addr(self.win[0]), eo + 18, fat_date) + _write16(c.Addr(self.win[0]), eo + 22, fat_time) + _write16(c.Addr(self.win[0]), eo + 24, fat_date) + + def dir_offset_to_sector(self, start_cluster: t.CUInt32T, offset: t.CUInt32T) -> t.CUInt64T: + epc: t.CUInt32T = self.entries_per_cluster() + cluster: t.CUInt32T = start_cluster + cluster_idx: t.CUInt32T = offset / epc + i: t.CUInt32T + for i in range(cluster_idx): + next_c: t.CUInt32T = self.cluster_get_next(cluster) + if next_c == 0: return 0 + cluster = next_c + off_in_cluster: t.CUInt32T = offset % epc + return self.cluster_to_sector(cluster) + t.CUInt64T(off_in_cluster / (512 / ENTRY_SIZE)) + + def dir_next_cluster(self, dp: dirobj | t.CPtr) -> FRESULT: + next_clust: t.CUInt32T = self.cluster_get_next(dp.cluster) + if next_clust == 0: return FRESULT.FR_NO_FILE + dp.cluster = next_clust + dp.sector = self.cluster_to_sector(next_clust) + dp.offset = 0 + return FRESULT.FR_OK + + def dir_open(self, dp: dirobj | t.CPtr, cluster: t.CUInt32T) -> FRESULT: + dp.vol = self + dp.start_cluster = cluster + dp.cluster = cluster + dp.sector = self.cluster_to_sector(cluster) + dp.offset = 0 + dp.is_open = 1 + dp.lfn_ofs = 0xFFFFFFFF + dp.lfn_cluster = 0 + self.winsect = 0xFFFFFFFFFFFFFFFF + return FRESULT.FR_OK + + def dir_read_entry(self, dp: dirobj | t.CPtr, lfn_buf: t.CUInt16T | t.CPtr) -> FRESULT: + lfn_started: t.CInt = 0 + dp.lfn_ofs = 0xFFFFFFFF + dp.lfn_cluster = 0 + while True: + epc: t.CUInt32T = self.entries_per_cluster() + if dp.offset >= epc: + res: FRESULT = self.dir_next_cluster(dp) + if res != FRESULT.FR_OK: return res + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res2: FRESULT = self.win_read(entry_sector) + if res2 != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + first_byte: t.CUInt8T = self.win[entry_offset_in_sector] + if first_byte == SFN_FREE: + return FRESULT.FR_NO_FILE + attr: t.CUInt8T = self.win[entry_offset_in_sector + 11] + if first_byte == SFN_DELETED: + lfn_started = 0 + dp.lfn_ofs = 0xFFFFFFFF + dp.lfn_cluster = 0 + dp.offset += 1 + continue + if attr == LFN_ATTR: + if lfn_buf: + ord_val: t.CUInt8T = first_byte + if ord_val & LFN_FLAG_LAST: + lfn_started = 1 + dp.lfn_ofs = dp.offset + dp.lfn_cluster = dp.cluster + if lfn_started: + entry_bytes: t.CUInt8T | t.CPtr = t.CUInt8T(c.Addr(self.win[entry_offset_in_sector]), t.CPtr) + lfn_extract(entry_bytes, lfn_buf, ord_val) + dp.offset += 1 + continue + if attr == AM_VOL: + dp.offset += 1 + continue + if dp.lfn_ofs == 0xFFFFFFFF: + dp.lfn_ofs = dp.offset + dp.lfn_cluster = dp.cluster + return FRESULT.FR_OK + + def dir_find(self, dp: dirobj | t.CPtr, name: t.CConst | t.CChar | t.CPtr, lfn_buf: t.CUInt16T | t.CPtr) -> FRESULT: + sfn: t.CArray[t.CChar, 12] + sfn_from_lfn(name, c.Addr(sfn[0])) + _dbg_sfn: t.CArray[t.CChar, 16] + _si: t.CInt + for _si in range(11): + _dbg_sfn[_si] = sfn[_si] + _dbg_sfn[11] = 0 + serial.puts("[dir_find] searching for: ") + serial.puts(name) + serial.puts(" sfn=") + serial.puts(c.Addr(_dbg_sfn[0])) + serial.puts("\n") + _db0: t.CArray[t.CChar, 120] + viperlib.snprintf(c.Addr(_db0), 120, "[dir_find] dp: sect=0x%lx off=%lu clust=%lu winsect=0x%lx\n", dp.sector, dp.offset, dp.cluster, self.winsect) + serial.puts(_db0) + _find_count: t.CInt = 0 + while True: + res: FRESULT = self.dir_read_entry(dp, lfn_buf) + if res != FRESULT.FR_OK: + _db2: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(_db2), 80, "[dir_find] dir_read_entry failed res=%d offset=%lu\n", t.CInt(res), dp.offset) + serial.puts(_db2) + return res + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res2: FRESULT = self.win_read(entry_sector) + if res2 != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + match_found: t.CInt = 1 + i: t.CInt + for i in range(11): + a: t.CChar = self.win[entry_offset_in_sector + i] + b: t.CChar = sfn[i] + if a >= 'a' and a <= 'z': a = a - 32 + if b >= 'a' and b <= 'z': b = b - 32 + if a != b: + match_found = 0 + break + if match_found: return FRESULT.FR_OK + if dp.offset < 10: + _db3: t.CArray[t.CChar, 80] + _fb: t.CUInt8T = self.win[entry_offset_in_sector] + _at: t.CUInt8T = self.win[entry_offset_in_sector + 11] + viperlib.snprintf(c.Addr(_db3), 80, "[dir_find] off=%lu fb=0x%02x attr=0x%02x sfn=%.11s\n", dp.offset, _fb, _at, c.Addr(self.win[entry_offset_in_sector])) + serial.puts(_db3) + if lfn_buf and dp.lfn_ofs != 0xFFFFFFFF and dp.lfn_ofs < dp.offset: + lfn_matched: t.CInt = 1 + li: t.CInt = 0 + for name_ch in name: + lfn_ch: t.CUInt16T = c.Deref(t.CUInt16T(t.CUInt64T(lfn_buf) + li * 2, t.CPtr)) + lfn_low: t.CUInt16T = lfn_ch + if lfn_low >= 65 and lfn_low <= 90: lfn_low = lfn_low + 32 + n_low: t.CUInt16T = t.CUInt16T(name_ch) + if n_low >= 65 and n_low <= 90: n_low = n_low + 32 + if lfn_low != n_low: + lfn_matched = 0 + break + li += 1 + if lfn_matched: + lfn_end: t.CUInt16T = c.Deref(t.CUInt16T(t.CUInt64T(lfn_buf) + li * 2, t.CPtr)) + if lfn_end == 0 or lfn_end == 0xFFFF: + return FRESULT.FR_OK + dp.offset += 1 + + def dir_get_entry_info(self, dp: dirobj | t.CPtr, info: fileinfo | t.CPtr, lfn_buf: t.CUInt16T | t.CPtr) -> FRESULT: + res: FRESULT = self._read_entry_from_dir(dp) + if res != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + off: t.CUInt32T = entry_offset_in_sector + raw_attr: t.CUInt8T = self.win[off + 11] + info.attr = raw_attr + info.ctime = _read16(c.Addr(self.win[0]), off + 14) + info.cdate = _read16(c.Addr(self.win[0]), off + 16) + info.adate = _read16(c.Addr(self.win[0]), off + 18) + info.mtime = _read16(c.Addr(self.win[0]), off + 22) + info.mdate = _read16(c.Addr(self.win[0]), off + 24) + info.file_size = _read32(c.Addr(self.win[0]), off + 28) + sfn_ptr: t.CChar | t.CPtr = t.CChar(c.Addr(self.win[0]) + off, t.CPtr) + sfn_to_str(sfn_ptr, c.Addr(info.altname[0])) + if dp.lfn_ofs != 0xFFFFFFFF and dp.lfn_ofs < dp.offset and lfn_buf: + lfn_ptr: t.CUInt16T | t.CPtr = lfn_buf + oi: t.CInt = 0 + for i in range(MAX_LFN): + ch: t.CUInt16T = c.Deref(lfn_ptr) + if ch == 0: break + if ch < 0x80: + info.fname[oi] = t.CChar(ch) + else: + oem_ch: t.CUInt16T = uni2oem_placeholder(ch, 0) + if oem_ch < 0x80: info.fname[oi] = t.CChar(oem_ch) + else: info.fname[oi] = '?' + oi += 1 + lfn_ptr = t.CUInt16T(t.CUInt64T(lfn_ptr) + 2, t.CPtr) + info.fname[oi] = 0 + else: + sfn_to_str(sfn_ptr, c.Addr(info.fname[0])) + return FRESULT.FR_OK + + def dir_add_entry(self, dp: dirobj | t.CPtr, name: t.CConst | t.CChar | t.CPtr, attr: t.CUInt8T, first_cluster: t.CUInt32T, file_size: t.CUInt32T, dir_sector_out: t.CUInt64T | t.CPtr, dir_offset_out: t.CUInt32T | t.CPtr) -> FRESULT: + sfn: t.CArray[t.CChar, 12] + sfn_from_lfn(name, c.Addr(sfn[0])) + lfn_entries: t.CInt = 0 + len_: t.CInt = 0 + for _ in name: len_ += 1 + if len_ > 12: + lfn_entries = (len_ + LFN_ENTRY_CHARS - 1) / LFN_ENTRY_CHARS + total_entries: t.CInt = lfn_entries + 1 + cur_cluster: t.CUInt32T = dp.cluster + cur_sector: t.CUInt64T = dp.sector + cur_offset: t.CUInt32T = dp.offset + found: t.CInt = 0 + consecutive: t.CInt = 0 + start_offset: t.CUInt32T = 0 + start_cluster: t.CUInt32T = 0 + epc: t.CUInt32T = self.entries_per_cluster() + while not found: + if cur_offset >= epc: + next_clust: t.CUInt32T = self.cluster_get_next(cur_cluster) + if next_clust == 0: + new_clust: t.CUInt32T = self.cluster_extend(cur_cluster) + if new_clust == 0: return FRESULT.FR_DENIED + self._zero_cluster(new_clust) + cur_cluster = new_clust + cur_sector = self.cluster_to_sector(new_clust) + cur_offset = 0 + else: + cur_cluster = next_clust + cur_sector = self.cluster_to_sector(next_clust) + cur_offset = 0 + es: t.CUInt64T = cur_sector + (cur_offset / (512 / ENTRY_SIZE)) + eo: t.CUInt32T = (cur_offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res: FRESULT = self.win_read(es) + if res != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + first_byte: t.CUInt8T = self.win[eo] + if first_byte == SFN_FREE or first_byte == SFN_DELETED: + if consecutive == 0: + start_offset = cur_offset + start_cluster = cur_cluster + consecutive += 1 + if consecutive >= total_entries: + found = 1 + break + else: + consecutive = 0 + cur_offset += 1 + write_cluster: t.CUInt32T = start_cluster + write_sector: t.CUInt64T = self.cluster_to_sector(start_cluster) + write_offset: t.CUInt32T = start_offset + if lfn_entries > 0: + le: t.CInt + for le in range(lfn_entries): + if write_offset >= epc: + next_c: t.CUInt32T = self.cluster_get_next(write_cluster) + if next_c == 0: return FRESULT.FR_INT_ERR + write_cluster = next_c + write_sector = self.cluster_to_sector(next_c) + write_offset = 0 + es2: t.CUInt64T = write_sector + (write_offset / (512 / ENTRY_SIZE)) + eo2: t.CUInt32T = (write_offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res2: FRESULT = self.win_read(es2) + if res2 != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + ord_val: t.CUInt8T = t.CUInt8T(lfn_entries - le) + if le == 0: ord_val |= LFN_FLAG_LAST + self.win[eo2 + 0] = ord_val + self.win[eo2 + 11] = LFN_ATTR + self.win[eo2 + 12] = 0 + self.win[eo2 + 13] = sfn_checksum(c.Addr(sfn[0])) + self.win[eo2 + 26] = 0 + self.win[eo2 + 27] = 0 + base: t.CInt = (lfn_entries - 1 - le) * LFN_ENTRY_CHARS + j: t.CInt + for j in range(LFN_ENTRY_CHARS): + idx: t.CInt = base + j + ch: t.CUInt16T + if idx < len_: ch = t.CUInt16T(name[idx]) + elif idx == len_: ch = 0x0000 + else: ch = 0xFFFF + if j < 5: + self.win[eo2 + 1 + j * 2] = t.CUInt8T(ch & 0xFF) + self.win[eo2 + 2 + j * 2] = t.CUInt8T((ch >> 8) & 0xFF) + elif j < 11: + self.win[eo2 + 14 + (j - 5) * 2] = t.CUInt8T(ch & 0xFF) + self.win[eo2 + 15 + (j - 5) * 2] = t.CUInt8T((ch >> 8) & 0xFF) + else: + self.win[eo2 + 28 + (j - 11) * 2] = t.CUInt8T(ch & 0xFF) + self.win[eo2 + 29 + (j - 11) * 2] = t.CUInt8T((ch >> 8) & 0xFF) + self.win_dirty = 1 + self.win_flush() + write_offset += 1 + if write_offset >= epc: + next_c2: t.CUInt32T = self.cluster_get_next(write_cluster) + if next_c2 == 0: return FRESULT.FR_INT_ERR + write_cluster = next_c2 + write_sector = self.cluster_to_sector(next_c2) + write_offset = 0 + es3: t.CUInt64T = write_sector + (write_offset / (512 / ENTRY_SIZE)) + eo3: t.CUInt32T = (write_offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res3: FRESULT = self.win_read(es3) + if res3 != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + string.memset(c.Addr(self.win[eo3]), 0, 32) + i: t.CInt + for i in range(11): self.win[eo3 + i] = sfn[i] + self.win[eo3 + 11] = attr + self.win[eo3 + 12] = 0 + self.win[eo3 + 13] = 0 + self._write_dirent_now(eo3) + _write16(c.Addr(self.win[0]), eo3 + 20, t.CUInt16T((first_cluster >> 16) & 0xFFFF)) + _write16(c.Addr(self.win[0]), eo3 + 26, t.CUInt16T(first_cluster & 0xFFFF)) + _write32(c.Addr(self.win[0]), eo3 + 28, file_size) + self.win_dirty = 1 + self.win_flush() + c.Set(t.CUInt64T(dir_sector_out, t.CPtr), es3) + c.Set(t.CUInt32T(dir_offset_out, t.CPtr), eo3) + return FRESULT.FR_OK + + def dir_update_entry(self, dir_sector: t.CUInt64T, dir_offset: t.CUInt32T, first_cluster: t.CUInt32T, file_size: t.CUInt32T, attr: t.CUInt8T) -> FRESULT: + res: FRESULT = self.win_read(dir_sector) + if res != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + self.win[dir_offset + 11] = attr + _write16(c.Addr(self.win[0]), dir_offset + 20, t.CUInt16T((first_cluster >> 16) & 0xFFFF)) + _write16(c.Addr(self.win[0]), dir_offset + 26, t.CUInt16T(first_cluster & 0xFFFF)) + _write32(c.Addr(self.win[0]), dir_offset + 28, file_size) + self._write_dirent_now(dir_offset) + self.win_dirty = 1 + return self.win_flush() + + def dir_delete_entry(self, dp: dirobj | t.CPtr) -> FRESULT: + start_off: t.CUInt32T = dp.lfn_ofs if dp.lfn_ofs != 0xFFFFFFFF else dp.offset + end_off: t.CUInt32T = dp.offset + cur_off: t.CUInt32T = start_off + cur_cluster: t.CUInt32T = dp.lfn_cluster if dp.lfn_ofs != 0xFFFFFFFF else dp.cluster + epc: t.CUInt32T = self.entries_per_cluster() + while cur_off <= end_off: + off_in_clus: t.CUInt32T = cur_off % epc + if off_in_clus == 0 and cur_off != start_off: + next_c: t.CUInt32T = self.cluster_get_next(cur_cluster) + if next_c == 0: return FRESULT.FR_INT_ERR + cur_cluster = next_c + sector_base: t.CUInt64T = self.cluster_to_sector(cur_cluster) + es: t.CUInt64T = sector_base + t.CUInt64T(off_in_clus / (512 / ENTRY_SIZE)) + eo: t.CUInt32T = (off_in_clus % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res: FRESULT = self.win_read(es) + if res != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + self.win[eo] = SFN_DELETED + self.win_dirty = 1 + res2: FRESULT = self.win_flush() + if res2 != FRESULT.FR_OK: return res2 + cur_off += 1 + return FRESULT.FR_OK + + def follow_path(self, dp: dirobj | t.CPtr, path: t.CConst | t.CChar | t.CPtr, lfn_buf: t.CUInt16T | t.CPtr) -> FRESULT: + cluster: t.CUInt32T = self.root_cluster + pi: t.CInt = 0 + if path[0] >= '0' and path[0] <= '9': + if path[1] == ':': + pi = 2 + elif path[1] == 0: + self.dir_open(dp, cluster) + return FRESULT.FR_OK + elif (path[0] >= 'A' and path[0] <= 'Z') or (path[0] >= 'a' and path[0] <= 'z'): + if path[1] == ':': + pi = 2 + if path[pi] == '/' or path[pi] == '\\': pi += 1 + while path[pi] == '/' or path[pi] == '\\': pi += 1 + if path[pi] == 0: + self.dir_open(dp, cluster) + return FRESULT.FR_OK + while True: + component: t.CArray[t.CChar, 256] + ci: t.CInt = 0 + while path[pi] != 0 and path[pi] != '/' and path[pi] != '\\' and ci < 255: + component[ci] = path[pi] + ci += 1; pi += 1 + component[ci] = 0 + if ci == 0: break + while path[pi] == '/' or path[pi] == '\\': pi += 1 + if ci == 2 and component[0] == '.' and component[1] == '.': + if cluster == self.root_cluster: + pass + else: + self.dir_open(dp, cluster) + dotdot_name: t.CArray[t.CChar, 256] + dotdot_name[0] = '.'; dotdot_name[1] = '.'; dotdot_name[2] = 0 + res_dd: FRESULT = self.dir_find(dp, c.Addr(dotdot_name[0]), lfn_buf) + if res_dd == FRESULT.FR_OK: + self._read_entry_from_dir(dp) + dd_eo: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + dd_off: t.CUInt32T = dd_eo + dd_ch: t.CUInt32T = t.CUInt32T(self.win[dd_off + 20]) | (t.CUInt32T(self.win[dd_off + 21]) << 8) + dd_cl: t.CUInt32T = t.CUInt32T(self.win[dd_off + 26]) | (t.CUInt32T(self.win[dd_off + 27]) << 8) + dd_clus: t.CUInt32T = (dd_ch << 16) | dd_cl + if dd_clus == 0: dd_clus = self.root_cluster + cluster = dd_clus + if path[pi] == 0: + self.dir_open(dp, cluster) + return FRESULT.FR_OK + continue + else: + cluster = self.root_cluster + if path[pi] == 0: + self.dir_open(dp, cluster) + return FRESULT.FR_OK + continue + if path[pi] == 0: + self.dir_open(dp, cluster) + return FRESULT.FR_OK + continue + if ci == 1 and component[0] == '.': + if path[pi] == 0: + self.dir_open(dp, cluster) + return FRESULT.FR_OK + continue + self.dir_open(dp, cluster) + res: FRESULT = self.dir_find(dp, c.Addr(component[0]), lfn_buf) + if res != FRESULT.FR_OK: return res + self._read_entry_from_dir(dp) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + off: t.CUInt32T = entry_offset_in_sector + entry_attr: t.CUInt8T = self.win[off + 11] + clus_hi: t.CUInt32T = t.CUInt32T(self.win[off + 20]) | (t.CUInt32T(self.win[off + 21]) << 8) + clus_lo: t.CUInt32T = t.CUInt32T(self.win[off + 26]) | (t.CUInt32T(self.win[off + 27]) << 8) + entry_cluster: t.CUInt32T = (clus_hi << 16) | clus_lo + if path[pi] == 0: return FRESULT.FR_OK + if not (entry_attr & AM_DIR): return FRESULT.FR_NO_PATH + cluster = entry_cluster + return FRESULT.FR_NO_PATH + + def mkdir_create_dir_entries(self, new_cluster: t.CUInt32T, parent_cluster: t.CUInt32T) -> FRESULT: + sector: t.CUInt64T = self.cluster_to_sector(new_cluster) + res: FRESULT = self.win_read(sector) + if res != FRESULT.FR_OK: return res + string.memset(c.Addr(self.win[0]), 0, 512) + self.win[0] = '.'; self.win[1] = ' '; self.win[2] = ' '; self.win[3] = ' ' + self.win[4] = ' '; self.win[5] = ' '; self.win[6] = ' '; self.win[7] = ' ' + self.win[8] = ' '; self.win[9] = ' '; self.win[10] = ' ' + self.win[11] = AM_DIR + _write16(c.Addr(self.win[0]), 20, t.CUInt16T((new_cluster >> 16) & 0xFFFF)) + _write16(c.Addr(self.win[0]), 26, t.CUInt16T(new_cluster & 0xFFFF)) + self._write_dirent_now(0) + self.win[32] = '.'; self.win[33] = '.'; self.win[34] = ' '; self.win[35] = ' ' + self.win[36] = ' '; self.win[37] = ' '; self.win[38] = ' '; self.win[39] = ' ' + self.win[40] = ' '; self.win[41] = ' '; self.win[42] = ' ' + self.win[11 + 32] = AM_DIR + _write16(c.Addr(self.win[0]), 20 + 32, t.CUInt16T((parent_cluster >> 16) & 0xFFFF)) + _write16(c.Addr(self.win[0]), 26 + 32, t.CUInt16T(parent_cluster & 0xFFFF)) + self._write_dirent_now(32) + self.win_dirty = 1 + return self.win_flush() + + def file_open(self, path: t.CConst | t.CChar | t.CPtr, mode: t.CUInt8T) -> fileobj | t.CPtr: + self.last_error = FRESULT.FR_OK + if not self.mounted: + self.last_error = FRESULT.FR_NOT_ENABLED + return None + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if mode & FA_CREATE_NEW: + if res == FRESULT.FR_OK: + self.last_error = FRESULT.FR_EXIST + return None + if res != FRESULT.FR_NO_FILE and res != FRESULT.FR_NO_PATH: + self.last_error = res + return None + need_create: t.CInt = 0 + if (mode & FA_CREATE_NEW): need_create = 1 + if (mode & FA_OPEN_ALWAYS) and res == FRESULT.FR_NO_FILE: need_create = 1 + if (mode & FA_CREATE_ALWAYS) and res == FRESULT.FR_NO_FILE: need_create = 1 + if need_create: + pp: t.CArray[t.CChar, 260] + fn: t.CArray[t.CChar, 256] + _split_path(path, c.Addr(pp[0]), c.Addr(fn[0])) + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res2: FRESULT = self.follow_path(c.Addr(dp), c.Addr(pp[0]), t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res2 != FRESULT.FR_OK: + self.last_error = res2 + return None + nc: t.CUInt32T = 0 + if mode & FA_WRITE: + nc = self.cluster_alloc() + if nc == 0: + self.last_error = FRESULT.FR_DENIED + return None + self._zero_cluster(nc) + ds: t.CUInt64T = 0 + do: t.CUInt32T = 0 + res3: FRESULT = self.dir_add_entry(c.Addr(dp), c.Addr(fn[0]), AM_ARC, nc, 0, c.Addr(ds), c.Addr(do)) + if res3 != FRESULT.FR_OK: + if nc >= 2: self.cluster_free_chain(nc) + self.last_error = res3 + return None + fp: fileobj | t.CPtr = handle_alloc() + if fp is None: + if nc >= 2: self.cluster_free_chain(nc) + self.last_error = FRESULT.FR_TOO_MANY_OPEN_FILES + return None + fp.vol = self + fp.is_open = 1 + fp.is_dir = 0 + fp.first_cluster = nc + fp.current_cluster = nc + fp.file_size = 0 + fp.file_pos = 0 + fp.dir_sector = ds + fp.dir_offset = do + fp.attr = AM_ARC + fp.flags = mode + fp.err = 0 + fp.lock_count = 0 + fp.buf_sect = 0xFFFFFFFFFFFFFFFF + fp.buf_dirty = 0 + if mode & FA_WRITE: + lock_acquire(ds, do, 1) + fp.lock_count = 1 + return fp + if res != FRESULT.FR_OK: + self.last_error = res + return None + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res_wr: FRESULT = self.win_read(entry_sector) + if res_wr != FRESULT.FR_OK: + self.last_error = res_wr + return None + off: t.CUInt32T = entry_offset_in_sector + entry_attr: t.CUInt8T = self.win[off + 11] + if entry_attr & AM_DIR: + self.last_error = FRESULT.FR_DENIED + return None + if mode & FA_WRITE: + if entry_attr & AM_RDO: + self.last_error = FRESULT.FR_DENIED + return None + lres: FRESULT = lock_check(entry_sector, entry_offset_in_sector, mode) + if lres != FRESULT.FR_OK: + self.last_error = lres + return None + clus_hi: t.CUInt32T = t.CUInt32T(self.win[off + 20]) | (t.CUInt32T(self.win[off + 21]) << 8) + clus_lo: t.CUInt32T = t.CUInt32T(self.win[off + 26]) | (t.CUInt32T(self.win[off + 27]) << 8) + entry_cluster: t.CUInt32T = (clus_hi << 16) | clus_lo + entry_size: t.CUInt32T = _read32(c.Addr(self.win[0]), off + 28) + fp2: fileobj | t.CPtr = handle_alloc() + if fp2 is None: + self.last_error = FRESULT.FR_TOO_MANY_OPEN_FILES + return None + fp2.vol = self + fp2.is_open = 1 + fp2.is_dir = 0 + fp2.first_cluster = entry_cluster + fp2.current_cluster = entry_cluster + fp2.file_size = entry_size + fp2.file_pos = 0 + fp2.dir_sector = entry_sector + fp2.dir_offset = entry_offset_in_sector + fp2.attr = entry_attr + fp2.flags = mode + fp2.err = 0 + fp2.lock_count = 0 + fp2.buf_sect = 0xFFFFFFFFFFFFFFFF + fp2.buf_dirty = 0 + if mode & FA_WRITE: + lock_acquire(entry_sector, entry_offset_in_sector, 1) + fp2.lock_count = 1 + if mode & FA_CREATE_ALWAYS: + if entry_cluster >= 2: self.cluster_free_chain(entry_cluster) + fp2.first_cluster = 0 + fp2.current_cluster = 0 + fp2.file_size = 0 + fp2.file_pos = 0 + self.dir_update_entry(entry_sector, entry_offset_in_sector, 0, 0, entry_attr) + if mode & FA_OPEN_APPEND: + fp2.file_pos = entry_size + fp2.current_cluster = self.file_cluster_seek(entry_cluster, entry_size) + return fp2 + + def file_delete(self, path: t.CConst | t.CChar | t.CPtr) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res_wr: FRESULT = self.win_read(entry_sector) + if res_wr != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + off: t.CUInt32T = entry_offset_in_sector + entry_attr: t.CUInt8T = self.win[off + 11] + if entry_attr & AM_RDO: return FRESULT.FR_DENIED + if entry_attr & AM_DIR: return FRESULT.FR_DENIED + lres: FRESULT = lock_check(entry_sector, entry_offset_in_sector, FA_WRITE) + if lres != FRESULT.FR_OK: return FRESULT.FR_LOCKED + clus_hi: t.CUInt32T = t.CUInt32T(self.win[off + 20]) | (t.CUInt32T(self.win[off + 21]) << 8) + clus_lo: t.CUInt32T = t.CUInt32T(self.win[off + 26]) | (t.CUInt32T(self.win[off + 27]) << 8) + entry_cluster: t.CUInt32T = (clus_hi << 16) | clus_lo + res2: FRESULT = self.dir_delete_entry(c.Addr(dp)) + if res2 != FRESULT.FR_OK: return res2 + if entry_cluster >= 2: self.cluster_free_chain(entry_cluster) + return FRESULT.FR_OK + + def file_rename(self, old_path: t.CConst | t.CChar | t.CPtr, new_path: t.CConst | t.CChar | t.CPtr) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), old_path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res_wr: FRESULT = self.win_read(entry_sector) + if res_wr != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + off: t.CUInt32T = entry_offset_in_sector + entry_attr: t.CUInt8T = self.win[off + 11] + clus_hi: t.CUInt32T = t.CUInt32T(self.win[off + 20]) | (t.CUInt32T(self.win[off + 21]) << 8) + clus_lo: t.CUInt32T = t.CUInt32T(self.win[off + 26]) | (t.CUInt32T(self.win[off + 27]) << 8) + entry_cluster: t.CUInt32T = (clus_hi << 16) | clus_lo + entry_size: t.CUInt32T = _read32(c.Addr(self.win[0]), off + 28) + lres: FRESULT = lock_check(entry_sector, entry_offset_in_sector, FA_WRITE) + if lres != FRESULT.FR_OK: return FRESULT.FR_LOCKED + new_parent: t.CArray[t.CChar, 260] + new_fname: t.CArray[t.CChar, 256] + _split_path(new_path, c.Addr(new_parent[0]), c.Addr(new_fname[0])) + dp2: dirobj + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res2: FRESULT = self.follow_path(c.Addr(dp2), c.Addr(new_parent[0]), t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res2 != FRESULT.FR_OK: return FRESULT.FR_NO_PATH + new_dir_sector: t.CUInt64T = 0 + new_dir_offset: t.CUInt32T = 0 + res3: FRESULT = self.dir_add_entry(c.Addr(dp2), c.Addr(new_fname[0]), entry_attr, entry_cluster, entry_size, c.Addr(new_dir_sector), c.Addr(new_dir_offset)) + if res3 != FRESULT.FR_OK: return res3 + res4: FRESULT = self.dir_delete_entry(c.Addr(dp)) + if res4 != FRESULT.FR_OK: return res4 + return FRESULT.FR_OK + + def file_stat(self, path: t.CConst | t.CChar | t.CPtr, info: fileinfo | t.CPtr) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + return self.dir_get_entry_info(c.Addr(dp), info, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + + def dir_make(self, path: t.CConst | t.CChar | t.CPtr) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res == FRESULT.FR_OK: return FRESULT.FR_EXIST + parent_path: t.CArray[t.CChar, 260] + dir_name: t.CArray[t.CChar, 256] + _split_path(path, c.Addr(parent_path[0]), c.Addr(dir_name[0])) + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res2: FRESULT = self.follow_path(c.Addr(dp), c.Addr(parent_path[0]), t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res2 != FRESULT.FR_OK: return FRESULT.FR_NO_PATH + parent_cluster: t.CUInt32T = dp.cluster + new_cluster: t.CUInt32T = self.cluster_alloc() + if new_cluster == 0: return FRESULT.FR_DENIED + self._zero_cluster(new_cluster) + dir_sector: t.CUInt64T = 0 + dir_offset: t.CUInt32T = 0 + res3: FRESULT = self.dir_add_entry(c.Addr(dp), c.Addr(dir_name[0]), AM_DIR, new_cluster, 0, c.Addr(dir_sector), c.Addr(dir_offset)) + if res3 != FRESULT.FR_OK: + self.cluster_free_chain(new_cluster) + return res3 + res4: FRESULT = self.mkdir_create_dir_entries(new_cluster, parent_cluster) + if res4 != FRESULT.FR_OK: + self.cluster_free_chain(new_cluster) + return res4 + return FRESULT.FR_OK + + def dir_open_path(self, path: t.CConst | t.CChar | t.CPtr, dp: dirobj | t.CPtr) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + pi: t.CInt = 0 + if path[0] >= '0' and path[0] <= '9': + if path[1] == ':' or path[1] == 0: + pi = 2 + if path[1] == 0: + self.dir_open(dp, self.root_cluster) + return FRESULT.FR_OK + if path[pi] == '/' or path[pi] == '\\': pi += 1 + while path[pi] == '/' or path[pi] == '\\': pi += 1 + is_root: t.CInt = 1 if path[pi] == 0 else 0 + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(dp, path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + if is_root: return FRESULT.FR_OK + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res_wr: FRESULT = self.win_read(entry_sector) + if res_wr != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + off: t.CUInt32T = entry_offset_in_sector + entry_attr: t.CUInt8T = self.win[off + 11] + if not (entry_attr & AM_DIR): return FRESULT.FR_NO_PATH + clus_hi: t.CUInt32T = t.CUInt32T(self.win[off + 20]) | (t.CUInt32T(self.win[off + 21]) << 8) + clus_lo: t.CUInt32T = t.CUInt32T(self.win[off + 26]) | (t.CUInt32T(self.win[off + 27]) << 8) + entry_cluster: t.CUInt32T = (clus_hi << 16) | clus_lo + return self.dir_open(dp, entry_cluster) + + def dir_read(self, dp: dirobj | t.CPtr, info: fileinfo | t.CPtr) -> FRESULT: + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.dir_read_entry(dp, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + res2: FRESULT = self.dir_get_entry_info(dp, info, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + dp.offset += 1 + return res2 + + def dir_remove(self, path: t.CConst | t.CChar | t.CPtr) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res_wr: FRESULT = self.win_read(entry_sector) + if res_wr != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + off: t.CUInt32T = entry_offset_in_sector + entry_attr: t.CUInt8T = self.win[off + 11] + if not (entry_attr & AM_DIR): return FRESULT.FR_NO_PATH + clus_hi: t.CUInt32T = t.CUInt32T(self.win[off + 20]) | (t.CUInt32T(self.win[off + 21]) << 8) + clus_lo: t.CUInt32T = t.CUInt32T(self.win[off + 26]) | (t.CUInt32T(self.win[off + 27]) << 8) + entry_cluster: t.CUInt32T = (clus_hi << 16) | clus_lo + if entry_cluster >= 2: + check_dp: dirobj + self.dir_open(c.Addr(check_dp), entry_cluster) + lfn_buf2: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf2), 0, 256 * 2) + while True: + check_res: FRESULT = self.dir_read_entry(c.Addr(check_dp), t.CUInt16T(c.Addr(lfn_buf2[0]), t.CPtr)) + if check_res != FRESULT.FR_OK: break + c_off: t.CUInt32T = (check_dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + c_sec: t.CUInt64T = check_dp.sector + (check_dp.offset / (512 / ENTRY_SIZE)) + self.win_read(c_sec) + c_name0: t.CUInt8T = self.win[c_off] + c_name1: t.CUInt8T = self.win[c_off + 1] + check_dp.offset += 1 + if c_name0 == '.' and (c_name1 == '.' or c_name1 == 0x20): + continue + return FRESULT.FR_DENIED + lres: FRESULT = lock_check(entry_sector, entry_offset_in_sector, FA_WRITE) + if lres != FRESULT.FR_OK: return FRESULT.FR_LOCKED + res2: FRESULT = self.dir_delete_entry(c.Addr(dp)) + if res2 != FRESULT.FR_OK: return res2 + if entry_cluster >= 2: self.cluster_free_chain(entry_cluster) + return FRESULT.FR_OK + + def chmod(self, path: t.CConst | t.CChar | t.CPtr, attr: t.CUInt8T, mask: t.CUInt8T) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res_wr: FRESULT = self.win_read(entry_sector) + if res_wr != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + off: t.CUInt32T = entry_offset_in_sector + old_attr: t.CUInt8T = self.win[off + 11] + new_attr: t.CUInt8T = (old_attr & ~mask) | (attr & mask) + self.win[off + 11] = new_attr + self.win_dirty = 1 + return self.win_flush() + + def utime(self, path: t.CConst | t.CChar | t.CPtr, mtime: t.CUInt16T, mdate: t.CUInt16T) -> FRESULT: + if not self.mounted: return FRESULT.FR_NOT_ENABLED + dp: dirobj + lfn_buf: t.CArray[t.CUInt16T, 256] + string.memset(c.Addr(lfn_buf), 0, 256 * 2) + res: FRESULT = self.follow_path(c.Addr(dp), path, t.CUInt16T(c.Addr(lfn_buf[0]), t.CPtr)) + if res != FRESULT.FR_OK: return res + entry_sector: t.CUInt64T = dp.sector + (dp.offset / (512 / ENTRY_SIZE)) + entry_offset_in_sector: t.CUInt32T = (dp.offset % (512 / ENTRY_SIZE)) * ENTRY_SIZE + res_wr: FRESULT = self.win_read(entry_sector) + if res_wr != FRESULT.FR_OK: return FRESULT.FR_DISK_ERR + off: t.CUInt32T = entry_offset_in_sector + self.win[off + 22] = t.CUInt8T(mtime & 0xFF) + self.win[off + 23] = t.CUInt8T((mtime >> 8) & 0xFF) + self.win[off + 24] = t.CUInt8T(mdate & 0xFF) + self.win[off + 25] = t.CUInt8T((mdate >> 8) & 0xFF) + self.win[off + 18] = t.CUInt8T(mdate & 0xFF) + self.win[off + 19] = t.CUInt8T((mdate >> 8) & 0xFF) + self.win_dirty = 1 + return self.win_flush() + + +@t.Object +class fileobj: + vol: volinfo | t.CPtr + is_open: t.CInt = 0 + is_dir: t.CInt = 0 + first_cluster: t.CUInt32T = 0 + current_cluster: t.CUInt32T = 0 + file_size: t.CUInt32T = 0 + file_pos: t.CUInt32T = 0 + dir_sector: t.CUInt64T = 0 + dir_offset: t.CUInt32T = 0 + attr: t.CUInt8T = 0 + ctime: t.CUInt16T = 0 + cdate: t.CUInt16T = 0 + mtime: t.CUInt16T = 0 + mdate: t.CUInt16T = 0 + adate: t.CUInt16T = 0 + flags: t.CUInt8T = 0 + err: t.CUInt8T = 0 + lock_count: t.CInt = 0 + buf: t.CArray[t.CUInt8T, 512] + buf_sect: t.CUInt64T = 0 + buf_dirty: t.CInt = 0 + + def buf_flush(self) -> FRESULT: + if not self.buf_dirty: return FRESULT.FR_OK + abs_sect: t.CUInt64T = t.CUInt64T(self.buf_sect) + t.CUInt64T(self.vol.hidden_sectors) + self.vol.lock.lock() + res: DRESULT = diskio.write(self.vol.pdrv, c.Addr(self.buf[0]), t.CUInt32T(abs_sect), 1) + self.vol.lock.unlock() + if res != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + self.buf_dirty = 0 + return FRESULT.FR_OK + + def buf_read(self, sector: t.CUInt64T) -> FRESULT: + if self.buf_sect == sector: return FRESULT.FR_OK + if self.buf_dirty: + res: FRESULT = self.buf_flush() + if res != FRESULT.FR_OK: return res + abs_sect: t.CUInt64T = sector + t.CUInt64T(self.vol.hidden_sectors) + self.vol.lock.lock() + res2: DRESULT = diskio.read(self.vol.pdrv, c.Addr(self.buf[0]), t.CUInt32T(abs_sect), 1) + self.vol.lock.unlock() + if res2 != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + self.buf_sect = sector + return FRESULT.FR_OK + + def close(self) -> FRESULT: + if not self.is_open: return FRESULT.FR_INVALID_OBJECT + res: FRESULT = self.buf_flush() + if res != FRESULT.FR_OK: return res + if self.flags & FA_WRITE: + self.vol.dir_update_entry(self.dir_sector, self.dir_offset, self.first_cluster, self.file_size, self.attr) + if self.lock_count > 0: lock_release(self.dir_sector, self.dir_offset) + self.is_open = 0 + self.lock_count = 0 + return FRESULT.FR_OK + + def read(self, buff: t.CVoid | t.CPtr, btr: t.CUInt32T, br: t.CUInt32T | t.CPtr) -> FRESULT: + if not self.is_open: return FRESULT.FR_INVALID_OBJECT + if not (self.flags & FA_READ): return FRESULT.FR_DENIED + c.Set(t.CUInt32T(br, t.CPtr), 0) + vol: volinfo | t.CPtr = self.vol + remaining: t.CUInt32T = btr + buf_ptr: t.CUInt8T | t.CPtr = t.CUInt8T(buff, t.CPtr) + total_read: t.CUInt32T = 0 + while remaining > 0: + if self.file_pos >= self.file_size: break + if self.first_cluster < 2: break + if self.current_cluster < 2 or vol.is_eoc(self.current_cluster): break + cluster_offset: t.CUInt32T = self.file_pos % vol.cluster_size + sector_in_cluster: t.CUInt32T = cluster_offset / 512 + offset_in_sector: t.CUInt32T = cluster_offset % 512 + sector: t.CUInt64T = vol.cluster_to_sector(self.current_cluster) + t.CUInt64T(sector_in_cluster) + can_read_in_sector: t.CUInt32T = 512 - offset_in_sector + can_read: t.CUInt32T = can_read_in_sector + if can_read > remaining: can_read = remaining + if can_read > self.file_size - self.file_pos: can_read = self.file_size - self.file_pos + if can_read == 0: break + if offset_in_sector == 0 and can_read >= 512: + abs_sect: t.CUInt64T = sector + t.CUInt64T(vol.hidden_sectors) + vol.lock.lock() + res: DRESULT = diskio.read(vol.pdrv, buf_ptr, t.CUInt32T(abs_sect), 1) + vol.lock.unlock() + if res != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + else: + res2: FRESULT = self.buf_read(sector) + if res2 != FRESULT.FR_OK: return res2 + string.memcpy(buf_ptr, c.Addr(self.buf[offset_in_sector]), can_read) + buf_ptr += can_read + self.file_pos += can_read + total_read += can_read + remaining -= can_read + if self.file_pos % vol.cluster_size == 0: + next_c: t.CUInt32T = vol.cluster_get_next(self.current_cluster) + if next_c == 0: break + self.current_cluster = next_c + c.Set(t.CUInt32T(br, t.CPtr), total_read) + return FRESULT.FR_OK + + def _extend_to(self, target_pos: t.CUInt32T) -> FRESULT: + if self.first_cluster < 2: + new_c: t.CUInt32T = self.vol.cluster_alloc() + if new_c == 0: return FRESULT.FR_DENIED + self.first_cluster = new_c + self.current_cluster = new_c + self.vol._zero_cluster(new_c) + cur_pos: t.CUInt32T = 0 + clust: t.CUInt32T = self.first_cluster + while cur_pos + self.vol.cluster_size <= target_pos: + next_c: t.CUInt32T = self.vol.cluster_get_next(clust) + if next_c == 0: + new_c2: t.CUInt32T = self.vol.cluster_extend(clust) + if new_c2 == 0: return FRESULT.FR_DENIED + self.vol._zero_cluster(new_c2) + next_c = new_c2 + clust = next_c + cur_pos += self.vol.cluster_size + self.current_cluster = clust + return FRESULT.FR_OK + + def write(self, buff: t.CConst | t.CVoid | t.CPtr, btw: t.CUInt32T, bw: t.CUInt32T | t.CPtr) -> FRESULT: + if not self.is_open: return FRESULT.FR_INVALID_OBJECT + if not (self.flags & FA_WRITE): return FRESULT.FR_DENIED + c.Set(t.CUInt32T(bw, t.CPtr), 0) + vol: volinfo | t.CPtr = self.vol + remaining: t.CUInt32T = btw + buf_ptr: t.CConst | t.CUInt8T | t.CPtr = t.CUInt8T(buff, t.CPtr) + total_written: t.CUInt32T = 0 + while remaining > 0: + if self.first_cluster < 2: + new_c: t.CUInt32T = vol.cluster_alloc() + if new_c == 0: return FRESULT.FR_DENIED + self.first_cluster = new_c + self.current_cluster = new_c + vol._zero_cluster(new_c) + if self.current_cluster < 2 or vol.is_eoc(self.current_cluster): + last_c: t.CUInt32T = self.current_cluster + if self.first_cluster >= 2 and self.current_cluster < 2: + last_c = vol.file_find_last_cluster(self.first_cluster) + new_c2: t.CUInt32T = vol.cluster_extend(last_c) + if new_c2 == 0: return FRESULT.FR_DENIED + if self.first_cluster < 2: self.first_cluster = new_c2 + self.current_cluster = new_c2 + vol._zero_cluster(new_c2) + cluster_offset: t.CUInt32T = self.file_pos % vol.cluster_size + sector_in_cluster: t.CUInt32T = cluster_offset / 512 + offset_in_sector: t.CUInt32T = cluster_offset % 512 + sector: t.CUInt64T = vol.cluster_to_sector(self.current_cluster) + t.CUInt64T(sector_in_cluster) + can_write_in_sector: t.CUInt32T = 512 - offset_in_sector + can_write: t.CUInt32T = can_write_in_sector + if can_write > remaining: can_write = remaining + if can_write == 0: break + if offset_in_sector == 0 and can_write >= 512: + abs_sect: t.CUInt64T = sector + t.CUInt64T(vol.hidden_sectors) + vol.lock.lock() + res: DRESULT = diskio.write(vol.pdrv, buf_ptr, t.CUInt32T(abs_sect), 1) + vol.lock.unlock() + if res != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + self.buf_sect = 0xFFFFFFFFFFFFFFFF + self.buf_dirty = 0 + else: + res2: FRESULT = self.buf_read(sector) + if res2 != FRESULT.FR_OK: return res2 + string.memcpy(c.Addr(self.buf[offset_in_sector]), buf_ptr, can_write) + self.buf_dirty = 1 + res3: FRESULT = self.buf_flush() + if res3 != FRESULT.FR_OK: return res3 + buf_ptr += can_write + self.file_pos += can_write + total_written += can_write + remaining -= can_write + if self.file_pos > self.file_size: self.file_size = self.file_pos + if self.file_pos % vol.cluster_size == 0: + next_c: t.CUInt32T = vol.cluster_get_next(self.current_cluster) + if next_c != 0: self.current_cluster = next_c + c.Set(t.CUInt32T(bw, t.CPtr), total_written) + return FRESULT.FR_OK + + def seek(self, offset: t.CUInt32T) -> FRESULT: + if not self.is_open: return FRESULT.FR_INVALID_OBJECT + if offset > self.file_size: + if not (self.flags & FA_WRITE): return FRESULT.FR_DENIED + gap: t.CUInt32T = offset - self.file_size + res: FRESULT = self._extend_to(offset) + if res != FRESULT.FR_OK: return res + old_size: t.CUInt32T = self.file_size + zero_buf: t.CArray[t.CUInt8T, 512] + string.memset(c.Addr(zero_buf[0]), 0, 512) + self.file_pos = old_size + while gap > 0: + chunk: t.CUInt32T = gap + if chunk > 512: chunk = 512 + written: t.CUInt32T = 0 + res2: FRESULT = self.write(c.Addr(zero_buf[0]), chunk, c.Addr(written)) + if res2 != FRESULT.FR_OK: return res2 + if written == 0: return FRESULT.FR_DENIED + gap -= written + self.file_size = offset + self.vol.dir_update_entry(self.dir_sector, self.dir_offset, self.first_cluster, self.file_size, self.attr) + return FRESULT.FR_OK + self.file_pos = offset + if self.first_cluster < 2: + self.current_cluster = 0 + return FRESULT.FR_OK + self.current_cluster = self.vol.file_cluster_seek(self.first_cluster, offset) + return FRESULT.FR_OK + + def truncate(self) -> FRESULT: + if not self.is_open: return FRESULT.FR_INVALID_OBJECT + if not (self.flags & FA_WRITE): return FRESULT.FR_DENIED + if self.file_pos >= self.file_size: return FRESULT.FR_OK + vol: volinfo | t.CPtr = self.vol + new_size: t.CUInt32T = self.file_pos + if new_size == 0: + if self.first_cluster >= 2: vol.cluster_free_chain(self.first_cluster) + self.first_cluster = 0 + self.current_cluster = 0 + self.file_size = 0 + self.file_pos = 0 + vol.dir_update_entry(self.dir_sector, self.dir_offset, 0, 0, self.attr) + return FRESULT.FR_OK + cluster_at_pos: t.CUInt32T = vol.file_cluster_seek(self.first_cluster, new_size) + if cluster_at_pos < 2: return FRESULT.FR_INT_ERR + next_c: t.CUInt32T = vol.cluster_get_next(cluster_at_pos) + vol.fat_write(cluster_at_pos, CLUSTER_EOC_MAX) + if next_c >= 2 and next_c < CLUSTER_EOC_MIN: + vol.cluster_free_chain(next_c) + self.file_size = new_size + self.current_cluster = cluster_at_pos + vol.dir_update_entry(self.dir_sector, self.dir_offset, self.first_cluster, new_size, self.attr) + return FRESULT.FR_OK + + def tell(self) -> t.CUInt32T: + return self.file_pos + + def size(self) -> t.CUInt32T: + return self.file_size + + +@t.Object +class dirobj: + vol: volinfo | t.CPtr + cluster: t.CUInt32T = 0 + start_cluster: t.CUInt32T = 0 + sector: t.CUInt64T = 0 + offset: t.CUInt32T = 0 + is_open: t.CInt = 0 + lfn_ofs: t.CUInt32T = 4294967295 + lfn_cluster: t.CUInt32T = 0 + sfn: t.CArray[t.CChar, 12] + + def close(self): + self.is_open = 0 + + +handle_table: t.CArray[fileobj, MAX_HANDLES] +lock_table: t.CArray[filelock, MAX_LOCKS] +handles_initialized: t.CStatic | t.CInt = 0 + +def handles_init(): + if handles_initialized: return + for i in range(MAX_HANDLES): + handle_table[i].is_open = 0 + handle_table[i].lock_count = 0 + for j in range(MAX_LOCKS): + lock_table[j].in_use = 0 + handles_initialized = 1 + +def handle_alloc() -> fileobj | t.CPtr: + handles_init() + for i in range(MAX_HANDLES): + if not handle_table[i].is_open: + return c.Addr(handle_table[i]) + return None + +def lock_check(dir_sector: t.CUInt64T, dir_offset: t.CUInt32T, mode: t.CUInt8T) -> FRESULT: + handles_init() + for i in range(MAX_LOCKS): + lk: filelock | t.CPtr = c.Addr(lock_table[i]) + if lk.in_use and lk.dir_sector == dir_sector and lk.dir_offset == dir_offset: + if mode & FA_WRITE: return FRESULT.FR_LOCKED + if lk.exclusive: return FRESULT.FR_LOCKED + return FRESULT.FR_OK + +def lock_acquire(dir_sector: t.CUInt64T, dir_offset: t.CUInt32T, exclusive: t.CInt) -> FRESULT: + handles_init() + for i in range(MAX_LOCKS): + lk: filelock | t.CPtr = c.Addr(lock_table[i]) + if not lk.in_use: + lk.in_use = 1 + lk.dir_sector = dir_sector + lk.dir_offset = dir_offset + lk.exclusive = exclusive + lk.shared_count = 1 if not exclusive else 0 + return FRESULT.FR_OK + return FRESULT.FR_TOO_MANY_OPEN_FILES + +def lock_release(dir_sector: t.CUInt64T, dir_offset: t.CUInt32T): + handles_init() + for i in range(MAX_LOCKS): + lk: filelock | t.CPtr = c.Addr(lock_table[i]) + if lk.in_use and lk.dir_sector == dir_sector and lk.dir_offset == dir_offset: + lk.shared_count -= 1 + if lk.shared_count <= 0: lk.in_use = 0 + return diff --git a/VKernel/Kernel/drivers/fs/fat32/fileio.py b/VKernel/Kernel/drivers/fs/fat32/fileio.py new file mode 100644 index 0000000..1ff5896 --- /dev/null +++ b/VKernel/Kernel/drivers/fs/fat32/fileio.py @@ -0,0 +1,3 @@ +import fat32 + + diff --git a/VKernel/Kernel/drivers/input/keyboard/keyboard.py b/VKernel/Kernel/drivers/input/keyboard/keyboard.py new file mode 100644 index 0000000..fce1039 --- /dev/null +++ b/VKernel/Kernel/drivers/input/keyboard/keyboard.py @@ -0,0 +1,146 @@ +import asm +import intr.idt as idt +import drivers.serial.uart.serial as serial +import viperstring as string +import t, c + + +KBD_DATA: t.CDefine = 0x60 +KBD_STATUS: t.CDefine = 0x64 +KBD_COMMAND: t.CDefine = 0x64 + +KBD_BUF_SIZE: t.CDefine = 1024 + +KEY_RELEASED: t.CDefine = 0x80 + +buffer: t.CArray[t.CUInt8T, KBD_BUF_SIZE] +buf_head: t.CStatic | t.CUInt32T = 0 +buf_tail: t.CStatic | t.CUInt32T = 0 +buf_count: t.CStatic | t.CUInt32T = 0 + +shift_l: t.CStatic | t.CUInt8T = 0 +shift_r: t.CStatic | t.CUInt8T = 0 +ctrl_l: t.CStatic | t.CUInt8T = 0 +alt_l: t.CStatic | t.CUInt8T = 0 +caps_lock: t.CStatic | t.CUInt8T = 0 + +scancode_set1: t.CArray[t.CChar, 128] = [ + '\0', '\x1b', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', + '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', + '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '`', '\0', + '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' ', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '8', '6', '2', '\0', + '1', '+', '4', '5', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0' +] + +scancode_set1_shift: t.CArray[t.CChar, 128] = [ + '\0', '\x1b', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', + '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', + '\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', '\0', + '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', '\0', '\0', '\0', ' ', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '8', '6', '2', '\0', + '1', '+', '4', '5', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0' +] + +def _wait_read(): + timeout: t.CUInt32T = 100000 + while timeout > 0: + if (asm.inb(KBD_STATUS) & 0x01) != 0: return + timeout -= 1 + +def _wait_write(): + timeout: t.CUInt32T = 100000 + while timeout > 0: + if (asm.inb(KBD_STATUS) & 0x02) == 0: return + timeout -= 1 + +def irq_handler() -> t.CInt: + global buf_head, buf_tail, buf_count + scancode: t.CUInt8T = asm.inb(KBD_DATA) + if buf_count < KBD_BUF_SIZE: + buffer[buf_tail] = scancode + buf_tail = buf_tail + 1 + if buf_tail >= KBD_BUF_SIZE: + buf_tail = 0 + buf_count += 1 + return 0 + +def read_scancode() -> t.CUInt8T: + global buf_head, buf_count + asm.cli() + if buf_count == 0: + asm.sti() + return 0 + code: t.CUInt8T = buffer[buf_head] + buf_head = buf_head + 1 + if buf_head >= KBD_BUF_SIZE: + buf_head = 0 + buf_count -= 1 + asm.sti() + return code + +def scancode_to_ascii(scancode: t.CUInt8T) -> t.CChar: + code: t.CUInt8T = scancode & ~t.CUInt8T(KEY_RELEASED) + if code >= 128: return '\0' + shifted: bool = (shift_l or shift_r) != 0 + if caps_lock and code >= 0x10 and code <= 0x32: + shifted = not shifted + if shifted: + return scancode_set1_shift[code] + return scancode_set1[code] + +def has_data() -> t.CInt: + return buf_count + +def init(): + drain: t.CUInt32T + drain = 0 + while (asm.inb(KBD_STATUS) & 0x01) != 0: + asm.inb(KBD_DATA) + drain += 1 + if drain > 1000: break + + _wait_write() + asm.outb(KBD_COMMAND, 0xAD) + + drain = 0 + while (asm.inb(KBD_STATUS) & 0x01) != 0: + asm.inb(KBD_DATA) + drain += 1 + if drain > 1000: break + + _wait_write() + asm.outb(KBD_COMMAND, 0x20) + _wait_read() + ccb: t.CUInt8T = asm.inb(KBD_DATA) + + ccb = ccb | 0x01 + ccb = ccb | 0x40 + ccb = ccb & 0xEF + + _wait_write() + asm.outb(KBD_COMMAND, 0x60) + _wait_write() + asm.outb(KBD_DATA, ccb) + + _wait_write() + asm.outb(KBD_COMMAND, 0xAE) + + drain = 0 + while (asm.inb(KBD_STATUS) & 0x01) != 0: + asm.inb(KBD_DATA) + drain += 1 + if drain > 1000: break + + _wait_write() + asm.outb(KBD_DATA, 0xF4) + _wait_read() + asm.inb(KBD_DATA) + + idt.irqInstallHandler(1, irq_handler, "keyboard") diff --git a/VKernel/Kernel/drivers/input/mouse/mouse.py b/VKernel/Kernel/drivers/input/mouse/mouse.py new file mode 100644 index 0000000..aee483f --- /dev/null +++ b/VKernel/Kernel/drivers/input/mouse/mouse.py @@ -0,0 +1,134 @@ +import asm +import intr.idt as idt +import platform.pch.pic as pic +import drivers.serial.uart.serial as serial +import t, c + + +MOUSE_DATA: t.CDefine = 0x60 +MOUSE_STATUS: t.CDefine = 0x64 +MOUSE_COMMAND: t.CDefine = 0x64 + +MOUSE_BUF_SIZE: t.CDefine = 1024 + +MOUSE_LEFT: t.CDefine = 0x01 +MOUSE_RIGHT: t.CDefine = 0x02 +MOUSE_MIDDLE: t.CDefine = 0x04 + +class mouse_packet: + flags: t.CUInt8T + dx: t.CInt8T + dy: t.CInt8T + buttons: t.CUInt8T + +packets: t.CArray[mouse_packet, MOUSE_BUF_SIZE] +pack_head: t.CStatic | t.CUInt32T = 0 +pack_tail: t.CStatic | t.CUInt32T = 0 +pack_count: t.CStatic | t.CUInt32T = 0 + +cycle: t.CStatic | t.CUInt8T = 0 +byte1: t.CStatic | t.CUInt8T = 0 +byte2: t.CStatic | t.CInt8T = 0 +byte3: t.CStatic | t.CInt8T = 0 + +mouse_x: t.CStatic | t.CInt32T = 0 +mouse_y: t.CStatic | t.CInt32T = 0 +mouse_buttons: t.CStatic | t.CUInt8T = 0 + +def _wait_read(): + timeout: t.CUInt32T = 100000 + while timeout > 0: + if (asm.inb(MOUSE_STATUS) & 0x01) != 0: return + timeout -= 1 + +def _wait_write(): + timeout: t.CUInt32T = 100000 + while timeout > 0: + if (asm.inb(MOUSE_STATUS) & 0x02) == 0: return + timeout -= 1 + +def _write_cmd(cmd: t.CUInt8T): + _wait_write() + asm.outb(MOUSE_COMMAND, 0xD4) + _wait_write() + asm.outb(MOUSE_DATA, cmd) + _wait_read() + asm.inb(MOUSE_DATA) + +def irq_handler() -> t.CInt: + global cycle, byte1, byte2, byte3, pack_tail, pack_count + status: t.CUInt8T = asm.inb(MOUSE_STATUS) + if (status & 0x20) == 0: return 0 + data: t.CUInt8T = asm.inb(MOUSE_DATA) + if cycle == 0: + if (data & 0x08) != 0: + byte1 = data + cycle = 1 + return 0 + if cycle == 1: + byte2 = data + cycle = 2 + return 0 + byte3 = data + cycle = 0 + if pack_count < MOUSE_BUF_SIZE: + packets[pack_tail].flags = byte1 + packets[pack_tail].dx = byte2 + packets[pack_tail].dy = byte3 + packets[pack_tail].buttons = byte1 & t.CUInt8T(0x07) + pack_tail = pack_tail + 1 + if pack_tail >= MOUSE_BUF_SIZE: + pack_tail = 0 + pack_count += 1 + return 0 + +def read_packet(out: mouse_packet | t.CPtr) -> t.CInt: + global pack_head, pack_count + asm.cli() + if pack_count == 0: + asm.sti() + out.flags = 0 + out.dx = 0 + out.dy = 0 + out.buttons = 0 + return -1 + out.flags = packets[pack_head].flags + out.dx = packets[pack_head].dx + out.dy = packets[pack_head].dy + out.buttons = packets[pack_head].buttons + pack_head = pack_head + 1 + if pack_head >= MOUSE_BUF_SIZE: + pack_head = 0 + pack_count -= 1 + asm.sti() + return 0 + +def has_data() -> t.CInt: + return pack_count + +def get_x() -> t.CInt32T: + return mouse_x + +def get_y() -> t.CInt32T: + return mouse_y + +def get_buttons() -> t.CUInt8T: + return mouse_buttons + +def init(): + _wait_write() + asm.outb(MOUSE_COMMAND, 0xA8) + _wait_write() + asm.outb(MOUSE_COMMAND, 0x20) + _wait_read() + status: t.CUInt8T = asm.inb(MOUSE_DATA) + status = status | 0x02 + status = status & 0xDF + _wait_write() + asm.outb(MOUSE_COMMAND, 0x60) + _wait_write() + asm.outb(MOUSE_DATA, status) + _write_cmd(0xF6) + _write_cmd(0xF4) + pic.clearMask(12) + idt.irqInstallHandler(12, irq_handler, "mouse") diff --git a/VKernel/Kernel/drivers/serial/uart/serial.py b/VKernel/Kernel/drivers/serial/uart/serial.py new file mode 100644 index 0000000..35ad868 --- /dev/null +++ b/VKernel/Kernel/drivers/serial/uart/serial.py @@ -0,0 +1,46 @@ +import platform.pch.pic as pic +import su8250 +import t, c + + +# 串口初始化 +def init(): + # 初始化COM1端口 + su8250.init(su8250.COM1_PORT) + # 启用COM1中断(IRQ4) + pic.clearMask(4) + +# 发送一个字符到COM1 +def putc(cr: t.CChar): + su8250.putchar(su8250.COM1_PORT, cr) + +def putchar(cr: t.CChar): + su8250.putchar(su8250.COM1_PORT, cr) + +# 从COM1读取一个字符 +def getchar() -> t.CChar: + return su8250.getchar(su8250.COM1_PORT) + +# 发送一个字符串到COM1 +def puts(s: str): + # 逐个字符发送,直接复用putchar的逻辑 + for i in s: + putchar(i) + +# 检查COM1是否有数据可读 +def isDataAvailable() -> t.CInt: + return su8250.isDataAvailable(su8250.COM1_PORT) + +# 检查COM1发送缓冲区是否为空 +def isTransmitEmpty() -> t.CInt: + return su8250.isTransmitEmpty(su8250.COM1_PORT) + +# 发送32位十六进制值到COM1 +def put_hex32(value: t.CUInt32T): + hex_digits: t.CArray[t.CChar, None] = ["0123456789ABCDEF"] + buffer: t.CArray[t.CChar, 9] + for i in range(7, -1, -1): + buffer[i] = hex_digits[value & 0xF] + value >>= 4 + buffer[8] = '\0' + puts(buffer) diff --git a/VKernel/Kernel/drivers/serial/uart/su8250.py b/VKernel/Kernel/drivers/serial/uart/su8250.py new file mode 100644 index 0000000..568a41b --- /dev/null +++ b/VKernel/Kernel/drivers/serial/uart/su8250.py @@ -0,0 +1,116 @@ +import platform.pch as pch +import asm +import t, c + + + +# 串口端口定义 +COM1_PORT: t.CDefine = 0x3F8 # COM1端口地址 +COM2_PORT: t.CDefine = 0x2F8 # COM2端口地址 + +# 8250/16550 UART寄存器偏移 +UART_RBR: t.CDefine = 0 # 接收缓冲区寄存器(读) +UART_THR: t.CDefine = 0 # 发送保持寄存器(写) +UART_DLL: t.CDefine = 0 # 除数锁存低位(写,DLAB=1) +UART_IER: t.CDefine = 1 # 中断使能寄存器 +UART_DLM: t.CDefine = 1 # 除数锁存高位(写,DLAB=1) +UART_FCR: t.CDefine = 2 # FIFO控制寄存器 +UART_IIR: t.CDefine = 2 # 中断识别寄存器(读) +UART_LCR: t.CDefine = 3 # 线路控制寄存器 +UART_MCR: t.CDefine = 4 # 调制解调器控制寄存器 +UART_LSR: t.CDefine = 5 # 线路状态寄存器 +UART_MSR: t.CDefine = 6 # 调制解调器状态寄存器 +UART_SCR: t.CDefine = 7 # scratch register + + +UART_LSR_THRE: t.CDefine = 0x20 # THRE:发送保持寄存器空 +UART_LSR_TEMT: t.CDefine = 0x40 # TEMT:发送移位寄存器空(真正的发送完成) + + +# 线路控制寄存器位定义 +UART_LCR_DLAB: t.CDefine = 0x80 # 除数锁存访问位 +UART_LCR_8BIT: t.CDefine = 0x03 # 8位数据位 + +# 线路状态寄存器位定义 +UART_LSR_DR: t.CDefine = 0x01 # 数据就绪 +UART_LSR_THRE: t.CDefine = 0x20 # 发送保持寄存器空 + +# 波特率除数(115200 bps) +BAUD_115200: t.CDefine = 1 + + +# 初始化UART 8250/16550 +def init(port: t.CUInt16T): + # 1. 禁用中断 + asm.outb(port + UART_IER, 0x00) + delay(1000) + # 2. 设置波特率除数(115200 bps) + asm.outb(port + UART_LCR, UART_LCR_DLAB) # 设置DLAB位 + delay(1000) + asm.outb(port + UART_DLL, 0x01) # 除数低位 (115200 bps) + delay(1000) + asm.outb(port + UART_DLM, 0x00) # 除数高位 + delay(1000) + # 3. 设置线路控制:8位数据位,1位停止位,无校验 + asm.outb(port + UART_LCR, UART_LCR_8BIT) + delay(1000) + # 4. 初始化FIFO + asm.outb(port + UART_FCR, 0x07) # 启用FIFO,清除接收和发送FIFO + delay(1000) + # 5. 设置调制解调器控制 + asm.outb(port + UART_MCR, 0x03) # 启用DTR、RTS + delay(1000) + # 6. 清除任何待处理的中断 + asm.inb(port + UART_IIR) + delay(1000) + asm.inb(port + UART_RBR) + delay(1000) + # 7. 等待一段时间让设备稳定 + delay(10000) + +# 延迟函数 +def delay(count: t.CInt): + for i in range(count): c.Asm("nop") + +# 检查发送缓冲区是否为空 +def isTransmitEmpty(port: t.CUInt16T) -> t.CInt: + return asm.inb(port + UART_LSR) & UART_LSR_THRE + +def putchar(port: t.CUInt16T, cr: t.CChar): + # 1. 处理换行符:串口终端需要\r\n才会正确换行,只发\n会错位 + if cr == '\n': + # 直接发送\r,避免递归调用 + timeout: t.CUInt32T = 1000000 + while ((asm.inb(port + UART_LSR) & UART_LSR_THRE) == 0) and timeout: + c.Asm("nop") + timeout -= 1 + if timeout == 0: return + asm.outb(port + UART_THR, '\r') + timeout = 1000000 + while ((asm.inb(port + UART_LSR) & UART_LSR_TEMT) == 0) and timeout: + c.Asm("nop") + timeout -= 1 + # 2. 等待发送缓冲区为空 + timeout: t.CUInt32T = 1000000 + while ((asm.inb(port + UART_LSR) & UART_LSR_THRE) == 0) and timeout: + c.Asm("nop") + timeout -= 1 + if timeout == 0: return + # 3. 发送字符 + asm.outb(port + UART_THR, cr) + # 4. 等待发送完成 + timeout = 1000000 + while ((asm.inb(port + UART_LSR) & UART_LSR_TEMT) == 0) and timeout: + c.Asm("nop") + timeout -= 1 + +# 检查是否有数据可读 +def isDataAvailable(port: t.CUInt16T) -> t.CInt: + return asm.inb(port + UART_LSR) & UART_LSR_DR + +# 读取一个字符 +def getchar(port: t.CUInt16T) -> t.CChar: + # 等待数据可用 + while not isDataAvailable(port): pass + # 读取字符 + return asm.inb(port + UART_RBR) diff --git a/VKernel/Kernel/drivers/storage/ide/ide.py b/VKernel/Kernel/drivers/storage/ide/ide.py new file mode 100644 index 0000000..d04d89e --- /dev/null +++ b/VKernel/Kernel/drivers/storage/ide/ide.py @@ -0,0 +1,161 @@ +import asm +import t, c +import serial +import viperlib +import pci + + +class IDError(t.CEnum): + IDE_ERR_NONE: t.State = 0 + IDE_ERR_TIMEOUT: t.State + IDE_ERR_DEVICE_ERROR: t.State + IDE_ERR_DEVICE_BUSY: t.State + IDE_ERR_INVALID_PARAM: t.State + IDE_ERR_BUFFER_NULL: t.State + IDE_ERR_SECTOR_COUNT: t.State + IDE_ERR_DEVICE_NOT_READY: t.State + IDE_ERR_DEVICE_FAILURE: t.State + IDE_ERR_COMMAND_FAILED: t.State + IDE_ERR_INVALID_LBA: t.State + +IDE_baseport_PRIMARY: t.CDefine = 0x1F0 +IDE_baseport_SECONDARY: t.CDefine = 0x170 + +IDE_STATUS_BSY: t.CDefine = 0x80 +IDE_STATUS_DRDY: t.CDefine = 0x40 +IDE_STATUS_DF: t.CDefine = 0x20 +IDE_STATUS_DRQ: t.CDefine = 0x08 +IDE_STATUS_ERR: t.CDefine = 0x01 + +SECTOR_SIZE: t.CDefine = 512 + +_ide_initialized: t.CInt = 0 + +def waitReady(baseport: t.CUInt16T) -> t.CStatic | IDError: + timeout: t.CInt = 100000 + while timeout > 0: + status: t.CUInt8T = asm.inb(baseport + 7) + if status & IDE_STATUS_DF: + return IDError.IDE_ERR_DEVICE_FAILURE + if not status & IDE_STATUS_BSY and status & IDE_STATUS_DRDY: + return IDError.IDE_ERR_NONE + timeout -= 1 + return IDError.IDE_ERR_TIMEOUT + +def waitDataReady(baseport: t.CUInt16T) -> t.CStatic | IDError: + timeout: t.CInt = 100000 + while timeout > 0: + status: t.CUInt8T = asm.inb(baseport + 7) + if status & IDE_STATUS_ERR: return IDError.IDE_ERR_DEVICE_ERROR + if status & IDE_STATUS_DF: return IDError.IDE_ERR_DEVICE_FAILURE + if not status & IDE_STATUS_BSY and (status & IDE_STATUS_DRQ or status & IDE_STATUS_DRDY): + return IDError.IDE_ERR_NONE + timeout -= 1 + return IDError.IDE_ERR_TIMEOUT + +def init() -> t.CInt: + if _ide_initialized: return IDError.IDE_ERR_NONE + dbg: t.CArray[t.CChar, 120] + bus: t.CUInt8T + dev_n: t.CUInt8T + func: t.CUInt8T + for bus in range(2): + for dev_n in range(32): + for func in range(8): + vendor: t.CUInt16T = pci.pci_read16(bus, dev_n, func, 0x00) + if vendor == 0xFFFF: continue + cls: t.CUInt32T = pci.pci_get_class(bus, dev_n, func) + if cls == 0x010180 or cls == 0x0101: + cmd: t.CUInt16T = pci.pci_read16(bus, dev_n, func, 0x04) + pci.pci_write32(bus, dev_n, func, 0x04, t.CUInt32T(cmd | 0x0007)) + viperlib.snprintf(c.Addr(dbg), 120, "[ide] found at %d:%d:%d cmd=0x%04x\n", t.CInt(bus), t.CInt(dev_n), t.CInt(func), t.CUInt32T(pci.pci_read16(bus, dev_n, func, 0x04))) + serial.puts(dbg) + asm.outb(0x3F6, 0x04) + i: t.CInt + for i in range(10000): + asm.nop() + asm.outb(0x3F6, 0x00) + for i in range(10000): + asm.nop() + status: t.CUInt8T = asm.inb(0x1F7) + viperlib.snprintf(c.Addr(dbg), 120, "[ide] status=0x%02x\n", t.CUInt32T(status)) + serial.puts(dbg) + _ide_initialized = 1 + return IDError.IDE_ERR_NONE + +def readSector(lba: t.CUInt32T, buffer: t.CVoid | t.CPtr) -> t.CInt: + if buffer is None: return IDError.IDE_ERR_BUFFER_NULL + dbg: t.CArray[t.CChar, 120] + err: IDError = waitReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: + viperlib.snprintf(c.Addr(dbg), 120, "[readSector] waitReady err=%d\n", t.CInt(err)) + serial.puts(dbg) + return err + asm.outb(0x3F6, 0x02) + asm.outb(0x1F2, 1) + asm.outb(0x1F3, t.CUInt8T(lba & 0xFF)) + asm.outb(0x1F4, t.CUInt8T((lba >> 8) & 0xFF)) + asm.outb(0x1F5, t.CUInt8T((lba >> 16) & 0xFF)) + asm.outb(0x1F6, t.CUInt8T(0xE0 | ((lba >> 24) & 0x0F))) + asm.outb(0x1F7, 0x20) + err = waitDataReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: + st: t.CUInt8T = asm.inb(0x1F7) + viperlib.snprintf(c.Addr(dbg), 120, "[readSector] waitDataReady err=%d status=0x%02x\n", t.CInt(err), t.CUInt32T(st)) + serial.puts(dbg) + return err + st2: t.CUInt8T = asm.inb(0x1F7) + buf: t.CUInt16T | t.CPtr = t.CUInt16T(buffer, t.CPtr) + for j in range(256): + c.Set(c.Deref(buf), asm.inw(0x1F0)) + buf += 1 + first_byte: t.CUInt8T = c.Deref(t.CUInt8T(buffer, t.CPtr)) + # viperlib.snprintf(c.Addr(dbg), 120, "[readSector] lba=%lu status=0x%02x first=%02x\n", lba, t.CUInt32T(st2), t.CUInt32T(first_byte)) + # serial.puts(dbg) + return IDError.IDE_ERR_NONE + +def writeSector(lba: t.CUInt32T, buffer: t.CConst | t.CVoid | t.CPtr) -> t.CInt: + if buffer is None: return IDError.IDE_ERR_BUFFER_NULL + err: IDError = waitReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return err + asm.outb(0x3F6, 0x02) + asm.outb(0x1F2, 1) + asm.outb(0x1F3, t.CUInt8T(lba & 0xFF)) + asm.outb(0x1F4, t.CUInt8T((lba >> 8) & 0xFF)) + asm.outb(0x1F5, t.CUInt8T((lba >> 16) & 0xFF)) + asm.outb(0x1F6, t.CUInt8T(0xE0 | ((lba >> 24) & 0x0F))) + asm.outb(0x1F7, 0x30) + err = waitDataReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return err + buf: t.CUInt16T | t.CPtr = t.CUInt16T(buffer, t.CPtr) + for j in range(256): + val: t.CUInt16T = c.Deref(buf) + asm.outw(0x1F0, val) + buf += 1 + asm.outb(0x1F7, 0xE7) + waitReady(IDE_baseport_PRIMARY) + return IDError.IDE_ERR_NONE + +def getDiskSize() -> t.CUInt64T: + err: IDError = waitReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return 0 + asm.outb(0x1F6, 0xE0) + asm.outb(0x1F7, 0xEC) + err = waitDataReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return 0 + status: t.CUInt8T = asm.inb(0x1F7) + if not status & IDE_STATUS_DRDY: return 0 + if status & IDE_STATUS_ERR: return 0 + identify_data: t.CArray[t.CUInt16T, 256] + for i in range(256): + identify_data[i] = asm.inw(0x1F0) + totalSectors: t.CUInt64T = identify_data[60] | t.CUInt64T(identify_data[61]) << 16 + if identify_data[83] & (1 << 10): + lba48_sectors: t.CUInt64T = ( + t.CUInt64T(identify_data[100]) | + t.CUInt64T(identify_data[101]) << 16 | + t.CUInt64T(identify_data[102]) << 32 | + t.CUInt64T(identify_data[103]) << 48) + if lba48_sectors > totalSectors: + totalSectors = lba48_sectors + return totalSectors diff --git a/VKernel/Kernel/drivers/usb/hid/hid.py b/VKernel/Kernel/drivers/usb/hid/hid.py new file mode 100644 index 0000000..a771b42 --- /dev/null +++ b/VKernel/Kernel/drivers/usb/hid/hid.py @@ -0,0 +1,37 @@ +import t, c +import drivers.usb.usb as usb + +HID_BOOT_PROTOCOL: t.CDefine = 0 +HID_REPORT_PROTOCOL: t.CDefine = 1 + +HID_KBD_REPORT_SIZE: t.CDefine = 8 +HID_MOUSE_REPORT_SIZE: t.CDefine = 4 + +class hid_kbd_report(t.CStruct): + modifier: t.CUInt8T + reserved: t.CUInt8T + keycode: t.CArray[t.CUInt8T, 6] + +class hid_mouse_report(t.CStruct): + buttons: t.CUInt8T + dx: t.CInt8T + dy: t.CInt8T + wheel: t.CInt8T + +def init_keyboard(dev: usb.usb_device | t.CPtr) -> t.CInt: + if dev.iface_class != 0x03: return -1 + if dev.iface_protocol != 0x01: return -2 + if dev.ep_in == 0: return -3 + return 0 + +def init_mouse(dev: usb.usb_device | t.CPtr) -> t.CInt: + if dev.iface_class != 0x03: return -1 + if dev.iface_protocol != 0x02: return -2 + if dev.ep_in == 0: return -3 + return 0 + +def read_keyboard(dev: usb.usb_device | t.CPtr, report: hid_kbd_report | t.CPtr) -> t.CInt: + return usb.read_interrupt(dev, c.Addr(report)) + +def read_mouse(dev: usb.usb_device | t.CPtr, report: hid_mouse_report | t.CPtr) -> t.CInt: + return usb.read_interrupt(dev, c.Addr(report)) diff --git a/VKernel/Kernel/drivers/usb/hid/usb_kbd.py b/VKernel/Kernel/drivers/usb/hid/usb_kbd.py new file mode 100644 index 0000000..0c953fc --- /dev/null +++ b/VKernel/Kernel/drivers/usb/hid/usb_kbd.py @@ -0,0 +1,111 @@ +import t, c +import drivers.usb.usb as usb +import drivers.usb.hid.hid as hid +import drivers.usb.uhci as uhci +import drivers.serial.uart.serial as serial + +KBD_BUF_SIZE: t.CDefine = 1024 +KBD_EVT_PRESS: t.CDefine = 0 +KBD_EVT_RELEASE: t.CDefine = 1 + +class usb_kbd_event(t.CStruct): + modifier: t.CUInt8T + keycode: t.CUInt8T + event_type: t.CUInt8T + +kbd_buf: t.CArray[usb_kbd_event, KBD_BUF_SIZE] +kbd_buf_head: t.CInt = 0 +kbd_buf_tail: t.CInt = 0 +kbd_buf_count: t.CInt = 0 +kbd_dev: usb.usb_device | t.CPtr = None +kbd_report: hid.hid_kbd_report +kbd_prev_keycodes: t.CArray[t.CUInt8T, 6] +kbd_initialized: t.CInt = 0 +kbd_slot_id: t.CInt = -1 + +def init() -> t.CInt: + global kbd_dev, kbd_initialized, kbd_slot_id + dev: usb.usb_device | t.CPtr = usb.find_hid_device(0x01) + if dev is None: + serial.puts("[usb_kbd] no USB keyboard found\n") + return -1 + kbd_dev = dev + if hid.init_keyboard(dev) != 0: + serial.puts("[usb_kbd] init failed\n") + return -2 + kbd_initialized = 1 + serial.puts("[usb_kbd] initialized\n") + kbd_slot_id = uhci.register_int_callback(c.Addr(_on_int_complete)) + if kbd_slot_id < 0: + serial.puts("[usb_kbd] register callback failed\n") + kbd_initialized = 0 + return -3 + uhci.schedule_int_transfer(kbd_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(kbd_report)) + return 0 + +def _on_int_complete() -> t.CInt: + global kbd_buf_head, kbd_buf_tail, kbd_buf_count, kbd_prev_keycodes + if kbd_initialized == 0: return 0 + i: t.CInt + j: t.CInt + for i in range(6): + code: t.CUInt8T = kbd_report.keycode[i] + if code == 0: continue + found: t.CInt = 0 + for j in range(6): + if code == kbd_prev_keycodes[j]: + found = 1 + break + if found == 0: + if kbd_buf_count < KBD_BUF_SIZE: + kbd_buf[kbd_buf_tail].modifier = kbd_report.modifier + kbd_buf[kbd_buf_tail].keycode = code + kbd_buf[kbd_buf_tail].event_type = KBD_EVT_PRESS + kbd_buf_tail = kbd_buf_tail + 1 + if kbd_buf_tail >= KBD_BUF_SIZE: + kbd_buf_tail = 0 + kbd_buf_count += 1 + for i in range(6): + prev: t.CUInt8T = kbd_prev_keycodes[i] + if prev == 0: continue + found2: t.CInt = 0 + for j in range(6): + if prev == kbd_report.keycode[j]: + found2 = 1 + break + if found2 == 0: + if kbd_buf_count < KBD_BUF_SIZE: + kbd_buf[kbd_buf_tail].modifier = kbd_report.modifier + kbd_buf[kbd_buf_tail].keycode = prev + kbd_buf[kbd_buf_tail].event_type = KBD_EVT_RELEASE + kbd_buf_tail = kbd_buf_tail + 1 + if kbd_buf_tail >= KBD_BUF_SIZE: + kbd_buf_tail = 0 + kbd_buf_count += 1 + for i in range(6): + kbd_prev_keycodes[i] = kbd_report.keycode[i] + dev: usb.usb_device | t.CPtr = kbd_dev + if dev is not None and kbd_slot_id >= 0: + uhci.schedule_int_transfer(kbd_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(kbd_report)) + return 0 + +def has_data() -> t.CInt: + if kbd_buf_count > 0: return 1 + return 0 + +def poll() -> t.CInt: + if kbd_initialized == 0: return 0 + if kbd_buf_count > 0: return 1 + return 0 + +def read_event(out: usb_kbd_event | t.CPtr) -> t.CInt: + global kbd_buf_head, kbd_buf_count + if kbd_buf_count == 0: return -1 + out.modifier = kbd_buf[kbd_buf_head].modifier + out.keycode = kbd_buf[kbd_buf_head].keycode + out.event_type = kbd_buf[kbd_buf_head].event_type + kbd_buf_head = kbd_buf_head + 1 + if kbd_buf_head >= KBD_BUF_SIZE: + kbd_buf_head = 0 + kbd_buf_count -= 1 + return 0 diff --git a/VKernel/Kernel/drivers/usb/hid/usb_mouse.py b/VKernel/Kernel/drivers/usb/hid/usb_mouse.py new file mode 100644 index 0000000..96fdd49 --- /dev/null +++ b/VKernel/Kernel/drivers/usb/hid/usb_mouse.py @@ -0,0 +1,77 @@ +import t, c +import drivers.usb.usb as usb +import drivers.usb.hid.hid as hid +import drivers.usb.uhci as uhci +import drivers.serial.uart.serial as serial + +MOUSE_BUF_SIZE: t.CDefine = 1024 + +class usb_mouse_packet(t.CStruct): + buttons: t.CUInt8T + dx: t.CInt16T + dy: t.CInt16T + wheel: t.CInt8T + +mse_buf: t.CArray[usb_mouse_packet, MOUSE_BUF_SIZE] +mse_buf_head: t.CInt = 0 +mse_buf_tail: t.CInt = 0 +mse_buf_count: t.CInt = 0 +mse_dev: usb.usb_device | t.CPtr = None +mse_report: hid.hid_mouse_report +mse_initialized: t.CInt = 0 +mse_slot_id: t.CInt = -1 + +def init() -> t.CInt: + global mse_dev, mse_initialized, mse_slot_id + dev: usb.usb_device | t.CPtr = usb.find_hid_device(0x02) + if dev is None: + serial.puts("[usb_mse] no USB mouse found\n") + return -1 + mse_dev = dev + if hid.init_mouse(dev) != 0: + serial.puts("[usb_mse] init failed\n") + return -2 + mse_initialized = 1 + serial.puts("[usb_mse] initialized\n") + mse_slot_id = uhci.register_int_callback(c.Addr(_on_int_complete)) + if mse_slot_id < 0: + serial.puts("[usb_mse] register callback failed\n") + mse_initialized = 0 + return -3 + uhci.schedule_int_transfer(mse_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(mse_report)) + return 0 + +def _on_int_complete() -> t.CInt: + global mse_buf_head, mse_buf_tail, mse_buf_count + if mse_initialized == 0: return 0 + if mse_report.dx != 0 or mse_report.dy != 0 or mse_report.buttons != 0: + if mse_buf_count < MOUSE_BUF_SIZE: + mse_buf[mse_buf_tail].buttons = mse_report.buttons + mse_buf[mse_buf_tail].dx = t.CInt16T(mse_report.dx) + mse_buf[mse_buf_tail].dy = t.CInt16T(mse_report.dy) + mse_buf[mse_buf_tail].wheel = mse_report.wheel + mse_buf_tail = mse_buf_tail + 1 + if mse_buf_tail >= MOUSE_BUF_SIZE: + mse_buf_tail = 0 + mse_buf_count += 1 + dev: usb.usb_device | t.CPtr = mse_dev + if dev is not None and mse_slot_id >= 0: + uhci.schedule_int_transfer(mse_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(mse_report)) + return 0 + +def has_data() -> t.CInt: + if mse_buf_count > 0: return 1 + return 0 + +def read_packet(out: usb_mouse_packet | t.CPtr) -> t.CInt: + global mse_buf_head, mse_buf_count + if mse_buf_count == 0: return -1 + out.buttons = mse_buf[mse_buf_head].buttons + out.dx = mse_buf[mse_buf_head].dx + out.dy = mse_buf[mse_buf_head].dy + out.wheel = mse_buf[mse_buf_head].wheel + mse_buf_head = mse_buf_head + 1 + if mse_buf_head >= MOUSE_BUF_SIZE: + mse_buf_head = 0 + mse_buf_count -= 1 + return 0 diff --git a/VKernel/Kernel/drivers/usb/pci.py b/VKernel/Kernel/drivers/usb/pci.py new file mode 100644 index 0000000..97fd0fc --- /dev/null +++ b/VKernel/Kernel/drivers/usb/pci.py @@ -0,0 +1,93 @@ +import t, c +import asm + +PCI_CONFIG_ADDR: t.CDefine = 0xCF8 +PCI_CONFIG_DATA: t.CDefine = 0xCFC + +PCI_CLASS_SERIAL_USB: t.CDefine = 0x0C03 +PCI_CLASS_SERIAL_UHCI: t.CDefine = 0x0C0300 +PCI_CLASS_SERIAL_OHCI: t.CDefine = 0x0C0310 +PCI_CLASS_SERIAL_EHCI: t.CDefine = 0x0C0320 +PCI_CLASS_SERIAL_XHCI: t.CDefine = 0x0C0330 + +PCI_CMD_IO_SPACE: t.CDefine = 0x0001 +PCI_CMD_BUS_MASTER: t.CDefine = 0x0004 + +class pci_device(t.CStruct): + bus: t.CUInt8T + dev: t.CUInt8T + func: t.CUInt8T + vendor_id: t.CUInt16T + device_id: t.CUInt16T + class_code: t.CUInt32T + irq: t.CUInt8T + bar: t.CArray[t.CUInt32T, 6] + +def pci_read32(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T) -> t.CUInt32T: + addr: t.CUInt32T = 0x80000000 | (t.CUInt32T(bus) << 16) | (t.CUInt32T(dev) << 11) | (t.CUInt32T(func) << 8) | (t.CUInt32T(offset) & 0xFC) + asm.outl(PCI_CONFIG_ADDR, addr) + return asm.inl(PCI_CONFIG_DATA) + +def pci_write32(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T, value: t.CUInt32T): + addr: t.CUInt32T = 0x80000000 | (t.CUInt32T(bus) << 16) | (t.CUInt32T(dev) << 11) | (t.CUInt32T(func) << 8) | (t.CUInt32T(offset) & 0xFC) + asm.outl(PCI_CONFIG_ADDR, addr) + asm.outl(PCI_CONFIG_DATA, value) + +def pci_read16(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T) -> t.CUInt16T: + val: t.CUInt32T = pci_read32(bus, dev, func, offset) + shift: t.CUInt32T = (t.CUInt32T(offset) & 0x02) * 8 + return t.CUInt16T((val >> shift) & 0xFFFF) + +def pci_read8(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T) -> t.CUInt8T: + val: t.CUInt32T = pci_read32(bus, dev, func, offset) + shift: t.CUInt32T = (t.CUInt32T(offset) & 0x03) * 8 + return t.CUInt8T((val >> shift) & 0xFF) + +def pci_get_class(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T) -> t.CUInt32T: + cls: t.CUInt8T = pci_read8(bus, dev, func, 0x0B) + sub: t.CUInt8T = pci_read8(bus, dev, func, 0x0A) + proto: t.CUInt8T = pci_read8(bus, dev, func, 0x09) + return (t.CUInt32T(cls) << 16) | (t.CUInt32T(sub) << 8) | t.CUInt32T(proto) + +def pci_get_bar(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, bar_idx: t.CUInt8T) -> t.CUInt32T: + return pci_read32(bus, dev, func, 0x10 + bar_idx * 4) + +def pci_enable_device(dev: pci_device | t.CPtr): + cmd: t.CUInt16T = pci_read16(dev.bus, dev.dev, dev.func, 0x04) + cmd = cmd | PCI_CMD_IO_SPACE | PCI_CMD_BUS_MASTER + pci_write32(dev.bus, dev.dev, dev.func, 0x04, t.CUInt32T(cmd)) + +def pci_fill_device(bus: t.CUInt8T, dev_n: t.CUInt8T, func: t.CUInt8T, out: pci_device | t.CPtr): + out.bus = bus + out.dev = dev_n + out.func = func + out.vendor_id = pci_read16(bus, dev_n, func, 0x00) + out.device_id = pci_read16(bus, dev_n, func, 0x02) + out.class_code = pci_get_class(bus, dev_n, func) + out.irq = pci_read8(bus, dev_n, func, 0x3C) + i: t.CInt + for i in range(6): + out.bar[i] = pci_get_bar(bus, dev_n, func, t.CUInt8T(i)) + +_uhci_dev: pci_device +_uhci_found: t.CInt = 0 + +def find_uhci() -> t.CUInt16T: + global _uhci_dev, _uhci_found + bus: t.CUInt8T + dev_n: t.CUInt8T + func: t.CUInt8T + for bus in range(1): + for dev_n in range(32): + for func in range(8): + vendor: t.CUInt16T = pci_read16(bus, dev_n, func, 0x00) + if vendor == 0xFFFF: continue + cls: t.CUInt32T = pci_get_class(bus, dev_n, func) + if cls == PCI_CLASS_SERIAL_UHCI: + pci_fill_device(bus, dev_n, func, c.Addr(_uhci_dev)) + pci_enable_device(c.Addr(_uhci_dev)) + _uhci_found = 1 + bar: t.CUInt32T = _uhci_dev.bar[4] + io_base: t.CUInt16T = t.CUInt16T(bar & 0xFFFE) + return io_base + return 0 diff --git a/VKernel/Kernel/drivers/usb/uhci.py b/VKernel/Kernel/drivers/usb/uhci.py new file mode 100644 index 0000000..72183ef --- /dev/null +++ b/VKernel/Kernel/drivers/usb/uhci.py @@ -0,0 +1,415 @@ +import t, c +import asm +import mm.mm as mm +import viperstring as string +import drivers.serial.uart.serial as serial +import platform.pch.timer as timer +import intr.idt as idt +import platform.pch.pic as pic +import drivers.usb.pci as pci + +UHCI_USBCMD: t.CDefine = 0x00 +UHCI_USBSTS: t.CDefine = 0x02 +UHCI_USBINTR: t.CDefine = 0x04 +UHCI_FRNUM: t.CDefine = 0x06 +UHCI_FLBASEADD: t.CDefine = 0x08 +UHCI_SOFMOD: t.CDefine = 0x0C +UHCI_PORTSC1: t.CDefine = 0x10 +UHCI_PORTSC2: t.CDefine = 0x12 + +UHCI_CMD_RUN: t.CDefine = 0x0001 +UHCI_CMD_HCRESET: t.CDefine = 0x0002 +UHCI_CMD_GRESET: t.CDefine = 0x0004 +UHCI_CMD_MAXPKT: t.CDefine = 0x0080 +UHCI_CMD_CF: t.CDefine = 0x0040 + +UHCI_STS_HCHALTED: t.CDefine = 0x0020 +UHCI_STS_USBINT: t.CDefine = 0x0001 +UHCI_STS_USBERRINT: t.CDefine = 0x0002 +UHCI_STS_RESUME: t.CDefine = 0x0004 +UHCI_INTR_TIMEOUT: t.CDefine = 0x0001 +UHCI_INTR_RESUME: t.CDefine = 0x0002 +UHCI_INTR_IOC: t.CDefine = 0x0004 +UHCI_INTR_SHORT: t.CDefine = 0x0008 + +UHCI_PORT_CONNECT: t.CDefine = 0x0001 +UHCI_PORT_CONNECT_CHANGE: t.CDefine = 0x0002 +UHCI_PORT_ENABLE: t.CDefine = 0x0004 +UHCI_PORT_ENABLE_CHANGE: t.CDefine = 0x0008 +UHCI_PORT_RESET: t.CDefine = 0x0200 +UHCI_PORT_LOW_SPEED: t.CDefine = 0x0100 + +TD_CTRL_ACTIVE: t.CDefine = 0x00800000 +TD_CTRL_IOC: t.CDefine = 0x01000000 +TD_CTRL_LS: t.CDefine = 0x04000000 +TD_CTRL_SPD: t.CDefine = 0x20000000 +TD_CTRL_CERR_SHIFT: t.CDefine = 27 +TD_CTRL_CERR_MASK: t.CDefine = 0x18000000 + +TD_TOKEN_SETUP: t.CDefine = 0x2D +TD_TOKEN_IN: t.CDefine = 0x69 +TD_TOKEN_OUT: t.CDefine = 0xE1 + +TD_TOKEN_DATA0: t.CDefine = 0 +TD_TOKEN_DATA1: t.CDefine = 1 + +TD_LINK_TERMINATE: t.CDefine = 0x00000001 +TD_LINK_QH: t.CDefine = 0x00000002 +TD_LINK_DEPTH_FIRST: t.CDefine = 0x00000004 + +UHCI_NUM_FRAMES: t.CDefine = 1024 +UHCI_INT_SLOTS: t.CDefine = 4 + +class uhci_td(t.CStruct): + link: t.CUInt32T + ctrl_status: t.CUInt32T + token: t.CUInt32T + buffer: t.CUInt32T + +class uhci_qh(t.CStruct): + link: t.CUInt32T + element: t.CUInt32T + +class int_slot(t.CStruct): + td: uhci_td | t.CPtr + callback: t.CInt | t.CPtr + active: t.CInt + qh: uhci_qh | t.CPtr + +_io_base: t.CUInt16T = 0 +_frame_list: t.CUInt32T | t.CPtr = None +_ctrl_qh: uhci_qh | t.CPtr = None +_bulk_qh: uhci_qh | t.CPtr = None +_irq: t.CInt = -1 +_int_slots: t.CArray[int_slot, UHCI_INT_SLOTS] +_int_slot_count: t.CInt = 0 + +def _reg_read16(offset: t.CUInt16T) -> t.CUInt16T: + return asm.inw(_io_base + offset) + +def _reg_write16(offset: t.CUInt16T, value: t.CUInt16T): + asm.outw(_io_base + offset, value) + +def _reg_read32(offset: t.CUInt16T) -> t.CUInt32T: + return asm.inl(_io_base + offset) + +def _reg_write32(offset: t.CUInt16T, value: t.CUInt32T): + asm.outl(_io_base + offset, value) + +def _uhci_irq_handler() -> t.CInt: + sts: t.CUInt16T = _reg_read16(UHCI_USBSTS) + _reg_write16(UHCI_USBSTS, sts) + if (sts & (UHCI_STS_USBINT | UHCI_STS_USBERRINT)) == 0: + return 0 + for si in range(_int_slot_count): + slot: int_slot | t.CPtr = c.Addr(_int_slots[si]) + if slot.active and slot.td is not None and slot.callback is not None: + status: t.CUInt32T = slot.td.ctrl_status + if (status & TD_CTRL_ACTIVE) == 0: + slot.active = 0 + c.Asm(f"""mov rbp, rsp + and rsp, -16 + call {c.AsmInp(slot.callback, t.ASM_DESCR.REG_ANY)} + mov rsp, rbp""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RBX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_RSI, t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11, + t.ASM_DESCR.CLOBBER_RBP]) + return 0 + +def register_int_callback(cb: t.CInt | t.CPtr) -> t.CInt: + global _int_slot_count + if _int_slot_count >= UHCI_INT_SLOTS: + return -1 + si: t.CInt = _int_slot_count + qh: uhci_qh | t.CPtr = mm.malloc_direct(uhci_qh.__sizeof__()) + if qh is None: + return -2 + string.memset(c.Addr(qh), 0, uhci_qh.__sizeof__()) + _qh_set_link(qh, 0, 0, 1) + _qh_set_element(qh, 0, 1) + td: uhci_td | t.CPtr = mm.malloc_direct(uhci_td.__sizeof__()) + if td is None: + return -3 + string.memset(c.Addr(td), 0, uhci_td.__sizeof__()) + slot: int_slot | t.CPtr = c.Addr(_int_slots[si]) + slot.td = td + slot.callback = cb + slot.active = 0 + slot.qh = qh + _int_slot_count = si + 1 + _rebuild_int_chain() + return si + +def schedule_int_transfer(slot_id: t.CInt, dev_addr: t.CUInt8T, endpoint: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, toggle_ptr: t.CUInt8T | t.CPtr, buf: t.CVoid | t.CPtr): + if slot_id < 0 or slot_id >= _int_slot_count: + return + slot: int_slot | t.CPtr = c.Addr(_int_slots[slot_id]) + td: uhci_td | t.CPtr = slot.td + qh: uhci_qh | t.CPtr = slot.qh + if td is None or qh is None: + return + string.memset(c.Addr(td), 0, uhci_td.__sizeof__()) + _td_set_link(td, 0, 0, 1) + _td_set_ctrl(td, ls, max_pkt) + _td_set_token(td, TD_TOKEN_IN, dev_addr, endpoint, toggle_ptr, max_pkt) + td.ctrl_status = td.ctrl_status | TD_CTRL_IOC + td.buffer = t.CUInt32T(t.CUInt64T(buf)) + _qh_set_element(qh, t.CUInt32T(t.CUInt64T(td)), 0) + slot.active = 1 + +def _rebuild_int_chain(): + if _int_slot_count == 0: + return + first_qh: uhci_qh | t.CPtr = None + prev_qh: uhci_qh | t.CPtr = None + for si in range(_int_slot_count): + cur_qh: uhci_qh | t.CPtr = _int_slots[si].qh + if cur_qh is not None: + if first_qh is None: + first_qh = cur_qh + if prev_qh is not None: + _qh_set_link(prev_qh, t.CUInt32T(t.CUInt64T(cur_qh)) | TD_LINK_QH, 1, 0) + prev_qh = cur_qh + if prev_qh is not None: + _qh_set_link(prev_qh, t.CUInt32T(t.CUInt64T(_ctrl_qh)) | TD_LINK_QH, 1, 0) + if first_qh is not None: + qh_addr: t.CUInt32T = t.CUInt32T(t.CUInt64T(first_qh)) | TD_LINK_QH + i: t.CInt + for i in range(UHCI_NUM_FRAMES): + entry_ptr: t.CUInt32T | t.CPtr = _frame_list + t.CUInt64T(i) * 4 + c.Set(c.Deref(entry_ptr), qh_addr) + +def _td_set_link(td: uhci_td | t.CPtr, addr: t.CUInt32T, is_qh: t.CInt, terminate: t.CInt): + val: t.CUInt32T = (addr & 0xFFFFFFF0) + if is_qh: val = val | TD_LINK_QH + if terminate: val = val | TD_LINK_TERMINATE + td.link = val + +def _td_set_token(td: uhci_td | t.CPtr, pid: t.CUInt8T, dev_addr: t.CUInt8T, endpoint: t.CUInt8T, toggle: t.CUInt8T, max_len: t.CUInt16T): + len_bits: t.CUInt32T + if max_len == 0: + len_bits = 0x7FF + else: + len_bits = t.CUInt32T(max_len) - 1 + td.token = (t.CUInt32T(pid) << 0) | (t.CUInt32T(dev_addr) << 8) | (t.CUInt32T(endpoint & 0x0F) << 15) | (t.CUInt32T(toggle & 1) << 19) | (len_bits << 21) + +def _td_set_ctrl(td: uhci_td | t.CPtr, ls: t.CInt, maxlen: t.CUInt16T): + td.ctrl_status = TD_CTRL_ACTIVE | (3 << TD_CTRL_CERR_SHIFT) + if ls: td.ctrl_status = td.ctrl_status | TD_CTRL_LS + +def _qh_set_link(qh: uhci_qh | t.CPtr, addr: t.CUInt32T, is_qh: t.CInt, terminate: t.CInt): + val: t.CUInt32T = (addr & 0xFFFFFFF0) + if is_qh: val = val | TD_LINK_QH + if terminate: val = val | TD_LINK_TERMINATE + qh.link = val + +def _qh_set_element(qh: uhci_qh | t.CPtr, addr: t.CUInt32T, terminate: t.CInt): + val: t.CUInt32T = (addr & 0xFFFFFFF0) + if terminate: val = val | TD_LINK_TERMINATE + qh.element = val + +def _wait_for_complete(td: uhci_td | t.CPtr, timeout_ms: t.CInt) -> t.CInt: + start: t.CUInt64T = timer.timer_get_ticks() + deadline: t.CUInt64T = start + t.CUInt64T(timeout_ms) + poll_cnt: t.CInt = 0 + while timer.timer_get_ticks() < deadline: + status: t.CUInt32T = td.ctrl_status + if (status & TD_CTRL_ACTIVE) == 0: + if (status & 0x00600000) != 0: + return -1 + return 0 + poll_cnt += 1 + if poll_cnt >= 100: + poll_cnt = 0 + asm.sti() + asm.hlt() + return -2 + +def reset(io_base: t.CUInt16T) -> t.CInt: + global _io_base + _io_base = io_base + _reg_write16(UHCI_USBCMD, UHCI_CMD_HCRESET) + i: t.CInt + for i in range(1000): + if (_reg_read16(UHCI_USBCMD) & UHCI_CMD_HCRESET) == 0: + break + _reg_write16(UHCI_USBSTS, 0xFFFF) + _reg_write16(UHCI_USBINTR, 0x0000) + return 0 + +def init(io_base: t.CUInt16T) -> t.CInt: + global _io_base, _frame_list, _ctrl_qh, _bulk_qh, _int_slot_count + _io_base = io_base + _int_slot_count = 0 + + serial.puts("[uhci] resetting...\n") + if reset(io_base) != 0: + serial.puts("[uhci] reset failed\n") + return -1 + + serial.puts("[uhci] allocating frame list...\n") + _frame_list = mm.malloc(UHCI_NUM_FRAMES * 4) + if _frame_list is None: + serial.puts("[uhci] frame list alloc failed\n") + return -2 + string.memset(_frame_list, 0, UHCI_NUM_FRAMES * 4) + + _ctrl_qh = mm.malloc_direct(uhci_qh.__sizeof__()) + if _ctrl_qh is None: + serial.puts("[uhci] ctrl_qh alloc failed\n") + return -3 + string.memset(c.Addr(_ctrl_qh), 0, uhci_qh.__sizeof__()) + _qh_set_link(_ctrl_qh, 0, 0, 1) + _qh_set_element(_ctrl_qh, 0, 1) + + _bulk_qh = mm.malloc_direct(uhci_qh.__sizeof__()) + if _bulk_qh is None: + serial.puts("[uhci] bulk_qh alloc failed\n") + return -4 + string.memset(c.Addr(_bulk_qh), 0, uhci_qh.__sizeof__()) + _qh_set_link(_bulk_qh, 0, 0, 1) + _qh_set_element(_bulk_qh, 0, 1) + + serial.puts("[uhci] setting up frame list...\n") + qh_addr: t.CUInt32T = t.CUInt32T(c.Addr(_ctrl_qh)) | TD_LINK_QH + i: t.CInt + for i in range(UHCI_NUM_FRAMES): + entry_ptr: t.CUInt32T | t.CPtr = _frame_list + t.CUInt64T(i) * 4 + c.Set(c.Deref(entry_ptr), qh_addr) + + _reg_write32(UHCI_FLBASEADD, t.CUInt32T(t.CUInt64T(_frame_list))) + _reg_write16(UHCI_FRNUM, 0) + _reg_write16(UHCI_SOFMOD, 64) + + _reg_write16(UHCI_USBCMD, UHCI_CMD_CF | UHCI_CMD_MAXPKT | UHCI_CMD_RUN) + + timer.timer_msleep(10) + + _reg_write16(UHCI_USBSTS, 0xFFFF) + _reg_write16(UHCI_USBINTR, UHCI_INTR_IOC | UHCI_INTR_SHORT) + + _irq = t.CInt(pci._uhci_dev.irq) + if _irq > 0 and _irq < 16: + idt.irqInstallHandler(_irq, _uhci_irq_handler, "uhci") + pic.clearMask(t.CUInt8T(_irq)) + + serial.puts("[uhci] started\n") + return 0 + +def port_reset(port: t.CInt) -> t.CInt: + sc: t.CUInt16T + if port == 0: + sc = _reg_read16(UHCI_PORTSC1) + else: + sc = _reg_read16(UHCI_PORTSC2) + + if (sc & UHCI_PORT_CONNECT) == 0: + return -1 + + if port == 0: + _reg_write16(UHCI_PORTSC1, sc | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE) + else: + _reg_write16(UHCI_PORTSC2, sc | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE) + + if port == 0: + _reg_write16(UHCI_PORTSC1, UHCI_PORT_RESET) + else: + _reg_write16(UHCI_PORTSC2, UHCI_PORT_RESET) + + timer.timer_msleep(50) + + if port == 0: + _reg_write16(UHCI_PORTSC1, 0x0000) + else: + _reg_write16(UHCI_PORTSC2, 0x0000) + + timer.timer_msleep(10) + + if port == 0: + sc = _reg_read16(UHCI_PORTSC1) + _reg_write16(UHCI_PORTSC1, sc | UHCI_PORT_ENABLE | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE) + else: + sc = _reg_read16(UHCI_PORTSC2) + _reg_write16(UHCI_PORTSC2, sc | UHCI_PORT_ENABLE | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE) + + timer.timer_msleep(10) + + if port == 0: + sc = _reg_read16(UHCI_PORTSC1) + else: + sc = _reg_read16(UHCI_PORTSC2) + + if (sc & UHCI_PORT_ENABLE) == 0: + return -2 + + return 0 + +def port_is_connected(port: t.CInt) -> t.CInt: + sc: t.CUInt16T + if port == 0: + sc = _reg_read16(UHCI_PORTSC1) + else: + sc = _reg_read16(UHCI_PORTSC2) + if sc & UHCI_PORT_CONNECT: return 1 + return 0 + +def port_is_low_speed(port: t.CInt) -> t.CInt: + sc: t.CUInt16T + if port == 0: + sc = _reg_read16(UHCI_PORTSC1) + else: + sc = _reg_read16(UHCI_PORTSC2) + if sc & UHCI_PORT_LOW_SPEED: return 1 + return 0 + +def control_transfer(dev_addr: t.CUInt8T, endpoint: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, setup_buf: t.CVoid | t.CPtr, data_buf: t.CVoid | t.CPtr, data_len: t.CUInt16T, data_in: t.CInt) -> t.CInt: + td_count: t.CInt = 2 + if data_len > 0: + td_count = td_count + 1 + total: t.CUInt64T = t.CUInt64T(td_count) * uhci_td.__sizeof__() + tds: uhci_td | t.CPtr = mm.malloc_direct(total) + if tds is None: return -1 + string.memset(c.Addr(tds), 0, total) + + setup_td: uhci_td | t.CPtr = tds + _td_set_link(setup_td, t.CUInt32T(t.CUInt64T(tds) + uhci_td.__sizeof__()), 0, 0) + _td_set_ctrl(setup_td, ls, 8) + _td_set_token(setup_td, TD_TOKEN_SETUP, dev_addr, endpoint, TD_TOKEN_DATA0, 8) + setup_td.buffer = t.CUInt32T(t.CUInt64T(setup_buf)) + + if data_len > 0: + data_td: uhci_td | t.CPtr = t.CUInt64T(tds) + uhci_td.__sizeof__() + _td_set_link(data_td, t.CUInt32T(t.CUInt64T(data_td) + uhci_td.__sizeof__()), 0, 0) + _td_set_ctrl(data_td, ls, max_pkt) + if data_in: + _td_set_token(data_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, data_len) + else: + _td_set_token(data_td, TD_TOKEN_OUT, dev_addr, endpoint, TD_TOKEN_DATA1, data_len) + data_td.buffer = t.CUInt32T(t.CUInt64T(data_buf)) + + status_td: uhci_td | t.CPtr = t.CUInt64T(data_td) + uhci_td.__sizeof__() + _td_set_link(status_td, 0, 0, 1) + _td_set_ctrl(status_td, ls, max_pkt) + if data_in: + _td_set_token(status_td, TD_TOKEN_OUT, dev_addr, endpoint, TD_TOKEN_DATA1, 0) + else: + _td_set_token(status_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, 0) + status_td.buffer = 0 + else: + status_td: uhci_td | t.CPtr = t.CUInt64T(tds) + uhci_td.__sizeof__() + _td_set_link(status_td, 0, 0, 1) + _td_set_ctrl(status_td, ls, max_pkt) + _td_set_token(status_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, 0) + status_td.buffer = 0 + + _qh_set_element(_ctrl_qh, t.CUInt32T(t.CUInt64T(setup_td)), 0) + + result: t.CInt = _wait_for_complete(status_td, 500) + + _qh_set_element(_ctrl_qh, 0, 1) + mm.free(tds) + return result diff --git a/VKernel/Kernel/drivers/usb/usb.py b/VKernel/Kernel/drivers/usb/usb.py new file mode 100644 index 0000000..d0f8d47 --- /dev/null +++ b/VKernel/Kernel/drivers/usb/usb.py @@ -0,0 +1,330 @@ +import t, c +import mm.mm as mm +import viperstring as string +import drivers.serial.uart.serial as serial +import drivers.usb.uhci as uhci +import drivers.usb.pci as pci +import platform.pch.timer as timer +import viperlib + +USB_DESC_DEVICE: t.CDefine = 1 +USB_DESC_CONFIG: t.CDefine = 2 +USB_DESC_STRING: t.CDefine = 3 +USB_DESC_INTERFACE: t.CDefine = 4 +USB_DESC_ENDPOINT: t.CDefine = 5 + +USB_REQ_GET_STATUS: t.CDefine = 0 +USB_REQ_CLEAR_FEATURE: t.CDefine = 1 +USB_REQ_SET_FEATURE: t.CDefine = 3 +USB_REQ_SET_ADDRESS: t.CDefine = 5 +USB_REQ_GET_DESCRIPTOR: t.CDefine = 6 +USB_REQ_SET_DESCRIPTOR: t.CDefine = 7 +USB_REQ_GET_CONFIGURATION: t.CDefine = 8 +USB_REQ_SET_CONFIGURATION: t.CDefine = 9 + +USB_REQ_SET_INTERFACE: t.CDefine = 11 + +HID_CLASS: t.CDefine = 0x03 +HID_SUBCLASS_BOOT: t.CDefine = 0x01 +HID_PROTO_KEYBOARD: t.CDefine = 0x01 +HID_PROTO_MOUSE: t.CDefine = 0x02 + +HID_REQ_SET_PROTOCOL: t.CDefine = 0x0B +HID_REQ_SET_IDLE: t.CDefine = 0x0A +HID_REQ_GET_REPORT: t.CDefine = 0x01 + +USB_MAX_DEVICES: t.CDefine = 8 + +class usb_dev_desc(t.CStruct): + bLength: t.CUInt8T + bDescriptorType: t.CUInt8T + bcdUSB: t.CUInt16T + bDeviceClass: t.CUInt8T + bDeviceSubClass: t.CUInt8T + bDeviceProtocol: t.CUInt8T + bMaxPacketSize0: t.CUInt8T + idVendor: t.CUInt16T + idProduct: t.CUInt16T + bcdDevice: t.CUInt16T + iManufacturer: t.CUInt8T + iProduct: t.CUInt8T + iSerialNumber: t.CUInt8T + bNumConfigurations: t.CUInt8T + +class usb_config_desc(t.CStruct): + bLength: t.CUInt8T + bDescriptorType: t.CUInt8T + wTotalLength: t.CUInt16T + bNumInterfaces: t.CUInt8T + bConfigurationValue: t.CUInt8T + iConfiguration: t.CUInt8T + bmAttributes: t.CUInt8T + bMaxPower: t.CUInt8T + +class usb_iface_desc(t.CStruct): + bLength: t.CUInt8T + bDescriptorType: t.CUInt8T + bInterfaceNumber: t.CUInt8T + bAlternateSetting: t.CUInt8T + bNumEndpoints: t.CUInt8T + bInterfaceClass: t.CUInt8T + bInterfaceSubClass: t.CUInt8T + bInterfaceProtocol: t.CUInt8T + iInterface: t.CUInt8T + +class usb_ep_desc(t.CStruct): + bLength: t.CUInt8T + bDescriptorType: t.CUInt8T + bEndpointAddress: t.CUInt8T + bmAttributes: t.CUInt8T + wMaxPacketSize: t.CUInt16T + bInterval: t.CUInt8T + +class usb_setup_pkt(t.CStruct): + bmRequestType: t.CUInt8T + bRequest: t.CUInt8T + wValue: t.CUInt16T + wIndex: t.CUInt16T + wLength: t.CUInt16T + +class usb_device(t.CStruct): + addr: t.CUInt8T + ls: t.CInt + max_pkt0: t.CUInt8T + iface_class: t.CUInt8T + iface_subclass: t.CUInt8T + iface_protocol: t.CUInt8T + iface_num: t.CUInt8T + ep_in: t.CUInt8T + ep_in_max: t.CUInt16T + ep_in_interval: t.CUInt8T + ep_in_toggle: t.CUInt8T + configured: t.CInt + +_devices: t.CArray[usb_device, USB_MAX_DEVICES] +_device_count: t.CInt = 0 + +def _setup_packet(bmRequestType: t.CUInt8T, bRequest: t.CUInt8T, wValue: t.CUInt16T, wIndex: t.CUInt16T, wLength: t.CUInt16T, buf: usb_setup_pkt | t.CPtr): + buf.bmRequestType = bmRequestType + buf.bRequest = bRequest + buf.wValue = wValue + buf.wIndex = wIndex + buf.wLength = wLength + +def _send_control(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, setup: usb_setup_pkt | t.CPtr, data_buf: t.CVoid | t.CPtr, data_len: t.CUInt16T, data_in: t.CInt) -> t.CInt: + return uhci.control_transfer(dev_addr, 0, ls, max_pkt, c.Addr(setup), data_buf, data_len, data_in) + +def get_descriptor(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, desc_type: t.CUInt8T, desc_idx: t.CUInt8T, buf: t.CVoid | t.CPtr, length: t.CUInt16T) -> t.CInt: + setup: usb_setup_pkt + _setup_packet(0x80, USB_REQ_GET_DESCRIPTOR, (t.CUInt16T(desc_type) << 8) | desc_idx, 0, length, c.Addr(setup)) + return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), buf, length, 1) + +def set_address(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, new_addr: t.CUInt8T) -> t.CInt: + setup: usb_setup_pkt + _setup_packet(0x00, USB_REQ_SET_ADDRESS, new_addr, 0, 0, c.Addr(setup)) + return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0) + +def set_configuration(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, config_val: t.CUInt8T) -> t.CInt: + setup: usb_setup_pkt + _setup_packet(0x00, USB_REQ_SET_CONFIGURATION, config_val, 0, 0, c.Addr(setup)) + return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0) + +def hid_set_protocol(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, iface: t.CUInt8T, protocol: t.CUInt8T) -> t.CInt: + setup: usb_setup_pkt + _setup_packet(0x21, HID_REQ_SET_PROTOCOL, protocol, t.CUInt16T(iface), 0, c.Addr(setup)) + return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0) + +def hid_set_idle(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, iface: t.CUInt8T, duration: t.CUInt8T) -> t.CInt: + setup: usb_setup_pkt + _setup_packet(0x21, HID_REQ_SET_IDLE, t.CUInt16T(duration) << 8, t.CUInt16T(iface), 0, c.Addr(setup)) + return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0) + +def _parse_config(dev: usb_device | t.CPtr, config_buf: t.CVoid | t.CPtr, total_len: t.CUInt16T): + offset: t.CInt = 0 + found_hid: t.CInt = 0 + while offset < t.CInt(total_len): + p: t.CUInt8T | t.CPtr = config_buf + t.CUInt64T(offset) + desc_len: t.CUInt8T = c.Deref(p) + p2: t.CUInt8T | t.CPtr = config_buf + t.CUInt64T(offset) + 1 + desc_type: t.CUInt8T = c.Deref(p2) + if desc_len == 0: break + if desc_type == USB_DESC_INTERFACE: + iface: usb_iface_desc | t.CPtr = config_buf + t.CUInt64T(offset) + if iface.bInterfaceClass == HID_CLASS: + found_hid = 1 + dev.iface_class = HID_CLASS + dev.iface_subclass = iface.bInterfaceSubClass + dev.iface_protocol = iface.bInterfaceProtocol + dev.iface_num = iface.bInterfaceNumber + if desc_type == USB_DESC_ENDPOINT and found_hid: + ep: usb_ep_desc | t.CPtr = config_buf + t.CUInt64T(offset) + if (ep.bEndpointAddress & 0x80) != 0: + dev.ep_in = ep.bEndpointAddress & 0x0F + dev.ep_in_max = ep.wMaxPacketSize + dev.ep_in_interval = ep.bInterval + found_hid = 0 + offset = offset + t.CInt(desc_len) + +def enumerate_port(port: t.CInt) -> t.CInt: + global _device_count + if _device_count >= USB_MAX_DEVICES: return -1 + + ls: t.CInt = uhci.port_is_low_speed(port) + + reset_ok: t.CInt = -1 + reset_retry: t.CInt + for reset_retry in range(3): + if uhci.port_reset(port) == 0: + reset_ok = 0 + break + timer.timer_msleep(50) + if reset_ok != 0: + return -2 + + timer.timer_msleep(10) + + dev_buf: t.CVoid | t.CPtr = mm.malloc(usb_dev_desc.__sizeof__()) + if dev_buf is None: return -3 + string.memset(dev_buf, 0, usb_dev_desc.__sizeof__()) + + result: t.CInt = get_descriptor(0, ls, 8, USB_DESC_DEVICE, 0, dev_buf, 8) + retry: t.CInt + for retry in range(3): + if result == 0: break + timer.timer_msleep(50) + uhci.port_reset(port) + timer.timer_msleep(10) + result = get_descriptor(0, ls, 8, USB_DESC_DEVICE, 0, dev_buf, 8) + if result != 0: + mm.free(dev_buf) + return -4 + + desc: usb_dev_desc | t.CPtr = dev_buf + max_pkt: t.CUInt8T = desc.bMaxPacketSize0 + if max_pkt < 8: max_pkt = 8 + + new_addr: t.CUInt8T = t.CUInt8T(_device_count + 1) + result = set_address(0, ls, max_pkt, new_addr) + for retry in range(3): + if result == 0: break + timer.timer_msleep(20) + result = set_address(0, ls, max_pkt, new_addr) + if result != 0: + mm.free(dev_buf) + return -5 + + timer.timer_msleep(10) + + dev: usb_device | t.CPtr = c.Addr(_devices[_device_count]) + dev.addr = new_addr + dev.ls = ls + dev.max_pkt0 = max_pkt + dev.configured = 0 + dev.iface_class = 0 + dev.iface_subclass = 0 + dev.iface_protocol = 0 + dev.ep_in = 0 + dev.ep_in_max = 0 + dev.ep_in_interval = 0 + dev.ep_in_toggle = 0 + + config_buf: t.CVoid | t.CPtr = mm.malloc(256) + if config_buf is None: + mm.free(dev_buf) + return -6 + string.memset(config_buf, 0, 256) + + result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, 9) + 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, 9) + if result != 0: + mm.free(config_buf) + mm.free(dev_buf) + return -7 + + cfg: usb_config_desc | t.CPtr = config_buf + total_len: t.CUInt16T = 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) + + result = set_configuration(new_addr, ls, max_pkt, cfg.bConfigurationValue) + if result != 0: + mm.free(config_buf) + mm.free(dev_buf) + return -9 + + dev.configured = 1 + + if dev.iface_class == HID_CLASS: + hid_set_protocol(new_addr, ls, max_pkt, dev.iface_num, 0) + hid_set_idle(new_addr, ls, max_pkt, dev.iface_num, 0) + + _device_count = _device_count + 1 + mm.free(config_buf) + mm.free(dev_buf) + return t.CInt(new_addr) + +def init() -> t.CInt: + global _device_count + _device_count = 0 + + io_base: t.CUInt16T = pci.find_uhci() + if io_base == 0: + serial.puts("[usb] UHCI controller not found\n") + return -1 + + buf: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(buf), 64, "[usb] UHCI at I/O 0x%04X\n", t.CUInt32T(io_base)) + serial.puts(buf) + + if uhci.init(io_base) != 0: + serial.puts("[usb] UHCI init failed\n") + return -2 + + serial.puts("[usb] UHCI initialized\n") + + timer.timer_msleep(50) + + port: t.CInt + for port in range(2): + if uhci.port_is_connected(port): + viperlib.snprintf(c.Addr(buf), 64, "[usb] port %d connected\n", t.CUInt32T(port)) + serial.puts(buf) + dev_id: t.CInt = enumerate_port(port) + if dev_id > 0: + dev: usb_device | t.CPtr = c.Addr(_devices[dev_id - 1]) + viperlib.snprintf(c.Addr(buf), 64, "[usb] device addr=%d class=%02X proto=%02X ep=%d\n", + t.CUInt32T(dev.addr), t.CUInt32T(dev.iface_class), + t.CUInt32T(dev.iface_protocol), t.CUInt32T(dev.ep_in)) + serial.puts(buf) + else: + viperlib.snprintf(c.Addr(buf), 64, "[usb] enumerate port %d failed: %d\n", t.CUInt32T(port), t.CInt32T(dev_id)) + serial.puts(buf) + + return _device_count + +def find_hid_device(protocol: t.CUInt8T) -> usb_device | t.CPtr: + i: t.CInt + for i in range(_device_count): + dev: usb_device | t.CPtr = c.Addr(_devices[i]) + if dev.iface_class == HID_CLASS: + if dev.iface_protocol == protocol: + if dev.configured != 0: + return dev + return None + +def read_interrupt(dev: usb_device | t.CPtr, buf: t.CVoid | t.CPtr) -> t.CInt: + return 0 diff --git a/VKernel/Kernel/drivers/video/ui/sheet.py b/VKernel/Kernel/drivers/video/ui/sheet.py new file mode 100644 index 0000000..7b70613 --- /dev/null +++ b/VKernel/Kernel/drivers/video/ui/sheet.py @@ -0,0 +1,85 @@ +from stdint import * +import drivers.video.vesafb.gfx as gfx +import t, c + +MAX_SHEETS: t.CDefine = 64 +MAX_UI_COMPONENTS: t.CDefine = 256 + +_next_sheet_id: t.CInt = 0 +_next_component_id: t.CInt = 0 + +@t.Object +class Sheet: + id: t.CInt + x: t.CInt + y: t.CInt + w: t.CInt + h: t.CInt + visible: t.CInt + _surf_obj: gfx.Surface + _renderer_obj: gfx.Renderer + surf: gfx.Surface | t.CPtr + renderer: gfx.Renderer | t.CPtr + parent: 'Sheet | t.CPtr' + bg_color: t.CUInt32T + + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, parent: 'Sheet | t.CPtr' = None): + global _next_sheet_id + self.id = _next_sheet_id + _next_sheet_id += 1 + self.x = x + self.y = y + self.w = w + self.h = h + self.visible = 1 + self._surf_obj = gfx.Surface(fb, zb, w, h) + self.surf = c.Addr(self._surf_obj) + self._renderer_obj = gfx.Renderer(self.surf) + self.renderer = c.Addr(self._renderer_obj) + self.parent = parent + self.bg_color = gfx.COLOR_RGB(20, 20, 30) + + def MoveTo(self, nx: t.CInt, ny: t.CInt): + self.x = nx + self.y = ny + + def Resize(self, nw: t.CInt, nh: t.CInt): + self.w = nw + self.h = nh + + def Show(self): + self.visible = 1 + + def Hide(self): + self.visible = 0 + + def Clear(self): + r: gfx.Renderer = c.Deref(self.renderer) + r.Clear(self.bg_color) + + def Fill(self, color: t.CUInt32T): + r: gfx.Renderer = c.Deref(self.renderer) + r.Clear(color) + + def DrawRect(self, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T): + r: gfx.Renderer = c.Deref(self.renderer) + r.DrawLine2D(rx, ry, rx + rw - 1, ry, color) + r.DrawLine2D(rx + rw - 1, ry, rx + rw - 1, ry + rh - 1, color) + r.DrawLine2D(rx + rw - 1, ry + rh - 1, rx, ry + rh - 1, color) + r.DrawLine2D(rx, ry + rh - 1, rx, ry, color) + + def DrawFilledRect(self, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T): + surf: gfx.Surface = c.Deref(self.surf) + fb: UINT32PTR = surf.fb + w: t.CInt = surf.w + h: t.CInt = surf.h + for py in range(ry, ry + rh): + if py < 0 or py >= h: continue + for px in range(rx, rx + rw): + if px < 0 or px >= w: continue + fb[py * w + px] = color + + def Contains(self, px: t.CInt, py: t.CInt) -> t.CInt: + if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h: + return 1 + return 0 diff --git a/VKernel/Kernel/drivers/video/ui/ui.py b/VKernel/Kernel/drivers/video/ui/ui.py new file mode 100644 index 0000000..d3ed8ef --- /dev/null +++ b/VKernel/Kernel/drivers/video/ui/ui.py @@ -0,0 +1,152 @@ +from stdint import * +import drivers.video.vesafb.gfx as gfx +import drivers.video.ui.sheet as sheet +import string +import t, c + +UI_LABEL: t.CDefine = 0 +UI_BUTTON: t.CDefine = 1 +UI_PANEL: t.CDefine = 2 +UI_PROGRESS: t.CDefine = 3 + +@t.Object +class UIComponent: + id: t.CInt + ctype: t.CInt + x: t.CInt + y: t.CInt + w: t.CInt + h: t.CInt + visible: t.CInt + sheet: sheet.Sheet | t.CPtr + fg_color: t.CUInt32T + bg_color: t.CUInt32T + text: t.CArray[t.CChar, 128] + text_len: t.CInt + value: t.CInt + + def __init__(self, ctype: t.CInt, sh: sheet.Sheet | t.CPtr, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt): + global _next_component_id + self.id = _next_component_id + _next_component_id += 1 + self.ctype = ctype + self.x = x + self.y = y + self.w = w + self.h = h + self.visible = 1 + self.sheet = sh + self.fg_color = gfx.COLOR_RGB(255, 255, 255) + self.bg_color = gfx.COLOR_RGB(40, 40, 60) + string.memset(c.Addr(self.text), 0, 128) + self.text_len = 0 + self.value = 0 + + def SetText(self, s: str): + i: t.CInt = 0 + for ch in s: + if i >= 127: break + self.text[i] = ch + i += 1 + self.text[i] = 0 + self.text_len = i + + def SetValue(self, v: t.CInt): + self.value = v + + def Render(self): + if self.visible == 0: return + sh: sheet.Sheet = c.Deref(self.sheet) + match self.ctype: + case 0: + self._RenderLabel(sh) + case 1: + self._RenderButton(sh) + case 2: + self._RenderPanel(sh) + case 3: + self._RenderProgress(sh) + + def _RenderLabel(self, sh: sheet.Sheet): + sh.DrawFilledRect(self.x, self.y, self.w, self.h, 0) + + def _RenderButton(self, sh: sheet.Sheet): + sh.DrawFilledRect(self.x, self.y, self.w, self.h, self.bg_color) + sh.DrawRect(self.x, self.y, self.w, self.h, self.fg_color) + + def _RenderPanel(self, sh: sheet.Sheet): + sh.DrawFilledRect(self.x, self.y, self.w, self.h, self.bg_color) + sh.DrawRect(self.x, self.y, self.w, self.h, gfx.COLOR_RGB(80, 80, 120)) + + def _RenderProgress(self, sh: sheet.Sheet): + sh.DrawFilledRect(self.x, self.y, self.w, self.h, gfx.COLOR_RGB(30, 30, 40)) + fill_w: t.CInt = self.w * self.value / 100 + if fill_w > 0: + sh.DrawFilledRect(self.x, self.y, fill_w, self.h, gfx.COLOR_RGB(0, 180, 255)) + sh.DrawRect(self.x, self.y, self.w, self.h, gfx.COLOR_RGB(80, 80, 100)) + + def Contains(self, px: t.CInt, py: t.CInt) -> t.CInt: + if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h: + return 1 + return 0 + + +@t.Object +class UIManager: + sheets: t.CArray[sheet.Sheet | t.CPtr, 64] + sheet_count: t.CInt + components: t.CArray[UIComponent | t.CPtr, 256] + component_count: t.CInt + screen_w: t.CInt + screen_h: t.CInt + + def __init__(self, w: t.CInt, h: t.CInt): + self.sheet_count = 0 + self.component_count = 0 + self.screen_w = w + self.screen_h = h + i: t.CInt + for i in range(64): + self.sheets[i] = None + for i in range(256): + self.components[i] = None + + def AddSheet(self, sh: sheet.Sheet | t.CPtr) -> t.CInt: + if self.sheet_count >= 64: return -1 + self.sheets[self.sheet_count] = sh + self.sheet_count += 1 + return 0 + + def AddComponent(self, comp: UIComponent | t.CPtr) -> t.CInt: + if self.component_count >= 256: return -1 + self.components[self.component_count] = comp + self.component_count += 1 + return 0 + + def FindComponent(self, cid: t.CInt) -> UIComponent | t.CPtr: + i: t.CInt + for i in range(self.component_count): + p: UIComponent | t.CPtr = self.components[i] + if p is not None: + comp: UIComponent = c.Deref(p) + if comp.id == cid: return p + return None + + def RenderAll(self): + i: t.CInt + for i in range(self.component_count): + p: UIComponent | t.CPtr = self.components[i] + if p is not None: + comp: UIComponent = c.Deref(p) + comp.Render() + + def HitTest(self, px: t.CInt, py: t.CInt) -> UIComponent | t.CPtr: + i: t.CInt = self.component_count - 1 + while i >= 0: + p: UIComponent | t.CPtr = self.components[i] + if p is not None: + comp: UIComponent = c.Deref(p) + if comp.visible == 1 and comp.Contains(px, py) == 1: + return p + i -= 1 + return None diff --git a/VKernel/Kernel/drivers/video/vesafb/gfx.py b/VKernel/Kernel/drivers/video/vesafb/gfx.py new file mode 100644 index 0000000..5292d3f --- /dev/null +++ b/VKernel/Kernel/drivers/video/vesafb/gfx.py @@ -0,0 +1,305 @@ +from stdint import * +import vipermath +import string +import t, c + + +PI: t.CDefine = float(3.14159265358979323846) + +SPHERE_SLICES: t.CDefine = (24 * 1) +SPHERE_STACKS: t.CDefine = (16 * 1) +SIN_WAVE_GRID: t.CDefine = (40 * 1) + +def COLOR_RGB(r: int, g: int, b: int) -> t.CInline | t.CInt: + return (255 << 24) | (r << 16) | (g << 8) | (b) + +@t.Object +class Vec3: + x: float + y: float + z: float + def __init__(self, x: float, y: float, z: float): + self.x = x + self.y = y + self.z = z + def Sub(self, b: 'Vec3') -> 'Vec3': + return Vec3(self.x - b.x, self.y - b.y, self.z - b.z) + def Cross(self, b: 'Vec3') -> 'Vec3': + return Vec3(self.y * b.z - self.z * b.y, self.z * b.x - self.x * b.z, self.x * b.y - self.y * b.x) + def Dot(self, b: 'Vec3') -> float: + return self.x * b.x + self.y * b.y + self.z * b.z + def Norm(self) -> 'Vec3': + l = vipermath.sqrtf(self.Dot(self)) + return Vec3(self.x / l, self.y / l, self.z / l) + +class Mat4: + m: t.CArray[t.CArray[float, 4], 4] + +def Mat4_Identity() -> Mat4: + m: Mat4 = Mat4() + string.memset(c.Addr(m), 0, Mat4.__sizeof__()) + m.m[0][0] = 1; m.m[1][1] = 1; m.m[2][2] = 1; m.m[3][3] = 1 + return m + +def Mat4_Mul(a: Mat4, b: Mat4) -> Mat4: + res: Mat4 = Mat4() + string.memset(c.Addr(res), 0, Mat4.__sizeof__()) + for i in range(4): + for j in range(4): + for k in range(4): + res.m[i][j] += a.m[i][k] * b.m[k][j] + return res + +def Mat4_MulVec(m: Mat4, v: Vec3) -> Vec3: + x: float = m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z + m.m[0][3] + y: float = m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z + m.m[1][3] + z: float = m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z + m.m[2][3] + w: float = m.m[3][0] * v.x + m.m[3][1] * v.y + m.m[3][2] * v.z + m.m[3][3] + if w != 0: + x /= w; y /= w; z /= w + return Vec3(x, y, z) + +def Mat4_Perspective(fov: float, aspect: float, zn: float, zf: float) -> Mat4: + m: Mat4 = Mat4() + string.memset(c.Addr(m), 0, Mat4.__sizeof__()) + f: float = float(1.0) / vipermath.tanf(fov / float(2.0)) + m.m[0][0] = f / aspect; m.m[1][1] = f + m.m[2][2] = zf / (zn - zf); m.m[2][3] = (zf * zn) / (zn - zf); m.m[3][2] = -1 + return m + +def Mat4_LookAt(eye: Vec3, target: Vec3, up: Vec3) -> Mat4: + zaxis: Vec3 = eye.Sub(target).Norm() + xaxis: Vec3 = up.Cross(zaxis).Norm() + yaxis: Vec3 = zaxis.Cross(xaxis) + m: Mat4 = Mat4_Identity() + m.m[0][0]=xaxis.x; m.m[0][1]=xaxis.y; m.m[0][2]=xaxis.z; m.m[0][3]= -xaxis.Dot(eye) + m.m[1][0]=yaxis.x; m.m[1][1]=yaxis.y; m.m[1][2]=yaxis.z; m.m[1][3]= -yaxis.Dot(eye) + m.m[2][0]=zaxis.x; m.m[2][1]=zaxis.y; m.m[2][2]=zaxis.z; m.m[2][3]= -zaxis.Dot(eye) + m.m[3][3] = 1 + return m + +def Mat4_Translate(x: float, y: float, z: float) -> Mat4: + m: Mat4 = Mat4_Identity() + m.m[0][3] = x; m.m[1][3] = y; m.m[2][3] = z + return m + +def Mat4_RotateY(a: float) -> Mat4: + m: Mat4 = Mat4_Identity(); + m.m[0][0] = vipermath.cosf(a); m.m[0][2] = vipermath.sinf(a) + m.m[2][0] = -vipermath.sinf(a); m.m[2][2] = vipermath.cosf(a) + return m + + +@t.Object +class Surface: + fb: UINT32PTR + zb: float | t.CPtr + w: int + h: int + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int): + self.fb = fb + self.zb = zb + self.w = w + self.h = h + +@t.Object +class Renderer: + _fb: UINT32PTR + _zb: float | t.CPtr + _w: int + _h: int + + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int): + self._fb = fb + self._zb = zb + self._w = w + self._h = h + + def Clear(self, color: t.CUInt32T): + fb: UINT32PTR = self._fb + zb: float | t.CPtr = self._zb + size: int = self._w * self._h + for i in range(size): + fb[i] = color + zb[i] = float(1.0) + + def DrawLine2D(self, x0: int, y0: int, x1: int, y1: int, col: t.CUInt32T): + fb: UINT32PTR = self._fb + w: int = self._w; h: int = self._h + dx: int = abs(x1-x0); dy: int = abs(y1-y0) + sx: int = 1 if x0 < x1 else -1; sy: int = 1 if y0 < y1 else -1 + err: int = dx - dy + while True: + if x0 >= 0 and x0 < w and y0 >= 0 and y0 < h: fb[y0 * w + x0] = col + if x0 == x1 and y0 == y1: break + e2: int = 2 * err + if e2 > -dy: err -= dy; x0 += sx + if e2 < dx: err += dx; y0 += sy + + def DrawTriangle(self, v0: Vec3, v1: Vec3, v2: Vec3, col: t.CUInt32T): + if v0.z < float(0.0) or v1.z < float(0.0) or v2.z < float(0.0): return + if (v0.x > float(10.0) or v0.x < float(-10.0) or v0.y > float(10.0) or v0.y < float(-10.0) or + v1.x > float(10.0) or v1.x < float(-10.0) or v1.y > float(10.0) or v1.y < float(-10.0) or + v2.x > float(10.0) or v2.x < float(-10.0) or v2.y > float(10.0) or v2.y < float(-10.0)): return + + fb: UINT32PTR = self._fb + zb: float | t.CPtr = self._zb + w: int = self._w + h: int = self._h + + sx0: float = (v0.x + float(1.0)) * float(0.5) * w; sy0: float = (float(1.0) - v0.y) * float(0.5) * h + sx1: float = (v1.x + float(1.0)) * float(0.5) * w; sy1: float = (float(1.0) - v1.y) * float(0.5) * h + sx2: float = (v2.x + float(1.0)) * float(0.5) * w; sy2: float = (float(1.0) - v2.y) * float(0.5) * h + + minX: int = max(0, int(vipermath.floorf(min(min(sx0, sx1), sx2)))) + maxX: int = min(w - 1, int(vipermath.ceilf(max(max(sx0, sx1), sx2)))) + minY: int = max(0, int(vipermath.floorf(min(min(sy0, sy1), sy2)))) + maxY: int = min(h - 1, int(vipermath.ceilf(max(max(sy0, sy1), sy2)))) + + dx12: float = sx1 - sx2; dy12: float = sy1 - sy2 + dx02: float = sx0 - sx2; dy02: float = sy0 - sy2 + denom: float = dx12 * dy02 - dy12 * dx02 + if denom < float(0.001) and denom > float(-0.001): return + invDenom: float = float(1.0) / denom + + row_dxP_start: float = float(minX) - sx2 + for y in range(minY, maxY + 1): + dyP: float = float(y) - sy2 + dxP: float = row_dxP_start + row_base: int = y * w + for x in range(minX, maxX + 1): + w0: float = (dx12 * dyP - dy12 * dxP) * invDenom + w1: float = (dxP * dy02 - dyP * dx02) * invDenom + w2: float = float(1.0) - w0 - w1 + if w0 >= 0 and w1 >= 0 and w2 >= 0: + z: float = w0 * v0.z + w1 * v1.z + w2 * v2.z + idx: int = row_base + x + if z < zb[idx]: + zb[idx] = z + fb[idx] = col + dxP += float(1.0) + + def DrawGlowDot(self, x: float, y: float, radius: float, core_col: t.CUInt32T, intensity: float): + fb: UINT32PTR = self._fb; w: int = self._w; h: int = self._h + r: int = int(radius) + 4 + ix: int = int(x); iy: int = int(y) + for dy in range(-r, r + 1): + py: int = iy + dy + if py < 0 or py >= h: continue + for dx in range(-r, r + 1): + px: int = ix + dx + if px < 0 or px >= w: continue + dist: float = vipermath.sqrtf(float(dx*dx + dy*dy)) + if dist < radius: + self.BlendPixel(px, py, core_col, intensity) + elif dist < float(r): + fade: float = 1.0 - (dist - radius) / 4.0 + self.BlendPixel(px, py, core_col, intensity * fade * 0.5) + + def BlendPixel(self, x: int, y: int, col: t.CUInt32T, alpha: float): + if alpha <= 0.0: return + fb: UINT32PTR = self._fb; w: int = self._w; h: int = self._h + if x < 0 or x >= w or y < 0 or y >= h: return + + idx: int = y * w + x + bg: t.CUInt32T = fb[idx] + bg_b: int = bg & 0xFF; bg_g: int = (bg >> 8) & 0xFF; bg_r: int = (bg >> 16) & 0xFF + f_b: int = col & 0xFF; f_g: int = (col >> 8) & 0xFF; f_r: int = (col >> 16) & 0xFF + + a: int = int(alpha * 255.0) + inv_a: int = 255 - a + nr: int = (f_r * a + bg_r * inv_a) >> 8 + ng: int = (f_g * a + bg_g * inv_a) >> 8 + nb: int = (f_b * a + bg_b * inv_a) >> 8 + + fb[idx] = COLOR_RGB(255 if (nr > 255) else nr, + 255 if (ng > 255) else ng, + 255 if (nb > 255) else nb) + + +@t.Object +class Scene3D: + renderer: Renderer + + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int): + self.renderer = Renderer(fb, zb, w, h) + + def Render(self, time: float): + rw: int = self.renderer._w + rh: int = self.renderer._h + self.renderer.Clear(COLOR_RGB(15, 15, 25)) + + yaw: float = float(45.0) * PI / float(180.0); pitch: float = float(45.0) * PI / float(180.0); radius: float = float(10.0) + eye: Vec3 = Vec3(radius*vipermath.cosf(pitch)*vipermath.cosf(yaw), radius*vipermath.sinf(pitch), radius*vipermath.cosf(pitch)*vipermath.sinf(yaw)) + target: Vec3 = Vec3(0, 0, 0); up: Vec3 = Vec3(0, 1, 0) + view: Mat4 = Mat4_LookAt(eye, target, up) + proj: Mat4 = Mat4_Perspective(PI / float(3.0), float(rw)/float(rh), float(0.1), float(100.0)) + vp: Mat4 = Mat4_Mul(proj, view) + + cubeX: float = float(-3.5) + cubeMVP: Mat4 = Mat4_Mul(vp, Mat4_Translate(cubeX, 0, 0)) + self._DrawCube(cubeMVP, COLOR_RGB(180, 180, 180)) + + colors: t.CArray[t.CUInt32T, 4] = [COLOR_RGB(255,50,50), COLOR_RGB(50,50,255), COLOR_RGB(255,255,50), COLOR_RGB(50,255,255)] + for i in range(4): + a: float = time * float(1.5) + i * (PI / float(2.0)); r: float = float(3.0) + pos: Vec3 = Vec3(vipermath.cosf(a) * r + cubeX, 0, vipermath.sinf(a) * r) + sphereMVP: Mat4 = Mat4_Mul(vp, Mat4_Translate(pos.x, pos.y, pos.z)) + self._DrawSphere(sphereMVP, Vec3(0,0,0), float(0.5), colors[i]) + + self._Draw3DSinWave(vp, Vec3(float(3.5), 0, 0), time) + + self.renderer.DrawLine2D(50, rh - 150, rw - 50, rh - 150, COLOR_RGB(80, 80, 80)) + self.renderer.DrawLine2D(50, rh - 50, 50, rh - 250, COLOR_RGB(80, 80, 80)) + wave_shift: float = time * float(2.0) + prev_x: int = 50; prev_y: int = rh - 150 - int(vipermath.sinf(0 - wave_shift) * float(100.0)) + for i in range(1, rw - 100): + tr: float = float(i) / float(rw-100) * float(4.0) * PI + cx: int = 50 + i; cy: int = rh - 150 - int(vipermath.sinf(tr - wave_shift) * float(100.0)) + self.renderer.DrawLine2D(prev_x, prev_y, cx, cy, COLOR_RGB(0, 255, 100)) + prev_x = cx; prev_y = cy + + def _DrawCube(self, mvp: Mat4, col: t.CUInt32T): + v: t.CArray[Vec3, 8] = [ + Vec3(-1, -1, -1), Vec3(-1, -1, 1), Vec3(-1, 1, -1), Vec3(-1, 1, 1), + Vec3( 1, -1, -1), Vec3( 1, -1, 1), Vec3( 1, 1, -1), Vec3( 1, 1, 1) + ] + for i in range(8): v[i] = Mat4_MulVec(mvp, v[i]) + faces: t.CArray[t.CArray[int, 3], 12] = [ + [0, 2, 1], [1, 2, 3], [4, 1, 5], [4, 0, 1], [6, 3, 2], [6, 7, 3], + [4, 6, 0], [0, 6, 2], [5, 7, 4], [4, 7, 6], [1, 3, 5], [5, 3, 7] + ] + for i in range(12): self.renderer.DrawTriangle(v[faces[i][0]], v[faces[i][1]], v[faces[i][2]], col) + + def _DrawSphere(self, mvp: Mat4, center: Vec3, radius: float, col: t.CUInt32T): + step_pi: float = PI / SPHERE_STACKS; step_2pi: float = float(2.0) * PI / SPHERE_SLICES + for i in range(SPHERE_STACKS): + for j in range(SPHERE_SLICES): + theta1: float = i * step_pi; theta2: float = (i + 1) * step_pi + phi1: float = j * step_2pi; phi2: float = (j + 1) * step_2pi + v: t.CArray[Vec3, 4] + v[0] = Vec3(center.x+radius*vipermath.sinf(theta1)*vipermath.cosf(phi1), center.y+radius*vipermath.cosf(theta1), center.z+radius*vipermath.sinf(theta1)*vipermath.sinf(phi1)) + v[1] = Vec3(center.x+radius*vipermath.sinf(theta1)*vipermath.cosf(phi2), center.y+radius*vipermath.cosf(theta1), center.z+radius*vipermath.sinf(theta1)*vipermath.sinf(phi2)) + v[2] = Vec3(center.x+radius*vipermath.sinf(theta2)*vipermath.cosf(phi1), center.y+radius*vipermath.cosf(theta2), center.z+radius*vipermath.sinf(theta2)*vipermath.sinf(phi1)) + v[3] = Vec3(center.x+radius*vipermath.sinf(theta2)*vipermath.cosf(phi2), center.y+radius*vipermath.cosf(theta2), center.z+radius*vipermath.sinf(theta2)*vipermath.sinf(phi2)) + for k in range(4): v[k] = Mat4_MulVec(mvp, v[k]) + self.renderer.DrawTriangle(v[0], v[1], v[2], col) + self.renderer.DrawTriangle(v[1], v[3], v[2], col) + + def _Draw3DSinWave(self, vp: Mat4, pos: Vec3, time: float): + _range: float = float(2.0) * PI; step: float = _range / SIN_WAVE_GRID; scale: float = float(0.5) + mvp: Mat4 = Mat4_Mul(vp, Mat4_Mul(Mat4_Translate(pos.x, pos.y, pos.z), Mat4_RotateY(time * float(1.0)))) + for i in range(SIN_WAVE_GRID): + for j in range(SIN_WAVE_GRID): + x0: float = -PI + i * step; z0 = -PI + j * step; x1: float = x0 + step; z1 = z0 + step + y00: float = vipermath.sinf(x0) * vipermath.cosf(z0) * scale; y10: float = vipermath.sinf(x1) * vipermath.cosf(z0) * scale + y01: float = vipermath.sinf(x0) * vipermath.cosf(z1) * scale; y11: float = vipermath.sinf(x1) * vipermath.cosf(z1) * scale + + t1: float = (y00 / scale + float(1.0)) * float(0.5); r1: int = int(50 + 205 * t1); g1: int = int(80 + 120 * vipermath.sinf(t1 * PI)); b1: int = int(255 - 205 * t1) + col1: t.CUInt32T = COLOR_RGB(255 if (r1 > 255) else (0 if (r1 < 0) else r1), 255 if (g1 > 255) else (0 if (g1 < 0) else g1), 255 if (b1 > 255) else (0 if (b1 < 0) else b1)) + t2: float = (y11 / scale + float(1.0)) * float(0.5); r2: int = int(50 + 205 * t2); g2: int = int(80 + 120 * vipermath.sinf(t2 * PI)); b2: int = int(255 - 205 * t2) + col2: t.CUInt32T = COLOR_RGB(255 if (r2 > 255) else (0 if (r2 < 0) else r2), 255 if (g2 > 255) else (0 if (g2 < 0) else g2), 255 if (b2 > 255) else (0 if (b2 < 0) else b2)) + + v00: Vec3 = Mat4_MulVec(mvp, Vec3(x0, y00, z0)); v10: Vec3 = Mat4_MulVec(mvp, Vec3(x1, y10, z0)) + v01: Vec3 = Mat4_MulVec(mvp, Vec3(x0, y01, z1)); v11: Vec3 = Mat4_MulVec(mvp, Vec3(x1, y11, z1)) + self.renderer.DrawTriangle(v00, v10, v11, col1); self.renderer.DrawTriangle(v00, v11, v01, col2) diff --git a/VKernel/Kernel/drivers/video/vesafb/vga.py b/VKernel/Kernel/drivers/video/vesafb/vga.py new file mode 100644 index 0000000..ef6206c --- /dev/null +++ b/VKernel/Kernel/drivers/video/vesafb/vga.py @@ -0,0 +1,504 @@ +import string # std: standard +#import math # std: standard +import t, c + + +# 颜色格式枚举 +class __ColorFormat(t.CEnum): + ColorFormat_BGRA: t.State # BGRA格式(蓝色、绿色、红色、Alpha) + ColorFormat_RGBA: t.State # RGBA格式(红色、绿色、蓝色、Alpha) + ColorFormat_BGR: t.State # BGR格式(蓝色、绿色、红色) + ColorFormat_RGB: t.State # RGB格式(红色、绿色、蓝色) + +# 常量定义 - 提升代码可读性和可维护性 +VGA_WIDTH: t.CDefine = 80 +VGA_HEIGHT: t.CDefine = 25 +FONT_WIDTH: t.CDefine = 8 +FONT_HEIGHT: t.CDefine = 16 +DEFAULT_CHAR: t.CDefine = 0 # 非ASCII字符的默认替换字符 +SERIAL_PORT: t.CDefine = 0x3F8 # 串口调试端口地址 + +# 8x16字符点阵字库 (每个字符16字节,共256个字符) +font: t.CArray[t.CArray[t.CUInt8T, 16], 256] = [ + # 字符编号 0x00 - 0x0F + {0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x00 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x01 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x56, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x02 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x56, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x03 + {0x00, 0x00, 0x70, 0x55, 0x55, 0x57, 0x71, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x04 + {0x00, 0x00, 0x70, 0x57, 0x54, 0x57, 0x71, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x05 + {0x00, 0x00, 0x70, 0x57, 0x54, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x06 + {0x00, 0x00, 0x70, 0x57, 0x51, 0x52, 0x72, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x07 + {0x00, 0x00, 0x70, 0x57, 0x55, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x08 + {0x00, 0x00, 0x70, 0x57, 0x55, 0x57, 0x71, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x09 + {0x00, 0x00, 0x70, 0x56, 0x51, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0A + {0x00, 0x00, 0x70, 0x54, 0x54, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0B + {0x00, 0x00, 0x70, 0x50, 0x50, 0x57, 0x74, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0C + {0x00, 0x00, 0x70, 0x51, 0x51, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0D + {0x00, 0x00, 0x70, 0x57, 0x55, 0x57, 0x74, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0E + {0x00, 0x00, 0x70, 0x53, 0x54, 0x56, 0x74, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0F + + # 字符编号 0x10 - 0x1F + {0x00, 0x00, 0x10, 0x37, 0x15, 0x15, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x10 + {0x00, 0x00, 0x10, 0x32, 0x16, 0x12, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x11 + {0x00, 0x00, 0x20, 0x6E, 0x22, 0x2E, 0x28, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x12 + {0x00, 0x00, 0x10, 0x37, 0x11, 0x13, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x13 + {0x00, 0x00, 0x10, 0x35, 0x15, 0x17, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x14 + {0x00, 0x00, 0x10, 0x37, 0x14, 0x17, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x15 + {0x00, 0x00, 0x10, 0x37, 0x14, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x16 + {0x00, 0x00, 0x10, 0x37, 0x11, 0x12, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x17 + {0x00, 0x00, 0x10, 0x37, 0x15, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x18 + {0x00, 0x00, 0x10, 0x37, 0x15, 0x17, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x19 + {0x00, 0x00, 0x10, 0x36, 0x11, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1A + {0x00, 0x00, 0x10, 0x34, 0x14, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1B + {0x00, 0x00, 0x10, 0x30, 0x10, 0x17, 0x14, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1C + {0x00, 0x00, 0x10, 0x31, 0x11, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1D + {0x00, 0x00, 0x10, 0x37, 0x15, 0x17, 0x14, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1E + {0x00, 0x00, 0x10, 0x33, 0x14, 0x16, 0x14, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1F + + # 字符编号 0x20 - 0x2F + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x20 + {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x21 + {0x6C, 0x6C, 0x6C, 0x6C, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x22 + {0x36, 0x36, 0x36, 0x7F, 0x7F, 0x36, 0x36, 0x36, 0x7F, 0x7F, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00}, # 0x23 + {0x0C, 0x0C, 0x3E, 0x7F, 0x68, 0x68, 0x3E, 0x0B, 0x0B, 0x7F, 0x3E, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x24 + {0x60, 0x60, 0x66, 0x06, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x60, 0x66, 0x06, 0x06, 0x00, 0x00, 0x00}, # 0x25 + {0x38, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x38, 0x6C, 0x6D, 0x66, 0x66, 0x6F, 0x3B, 0x00, 0x00, 0x00}, # 0x26 + {0x18, 0x18, 0x18, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x27 + {0x1C, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x1C, 0x00, 0x00}, # 0x28 + {0x38, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x38, 0x00, 0x00}, # 0x29 + {0x00, 0x00, 0x18, 0x18, 0x7E, 0x3C, 0x3C, 0x3C, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2A + {0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2B + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x30, 0x00}, # 0x2C + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2D + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x2E + {0x00, 0x00, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2F + + # 字符编号 0x30 - 0x3F + {0x3C, 0x7E, 0x66, 0x66, 0x6E, 0x6E, 0x7E, 0x76, 0x76, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x30 + {0x18, 0x38, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x31 + {0x3C, 0x7E, 0x66, 0x06, 0x06, 0x0E, 0x1C, 0x38, 0x70, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x32 + {0x3C, 0x7E, 0x66, 0x06, 0x06, 0x1C, 0x1C, 0x06, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x33 + {0x0C, 0x0C, 0x1C, 0x1C, 0x3C, 0x2C, 0x6C, 0x7E, 0x7E, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x00, 0x00}, # 0x34 + {0x7E, 0x7E, 0x60, 0x60, 0x7C, 0x7E, 0x06, 0x06, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x35 + {0x3C, 0x7E, 0x66, 0x60, 0x60, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x36 + {0x7E, 0x7E, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00}, # 0x37 + {0x3C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x38 + {0x3C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x39 + {0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x3A + {0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x30, 0x00}, # 0x3B + {0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, 0x00, 0x00}, # 0x3C + {0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x3D + {0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00, 0x00, 0x00}, # 0x3E + {0x3C, 0x7E, 0x66, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x3F + + # 字符编号 0x40 - 0x4F + {0x3C, 0x7E, 0x66, 0x66, 0x6E, 0x6A, 0x6A, 0x6A, 0x6E, 0x60, 0x60, 0x7C, 0x3C, 0x00, 0x00, 0x00}, # 0x40 + {0x3C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x41 + {0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7C, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x00, 0x00, 0x00}, # 0x42 + {0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x43 + {0x78, 0x7C, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0x7C, 0x78, 0x00, 0x00, 0x00}, # 0x44 + {0x7E, 0x7E, 0x60, 0x60, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x45 + {0x7E, 0x7E, 0x60, 0x60, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0x46 + {0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x6E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x47 + {0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x48 + {0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x49 + {0x3E, 0x3E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x7C, 0x38, 0x00, 0x00, 0x00}, # 0x4A + {0x66, 0x66, 0x66, 0x6C, 0x6C, 0x78, 0x78, 0x78, 0x6C, 0x6C, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x4B + {0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x4C + {0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00}, # 0x4D + {0x66, 0x66, 0x66, 0x66, 0x76, 0x76, 0x7E, 0x6E, 0x6E, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x4E + {0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x4F + + # 字符编号 0x50 - 0x5F + {0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0x50 + {0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6A, 0x6A, 0x6C, 0x7E, 0x36, 0x00, 0x00, 0x00}, # 0x51 + {0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x52 + {0x3C, 0x7E, 0x66, 0x60, 0x60, 0x78, 0x3C, 0x0E, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x53 + {0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x54 + {0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x55 + {0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x56 + {0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x6B, 0x7F, 0x77, 0x77, 0x22, 0x00, 0x00, 0x00}, # 0x57 + {0x66, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x3C, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x58 + {0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x59 + {0x7E, 0x7E, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x5A + {0x78, 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x78, 0x00, 0x00}, # 0x5B + {0xC0, 0xC0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x0C, 0x0C, 0x06, 0x06, 0x03, 0x03, 0x00, 0x00, 0x00}, # 0x5C + {0x1E, 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x1E, 0x00, 0x00}, # 0x5D + {0x3C, 0x7E, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x5E + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00}, # 0x5F + + # 字符编号 0x60 - 0x6F + {0x30, 0x38, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x60 + {0x00, 0x00, 0x00, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0x61 + {0x60, 0x60, 0x60, 0x60, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x00, 0x00, 0x00}, # 0x62 + {0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x63 + {0x06, 0x06, 0x06, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0x64 + {0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x65 + {0x1C, 0x3E, 0x30, 0x30, 0x30, 0x7C, 0x7C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00}, # 0x66 + {0x00, 0x00, 0x00, 0x00, 0x3E, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x3E, 0x3C, 0x00}, # 0x67 + {0x60, 0x60, 0x60, 0x60, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x68 + {0x18, 0x18, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x69 + {0x0C, 0x0C, 0x00, 0x00, 0x3C, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x7C, 0x78, 0x00}, # 0x6A + {0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x6C, 0x78, 0x78, 0x6C, 0x6C, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x6B + {0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x6C + {0x00, 0x00, 0x00, 0x00, 0x36, 0x7F, 0x6B, 0x6B, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00}, # 0x6D + {0x00, 0x00, 0x00, 0x00, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x6E + {0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x6F + + # 字符编号 0x70 - 0x7F + {0x00, 0x00, 0x00, 0x00, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x00}, # 0x70 + {0x00, 0x00, 0x00, 0x00, 0x3E, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x07, 0x07, 0x00}, # 0x71 + {0x00, 0x00, 0x00, 0x00, 0x6C, 0x7E, 0x76, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0x72 + {0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x62, 0x70, 0x3C, 0x0E, 0x46, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x73 + {0x30, 0x30, 0x30, 0x30, 0x7C, 0x7C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x1C, 0x00, 0x00, 0x00}, # 0x74 + {0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0x75 + {0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x76 + {0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x6B, 0x7F, 0x7F, 0x36, 0x00, 0x00, 0x00}, # 0x77 + {0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x3C, 0x3C, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x78 + {0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0x79 + {0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x7A + {0x0E, 0x1E, 0x18, 0x18, 0x18, 0x18, 0x30, 0x30, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x0E, 0x00, 0x00}, # 0x7B + {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00}, # 0x7C + {0x70, 0x78, 0x18, 0x18, 0x18, 0x18, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x78, 0x70, 0x00, 0x00}, # 0x7D + {0x31, 0x79, 0x6B, 0x4F, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x7E + {0x00, 0x00, 0x70, 0x13, 0x24, 0x26, 0x24, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x7F + + # 字符编号 0x80 - 0x8F + {0x03, 0x03, 0x03, 0x03, 0x06, 0x06, 0x06, 0x06, 0x66, 0x7C, 0x3C, 0x1C, 0x0C, 0x00, 0x00, 0x00}, # 0x80 + {0x1C, 0x36, 0x00, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x6B, 0x7F, 0x7F, 0x77, 0x22, 0x00, 0x00, 0x00}, # 0x81 + {0x1C, 0x36, 0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00, 0x00, 0x00}, # 0x82 + {0xFE, 0x92, 0x92, 0x92, 0x92, 0x92, 0xF2, 0x82, 0x82, 0x82, 0x82, 0x82, 0xFE, 0x00, 0x00, 0x00}, # 0x83 + {0x66, 0x66, 0x99, 0x99, 0x81, 0x42, 0x42, 0x42, 0x81, 0x99, 0x99, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x84 + {0x18, 0x3C, 0x66, 0x00, 0x42, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x85 + {0x18, 0x3C, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0x86 + {0x07, 0x01, 0x01, 0x01, 0x02, 0x02, 0x64, 0x94, 0x94, 0x90, 0x60, 0x90, 0x90, 0x90, 0x60, 0x00}, # 0x87 + {0x18, 0x28, 0x28, 0x48, 0x4F, 0x81, 0x81, 0x81, 0x4F, 0x48, 0x28, 0x28, 0x18, 0x00, 0x00, 0x00}, # 0x88 + {0x18, 0x14, 0x14, 0x12, 0xF2, 0x81, 0x81, 0x81, 0xF2, 0x12, 0x14, 0x14, 0x18, 0x00, 0x00, 0x00}, # 0x89 + {0x3C, 0x24, 0x24, 0x24, 0x24, 0x24, 0xE7, 0x81, 0x42, 0x42, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00}, # 0x8A + {0x18, 0x24, 0x24, 0x42, 0x42, 0x81, 0xE7, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00}, # 0x8B + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0x00, 0x00, 0x00}, # 0x8C + {0x45, 0x4B, 0x51, 0x61, 0x61, 0x61, 0x51, 0x49, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x8D + {0xC0, 0xC0, 0xCC, 0x0C, 0x18, 0x18, 0x30, 0x60, 0x60, 0xC0, 0xDB, 0x1B, 0x1B, 0x00, 0x00, 0x00}, # 0x8E + {0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x8F + + # 字符编号 0x90 - 0x9F + {0x0C, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x90 + {0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x91 + {0x00, 0x00, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x30, 0x18, 0x18, 0x0C, 0x0C, 0x00, 0x00, 0x00}, # 0x92 + {0x00, 0x00, 0x30, 0x30, 0x18, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x00, 0x00, 0x00}, # 0x93 + {0x1B, 0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x94 + {0x36, 0x36, 0x36, 0x36, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x95 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36, 0x6C, 0x6C}, # 0x96 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x97 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x98 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x99 + {0x77, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xCC, 0xCC, 0xCC, 0xCC, 0xFF, 0x77, 0x00, 0x00, 0x00}, # 0x9A + {0x00, 0x00, 0x00, 0x00, 0x6E, 0xFF, 0xDB, 0xDB, 0xDF, 0xD8, 0xD8, 0xFF, 0x6E, 0x00, 0x00, 0x00}, # 0x9B + {0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, # 0x9C + {0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, # 0x9D + {0x3C, 0x7C, 0x60, 0x66, 0x66, 0xF0, 0xF6, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x9E + {0x3E, 0x7E, 0x66, 0x66, 0x66, 0xF6, 0xF6, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x9F + + # 字符编号 0xA0 - 0xAF + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xA0 + {0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xA1 + {0x08, 0x08, 0x3E, 0x7F, 0x6B, 0x68, 0x68, 0x68, 0x6B, 0x7F, 0x3E, 0x08, 0x08, 0x00, 0x00, 0x00}, # 0xA2 + {0x1C, 0x3E, 0x36, 0x30, 0x30, 0x7C, 0x7C, 0x30, 0x30, 0x30, 0x30, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xA3 + {0x00, 0x00, 0x42, 0x66, 0x3C, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x66, 0x42, 0x00, 0x00, 0x00}, # 0xA4 + {0x66, 0x66, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xA5 + {0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xA6 + {0x3C, 0x7E, 0x60, 0x78, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x1E, 0x06, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xA7 + {0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xA8 + {0x3C, 0x42, 0x42, 0x81, 0x99, 0xA5, 0xA1, 0xA1, 0xA1, 0xA5, 0x99, 0x81, 0x42, 0x42, 0x3C, 0x00}, # 0xA9 + {0x1C, 0x1E, 0x06, 0x06, 0x1E, 0x36, 0x36, 0x3E, 0x1E, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x00}, # 0xAA + {0x00, 0x00, 0x33, 0x33, 0x66, 0x66, 0xCC, 0xCC, 0xCC, 0x66, 0x66, 0x33, 0x33, 0x00, 0x00, 0x00}, # 0xAB + {0x7E, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xAC + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xAD + {0x3C, 0x42, 0x42, 0x81, 0xB9, 0xA5, 0xA5, 0xA5, 0xB9, 0xA5, 0xA5, 0x81, 0x42, 0x42, 0x3C, 0x00}, # 0xAE + {0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xAF + + # 字符编号 0xB0 - 0xBF + {0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB0 + {0x00, 0x00, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00}, # 0xB1 + {0x38, 0x04, 0x04, 0x04, 0x18, 0x20, 0x20, 0x20, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB2 + {0x38, 0x04, 0x04, 0x04, 0x18, 0x04, 0x04, 0x04, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB3 + {0x0C, 0x1C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB4 + {0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x3E, 0x30, 0x60, 0x60}, # 0xB5 + {0x03, 0x03, 0x3E, 0x7E, 0x76, 0x76, 0x76, 0x36, 0x36, 0x36, 0x36, 0x3E, 0x3E, 0x00, 0x00, 0x00}, # 0xB6 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB7 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x18, 0x30, 0x00}, # 0xB8 + {0x10, 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB9 + {0x1C, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x1C, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x00}, # 0xBA + {0x00, 0x00, 0x88, 0xCC, 0x66, 0x66, 0x33, 0x33, 0x33, 0x66, 0x66, 0xCC, 0x88, 0x00, 0x00, 0x00}, # 0xBB + {0x40, 0x40, 0xC0, 0x40, 0x40, 0x40, 0x48, 0x48, 0x48, 0x08, 0x0A, 0x0A, 0x0F, 0x02, 0x02, 0x00}, # 0xBC + {0x40, 0x40, 0xC0, 0x40, 0x40, 0x40, 0x4F, 0x41, 0x41, 0x01, 0x0F, 0x08, 0x08, 0x08, 0x0F, 0x00}, # 0xBD + {0xE0, 0x20, 0x20, 0x20, 0xE0, 0x20, 0x28, 0x28, 0xE8, 0x08, 0x0A, 0x0A, 0x0F, 0x02, 0x02, 0x00}, # 0xBE + {0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x30, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xBF + + # 字符编号 0xC0 - 0xCF + {0x30, 0x38, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC0 + {0x0C, 0x1C, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC1 + {0x18, 0x3C, 0x66, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC2 + {0x36, 0x7E, 0x6C, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC3 + {0x66, 0x66, 0x66, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC4 + {0x3C, 0x66, 0x66, 0x3C, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC5 + {0x3F, 0x7F, 0x6C, 0x6C, 0x6C, 0x7F, 0x7F, 0x6C, 0x6C, 0x6C, 0x6C, 0x6F, 0x6F, 0x00, 0x00, 0x00}, # 0xC6 + {0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x18, 0x30, 0x30, 0x60, 0x00}, # 0xC7 + {0x30, 0x38, 0x18, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xC8 + {0x0C, 0x1C, 0x18, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xC9 + {0x3C, 0x7E, 0x66, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCA + {0x66, 0x66, 0x00, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCB + {0x30, 0x38, 0x18, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCC + {0x0C, 0x1C, 0x18, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCD + {0x3C, 0x7E, 0x66, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCE + {0x66, 0x66, 0x66, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCF + + # 字符编号 0xD0 - 0xDF + {0x78, 0x7C, 0x6C, 0x66, 0x66, 0xF6, 0xF6, 0x66, 0x66, 0x66, 0x6C, 0x7C, 0x78, 0x00, 0x00, 0x00}, # 0xD0 + {0x36, 0x7E, 0x6C, 0x00, 0x66, 0x66, 0x76, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xD1 + {0x30, 0x38, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD2 + {0x0C, 0x1C, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD3 + {0x3C, 0x7E, 0x66, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD4 + {0x36, 0x7E, 0x6C, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD5 + {0x66, 0x66, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD6 + {0x00, 0x00, 0x63, 0x63, 0x36, 0x36, 0x1C, 0x08, 0x1C, 0x36, 0x36, 0x63, 0x63, 0x00, 0x00, 0x00}, # 0xD7 + {0x3D, 0x7F, 0x66, 0x66, 0x6E, 0x6E, 0x7E, 0x76, 0x76, 0x66, 0x66, 0x7E, 0xBC, 0x80, 0x00, 0x00}, # 0xD8 + {0x30, 0x38, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD9 + {0x0C, 0x1C, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xDA + {0x3C, 0x7E, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xDB + {0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xDC + {0x0C, 0x1C, 0x18, 0x00, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xDD + {0xF0, 0xF0, 0x60, 0x78, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x78, 0x60, 0xF0, 0xF0, 0x00, 0x00, 0x00}, # 0xDE + {0x3C, 0x7E, 0x66, 0x66, 0x66, 0x6C, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x6E, 0x6C, 0x60, 0xC0, 0x00}, # 0xDF + + # 字符编号 0xE0 - 0xEF + {0x30, 0x38, 0x18, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE0 + {0x0C, 0x1C, 0x18, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE1 + {0x18, 0x3C, 0x66, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE2 + {0x36, 0x7E, 0x6C, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE3 + {0x66, 0x66, 0x00, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE4 + {0x3C, 0x66, 0x66, 0x3C, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE5 + {0x00, 0x00, 0x00, 0x00, 0x3E, 0x3F, 0x0B, 0x3B, 0x7F, 0x6F, 0x68, 0x7F, 0x3F, 0x00, 0x00, 0x00}, # 0xE6 + {0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x10, 0x60, 0x00}, # 0xE7 + {0x30, 0x38, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE8 + {0x0C, 0x1C, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE9 + {0x3C, 0x7E, 0x66, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xEA + {0x66, 0x66, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xEB + {0x30, 0x38, 0x18, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xEC + {0x0C, 0x1C, 0x18, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xED + {0x3C, 0x7E, 0x66, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xEE + {0x66, 0x66, 0x00, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xEF + + # 字符编号 0xF0 - 0xFF + {0x18, 0x18, 0x7E, 0x7E, 0x0C, 0x06, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF0 + {0x36, 0x7E, 0x6C, 0x00, 0x00, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xF1 + {0x30, 0x38, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF2 + {0x0C, 0x1C, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF3 + {0x3C, 0x7E, 0x66, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF4 + {0x36, 0x7E, 0x6C, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF5 + {0x66, 0x66, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF6 + {0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00}, # 0xF7 + {0x00, 0x00, 0x00, 0x02, 0x02, 0x3C, 0x7E, 0x6E, 0x76, 0x76, 0x66, 0x7E, 0xBC, 0x80, 0x00, 0x00}, # 0xF8 + {0x30, 0x38, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xF9 + {0x0C, 0x1C, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xFA + {0x18, 0x3C, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xFB + {0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xFC + {0x0C, 0x1C, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0xFD + {0x60, 0x60, 0x60, 0x78, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x78, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0xFE + {0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0xFF +] + +@t.Object +class _VGAScreenDriver: + # 帧缓冲区信息 + fbv: t.CVoid | t.CPtr # = None + fb: t.CUnsignedChar | t.CPtr # = None + fw: int # = 800 + fh: int # = 600 + fp: int # = 800 * 4 # 3200 800*4 + ColorFormat: __ColorFormat # = __ColorFormat.ColorFormat_BGRA # 默认使用BGRA格式 + BgColor: t.CUnsignedInt = 0xFF000080 # RGBA: 不透明深蓝色 + + # 初始化VGA驱动 + def __init__(self, fb: t.CVoid | t.CPtr, width: int, height: int, pitch: int, format: int): + # 先做参数合法性检查 + if fb is None or width <= 0 or height <= 0 or pitch <= 0: return + # 更新帧缓冲区参数 + self.fbv = fb + self.fb = t.CUnsignedChar(fb, t.CPtr) + self.fw = width + self.fh = height + self.fp = pitch + # 设置颜色格式 + match format: + case 0: self.ColorFormat = __ColorFormat.ColorFormat_RGBA + case 1: self.ColorFormat = __ColorFormat.ColorFormat_BGRA + case 2: pass + case 3: pass + case _: self.ColorFormat = __ColorFormat.ColorFormat_BGRA + # 检测硬件实际颜色格式(覆盖传入的format) + self.DetectColorFormat() + # 初始化背景颜色 + self.ClearScreen(self.BgColor) + + # 检测硬件颜色格式 - 优化测试逻辑,避免误判 + def DetectColorFormat(self) -> t.CStatic | t.CVoid: + if self.fb is None: return + fb: t.CUnsignedChar | t.CPtr = t.CUnsignedChar(self.fb, t.CPtr) + testX: int = 0 + testY: int = 0 + offset: int = testY * self.fp + testX * 4 + # 保存测试位置原始像素值,避免污染屏幕 + OriginalPixel: t.CUInt32T = c.Deref(t.CUInt32T(fb + offset, t.CPtr)) + # 写入测试颜色:青色 (RGBA逻辑值: 0xFF00FFFF) + fb[offset + 0] = 0xFF # B + fb[offset + 1] = 0xFF # G + fb[offset + 2] = 0x00 # R + fb[offset + 3] = 0xFF # A + # 读取回测试像素 + b: t.CUnsignedChar = fb[offset + 0] + g: t.CUnsignedChar = fb[offset + 1] + r: t.CUnsignedChar = fb[offset + 2] + # t.CVoid(fb[offset + 3]) # 忽略Alpha + # 恢复原始像素 + c.Set(c.Deref(t.CUInt32T(fb + offset, t.CPtr)), OriginalPixel) + # 判定颜色格式 + if r == 0x00 and g == 0xFF and b == 0xFF: self.ColorFormat = __ColorFormat.ColorFormat_BGRA + elif r == 0xFF and g == 0xFF and b == 0x00: self.ColorFormat = __ColorFormat.ColorFormat_RGBA + else: self.ColorFormat = __ColorFormat.ColorFormat_BGRA + + # 设置像素颜色 - 统一接口,输入为RGBA逻辑颜色 + # color格式: 0xAARRGGBB (Alpha, Red, Green, Blue) + def SetPixel(self, x: int, y: int, color: t.CUInt32T): + fb: t.CUnsignedChar | t.CPtr = self.fb + if fb is None: return + if x < 0 or x >= self.fw or y < 0 or y >= self.fh: return + offset: int = y * self.fp + x * 4 + # 解析RGBA分量 + r: t.CUnsignedChar = (color >> 16) & 0xFF + g: t.CUnsignedChar = (color >> 8) & 0xFF + b: t.CUnsignedChar = color & 0xFF + a: t.CUnsignedChar = (color >> 24) & 0xFF + # 根据硬件格式写入像素 + match self.ColorFormat: + case __ColorFormat.ColorFormat_BGRA: + self.fb[offset + 0] = b + fb[offset + 1] = g + fb[offset + 2] = r + fb[offset + 3] = a + case __ColorFormat.ColorFormat_RGBA: + fb[offset + 0] = r + fb[offset + 1] = g + fb[offset + 2] = b + fb[offset + 3] = a + case __ColorFormat.ColorFormat_BGR: + fb[offset + 0] = b + fb[offset + 1] = g + fb[offset + 2] = r + case __ColorFormat.ColorFormat_RGB: + fb[offset + 0] = r + fb[offset + 1] = g + fb[offset + 2] = b + case _: + # 降级到BGRA + fb[offset + 0] = b + fb[offset + 1] = g + fb[offset + 2] = r + fb[offset + 3] = a + + # 清除屏幕 - 优化循环效率,减少函数调用开销 + def ClearScreen(self, color: t.CUInt32T): + fb: t.CUnsignedChar | t.CPtr = self.fb + fp: int = self.fp + if fb is None: return + PixelSize: int = 3 if self.ColorFormat == __ColorFormat.ColorFormat_BGR or self.ColorFormat == __ColorFormat.ColorFormat_RGB else 4 + RowSize: int = self.fw * PixelSize + # 解析清除颜色的RGBA分量 + r: t.CUnsignedChar = (color >> 16) & 0xFF + g: t.CUnsignedChar = (color >> 8 ) & 0xFF + b: t.CUnsignedChar = (color) & 0xFF + a: t.CUnsignedChar = (color >> 24) & 0xFF + # 逐行填充,比逐像素调用SetPixel快得多 + for y in range(self.fh): + row_ptr: t.CUnsignedChar | t.CPtr = fb + y * fp + for x in range(self.fw): + offset: int = x * PixelSize + match self.ColorFormat: + case __ColorFormat.ColorFormat_BGRA: + row_ptr[offset + 0] = b + row_ptr[offset + 1] = g + row_ptr[offset + 2] = r + row_ptr[offset + 3] = a + case __ColorFormat.ColorFormat_RGBA: + row_ptr[offset + 0] = r + row_ptr[offset + 1] = g + row_ptr[offset + 2] = b + row_ptr[offset + 3] = a + case __ColorFormat.ColorFormat_BGR: + row_ptr[offset + 0] = b + row_ptr[offset + 1] = g + row_ptr[offset + 2] = r + case __ColorFormat.ColorFormat_RGB: + row_ptr[offset + 0] = r + row_ptr[offset + 1] = g + row_ptr[offset + 2] = b + + def Print(self, x: int, y: int, s: str, fontcolor: t.CUnsignedInt = 0xFFFFFFFF, size: int = 16): + self.PrintColor(x, y, s, fontcolor, 0x00000000, size) + + def PutcharColor(self, x: int, y: int, cr: t.CChar, fontcolor: t.CUnsignedInt = 0xFFFFFFFF, bgcolor: t.CUnsignedInt = 0x00000000, size: int = 16): + fw: int = self.fw + fh: int = self.fh + fb: t.CUnsignedChar | t.CPtr = self.fb + if fb is None: return + # 提前判断字符整体是否越界(8x16),避免内层循环冗余检查 + if x < 0 or y < 0 or (x + FONT_WIDTH) > fw or (y + FONT_HEIGHT) > fh: return + # 处理字符范围,非ASCII字符替换为默认字符 + ch: t.CUnsignedChar = t.CUnsignedChar(cr) + if ch >= 128: ch = DEFAULT_CHAR + # 预解析背景色Alpha,避免内层循环重复计算 + bg_alpha: t.CUInt8T = (bgcolor >> 24) & 0xFF + draw_bg: int = (bg_alpha != 0) # 背景是否需要绘制 + # 渲染8x16点阵字符 + for fy in range(FONT_HEIGHT): + row: t.CUnsignedChar = font[ch][fy] + for fx in range(FONT_WIDTH): + px: int = x + fx + py: int = y + fy + # 检测当前点阵位是否点亮 + if row & (0x80 >> fx): + # 绘制前景色 + self.SetPixel(px, py, fontcolor) + elif draw_bg: + # 仅当背景不透明时绘制背景色 + self.SetPixel(px, py, bgcolor) + + # 在指定位置显示带颜色和字号的字符串 + def PrintColor(self, x: int, y: int, s: str, fontcolor: t.CUnsignedInt = 0xFFFFFFFF, bgcolor: t.CUnsignedInt = 0x00000000, size: int = 16): + fw: int = self.fw + fh: int = self.fh + # fb: t.CUnsignedChar | t.CPtr = self.fb + currentX: int = x + currentY: int = y + if s is None: return + # 逐字符处理 + for _s in s: + if _s == '\n': + # 换行:重置X,Y增加字符高度 + currentX = x + currentY += size + 2 # 字体大小 + 2像素行间距 + # 检查换行后是否越界 + if currentY + size > fh: break + else: + # 显示普通字符 + self.PutcharColor(currentX, currentY, _s, fontcolor, bgcolor, size) + # 计算字符宽度 + CharWidth: int = size / 2 + 1 # 简单估算字符宽度 + currentX += CharWidth + # 检查横向是否越界 + if currentX + CharWidth > fw: + # 自动换行 + currentX = x + currentY += size + 2 + if currentY + size > fh: break diff --git a/VKernel/Kernel/execrunner/elf.py b/VKernel/Kernel/execrunner/elf.py new file mode 100644 index 0000000..8e620ea --- /dev/null +++ b/VKernel/Kernel/execrunner/elf.py @@ -0,0 +1,757 @@ +import drivers.serial.uart.serial as serial +import drivers.fs.fat32.fat32 as fat32 +import drivers.fs.fat32.fat32_types as fat32_types +import drivers.core.cpu.cpu as cpu +import mm.mm as mm +import paging.paging as paging +import sched.process as proc +import sched.sched as sched +import intr.gdt as gdt +# Do NOT import KERN_STACK_SIZE/USER_STACK_SIZE from process - CDefine cross-module import is broken +# Redefine them here to ensure correct values +KERN_STACK_SIZE: t.CDefine = 8192 +USER_STACK_SIZE: t.CDefine = 65536 +import viperlib +import string +import asm +import t, c + +ELFMAG0: t.CDefine = 0x7F +ELFMAG1: t.CDefine = 0x45 +ELFMAG2: t.CDefine = 0x4C +ELFMAG3: t.CDefine = 0x46 +ELFCLASS64: t.CDefine = 2 +ELFDATA2LSB: t.CDefine = 1 +ET_EXEC: t.CDefine = 2 +ET_DYN: t.CDefine = 3 +EM_X86_64: t.CDefine = 62 +PT_LOAD: t.CDefine = 1 +PT_DYNAMIC: t.CDefine = 2 +PF_X: t.CDefine = 1 +PF_W: t.CDefine = 2 +PF_R: t.CDefine = 4 +DT_NULL: t.CDefine = 0 +DT_HASH: t.CDefine = 4 +DT_STRTAB: t.CDefine = 5 +DT_SYMTAB: t.CDefine = 6 +DT_STRSZ: t.CDefine = 10 +DT_SYMENT: t.CDefine = 11 +DT_RELA: t.CDefine = 7 +DT_RELASZ: t.CDefine = 8 +DT_RELAENT: t.CDefine = 9 +DT_PLTRELSZ: t.CDefine = 2 +DT_JMPREL: t.CDefine = 23 +R_X86_64_64: t.CDefine = 1 +R_X86_64_GLOB_DAT: t.CDefine = 6 +R_X86_64_JUMP_SLOT: t.CDefine = 7 +R_X86_64_RELATIVE: t.CDefine = 8 +STB_GLOBAL: t.CDefine = 1 +STB_WEAK: t.CDefine = 2 + +@c.Attribute(t.attr.packed) +class Elf64_Ehdr: + e_ident: t.CArray[t.CUInt8T, 16] + e_type: t.CUInt16T + e_machine: t.CUInt16T + e_version: t.CUInt32T + e_entry: t.CUInt64T + e_phoff: t.CUInt64T + e_shoff: t.CUInt64T + e_flags: t.CUInt32T + e_ehsize: t.CUInt16T + e_phentsize: t.CUInt16T + e_phnum: t.CUInt16T + e_shentsize: t.CUInt16T + e_shnum: t.CUInt16T + e_shstrndx: t.CUInt16T + +@c.Attribute(t.attr.packed) +class Elf64_Phdr: + p_type: t.CUInt32T + p_flags: t.CUInt32T + p_offset: t.CUInt64T + p_vaddr: t.CUInt64T + p_paddr: t.CUInt64T + p_filesz: t.CUInt64T + p_memsz: t.CUInt64T + p_align: t.CUInt64T + +MAX_PHDRS: t.CDefine = 16 + +@c.Attribute(t.attr.packed) +class Elf64_Shdr: + sh_name: t.CUInt32T + sh_type: t.CUInt32T + sh_flags: t.CUInt64T + sh_addr: t.CUInt64T + sh_offset: t.CUInt64T + sh_size: t.CUInt64T + sh_link: t.CUInt32T + sh_info: t.CUInt32T + sh_addralign: t.CUInt64T + sh_entsize: t.CUInt64T + +@c.Attribute(t.attr.packed) +class Elf64_Sym: + st_name: t.CUInt32T + st_info: t.CUInt8T + st_other: t.CUInt8T + st_shndx: t.CUInt16T + st_value: t.CUInt64T + st_size: t.CUInt64T + +@c.Attribute(t.attr.packed) +class Elf64_Rela: + r_offset: t.CUInt64T + r_info: t.CUInt64T + r_addend: t.CInt64T + +@c.Attribute(t.attr.packed) +class Elf64_Dyn: + d_tag: t.CInt64T + d_val: t.CUInt64T + +MAX_SHDRS: t.CDefine = 64 +MAX_DYNS: t.CDefine = 64 +MAX_RELAS: t.CDefine = 256 + +def load_elf(path: t.CConst | str, do_map: t.CInt = 1) -> t.CVoid | t.CPtr: + fp: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ) + if fp is None: + err: t.CUInt32T = fat32.last_error() + eb: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(eb), 64, "[elf] failed to open file err=%d\n", err) + serial.puts(eb) + return None + + ehdr_buf: t.CArray[t.CUInt8T, 64] + string.memset(c.Addr(ehdr_buf), 0, 64) + read_len: t.CUInt32T = fat32.read(fp, c.Addr(ehdr_buf), 64) + if read_len < 64: + serial.puts("[elf] failed to read ehdr\n") + fat32.close(fp) + return None + + ehdr: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf) + + if ehdr.e_ident[0] != ELFMAG0 or ehdr.e_ident[1] != ELFMAG1 or ehdr.e_ident[2] != ELFMAG2 or ehdr.e_ident[3] != ELFMAG3: + serial.puts("[elf] bad magic\n") + fat32.close(fp) + return None + + if ehdr.e_ident[4] != ELFCLASS64: + serial.puts("[elf] not ELF64\n") + fat32.close(fp) + return None + + if ehdr.e_ident[5] != ELFDATA2LSB: + serial.puts("[elf] not little-endian\n") + fat32.close(fp) + return None + + if ehdr.e_type != ET_EXEC and ehdr.e_type != ET_DYN: + serial.puts("[elf] not ET_EXEC or ET_DYN\n") + fat32.close(fp) + return None + + if ehdr.e_machine != EM_X86_64: + serial.puts("[elf] not x86_64\n") + fat32.close(fp) + return None + + entry_point: t.CUInt64T = ehdr.e_entry + phoff: t.CUInt64T = ehdr.e_phoff + phentsize: t.CUInt16T = ehdr.e_phentsize + phnum: t.CUInt16T = ehdr.e_phnum + + if phentsize == 0 or phnum == 0 or phnum > MAX_PHDRS: + serial.puts("[elf] invalid phdr count\n") + fat32.close(fp) + return None + + cr: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(cr), 80, "[elf] entry=0x%lx phoff=%lu phentsize=%u phnum=%u\n", entry_point, phoff, phentsize, phnum) + serial.puts(cr) + + phdr_bufs: t.CArray[Elf64_Phdr, MAX_PHDRS] + string.memset(c.Addr(phdr_bufs), 0, MAX_PHDRS * 56) + + for pi in range(phnum): + phdr_off: t.CUInt64T = phoff + t.CUInt64T(pi) * t.CUInt64T(phentsize) + fat32.seek(fp, t.CUInt32T(phdr_off)) + fat32.read(fp, c.Addr(phdr_bufs[pi]), 56) + + found_load: t.CInt = 0 + lowest_vaddr: t.CUInt64T = 0 + highest_vaddr_end: t.CUInt64T = 0 + for pi in range(phnum): + if phdr_bufs[pi].p_type == PT_LOAD: + seg_end: t.CUInt64T = phdr_bufs[pi].p_vaddr + phdr_bufs[pi].p_memsz + if found_load == 0: + lowest_vaddr = phdr_bufs[pi].p_vaddr + highest_vaddr_end = seg_end + found_load = 1 + else: + if phdr_bufs[pi].p_vaddr < lowest_vaddr: + lowest_vaddr = phdr_bufs[pi].p_vaddr + if seg_end > highest_vaddr_end: + highest_vaddr_end = seg_end + + if found_load == 0: + serial.puts("[elf] no LOAD segments\n") + fat32.close(fp) + return None + + total_memsz: t.CUInt64T = highest_vaddr_end - lowest_vaddr + total_pages: t.CUInt64T = (total_memsz + 0xFFF) // 0x1000 + + cr2: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(cr2), 80, "[elf] vaddr 0x%lx-0x%lx pages=%lu\n", lowest_vaddr, highest_vaddr_end, total_pages) + serial.puts(cr2) + + raw_alloc: t.CVoid | t.CPtr = mm.malloc(total_pages * 0x1000 + 0x1000) + _db_ra: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(_db_ra), 80, "[elf] malloc ret raw_alloc=0x%lx\n", t.CUInt64T(raw_alloc)) + serial.puts(_db_ra) + if not raw_alloc: + serial.puts("[elf] malloc failed\n") + fat32.close(fp) + return None + + phys_base_aligned: t.CUInt64T = (t.CUInt64T(raw_alloc) + t.CUInt64T(0xFFF)) & ~t.CUInt64T(0xFFF) + phys_base: t.CVoid | t.CPtr = t.CVoid(phys_base_aligned, t.CPtr) + + string.memset(phys_base, 0, total_pages * 0x1000) + + for pi in range(phnum): + if phdr_bufs[pi].p_type == PT_LOAD: + vaddr: t.CUInt64T = phdr_bufs[pi].p_vaddr + file_off: t.CUInt64T = phdr_bufs[pi].p_offset + filesz: t.CUInt64T = phdr_bufs[pi].p_filesz + memsz: t.CUInt64T = phdr_bufs[pi].p_memsz + dest_off: t.CUInt64T = vaddr - lowest_vaddr + dest: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(phys_base) + dest_off, t.CPtr) + + if filesz > 0: + fat32.seek(fp, t.CUInt32T(file_off)) + fat32.read(fp, dest, t.CUInt32T(filesz)) + + if memsz > filesz: + bss_start: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(dest) + filesz, t.CPtr) + bss_len: t.CUInt64T = memsz - filesz + string.memset(bss_start, 0, bss_len) + + fat32.close(fp) + + virt_page_start: t.CUInt64T = lowest_vaddr & ~t.CUInt64T(0xFFF) + phys_page_start: t.CUInt64T = t.CUInt64T(phys_base) & ~t.CUInt64T(0xFFF) + map_end: t.CUInt64T = (highest_vaddr_end + 0xFFF) & ~t.CUInt64T(0xFFF) + num_map_pages: t.CUInt64T = (map_end - virt_page_start) // 0x1000 + + # Store mapping info for spawn_elf (used when do_map=0) + global _last_elf_phys_page_start, _last_elf_virt_page_start, _last_elf_num_map_pages + _last_elf_phys_page_start = phys_page_start + _last_elf_virt_page_start = virt_page_start + _last_elf_num_map_pages = num_map_pages + + if do_map != 0: + for mi in range(num_map_pages): + virt_addr: t.CUInt64T = virt_page_start + mi * 0x1000 + phys_addr: t.CUInt64T = phys_page_start + mi * 0x1000 + page_flags: t.CUInt64T = paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER + paging.MapPage(virt_addr, phys_addr, page_flags) + + entry_addr: t.CVoid | t.CPtr = t.CVoid(entry_point, t.CPtr) + + return entry_addr + +def run_elf(path: t.CConst | str) -> t.CInt: + entry: t.CVoid | t.CPtr = load_elf(path) + if entry is None: + serial.puts("[elf] load failed\n") + return -1 + serial.puts("[elf] jumping to entry...\n") + c.Asm(f"""call {c.AsmInp(entry, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + serial.puts("[elf] returned from entry\n") + return 0 + +MAX_SPAWNED: t.CDefine = 8 +_elf_entries: t.CArray[t.CVoid | t.CPtr, MAX_SPAWNED] + +# User stack virtual address: top of 48-bit user space, 64KB below +USER_STACK_VTOP: t.CDefine = 0x7FFFFFE00000 + +# Globals for ELF mapping info (set by load_elf when do_map=0, used by spawn_elf +# to map ELF pages in the cloned PML4 instead of kernel PML4). +# ROOT CAUSE: previously load_elf mapped ELF vaddr (e.g. 0x400000) into the +# kernel PML4, overwriting the identity mapping of buddy system memory_block +# headers at 0x400000. This corrupted buddy metadata -> malloc failures, +# pid=0, and #GP in split_block. Fix: load_elf does NOT map; spawn_elf maps +# in the cloned PML4 after clone_for_process. +_last_elf_phys_page_start: t.CUInt64T = 0 +_last_elf_virt_page_start: t.CUInt64T = 0 +_last_elf_num_map_pages: t.CUInt64T = 0 + +def _elf_entry_wrapper(arg: t.CVoid | t.CPtr) -> t.CInt: + pid: t.CInt = t.CInt(t.CUInt64T(arg)) + _db1: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(_db1), 64, "[elf] wrapper pid=%d\n", pid) + serial.puts(_db1) + # Disable interrupts around switch_pid + MapPage + TSS setup: @pml4 is a global + # that switch_pid modifies. A timer interrupt mid-section would make the scheduler + # call switch_pid for another thread, corrupting @pml4 for our MapPage calls. + asm.cli() + proc.switch_pid(pid) + cur_p: proc.Process | t.CPtr = proc.ProcessManager.current() + entry_fn: t.CVoid | t.CPtr = cur_p.entry + if entry_fn is not None: + user_stack_vtop: t.CUInt64T = USER_STACK_VTOP + us_sz: t.CUInt64T = USER_STACK_SIZE + user_stack_vbottom: t.CUInt64T = USER_STACK_VTOP - us_sz + # Physical pages of the user stack (allocated by mm.malloc, identity-mapped) + us_size: t.CUInt64T = USER_STACK_SIZE + phys_bottom: t.CUInt64T = (cur_p.user_stack - us_size) & ~t.CUInt64T(0xFFF) + # Map each page of the user stack at the virtual address with PTE_USER + num_stack_pages: t.CUInt64T = 16 # USER_STACK_SIZE(65536) / PAGE_SIZE(4096) = 16 + si: t.CUInt64T + for si in range(num_stack_pages): + vaddr: t.CUInt64T = (user_stack_vbottom & ~t.CUInt64T(0xFFF)) + si * t.CUInt64T(0x1000) + paddr: t.CUInt64T = phys_bottom + si * t.CUInt64T(0x1000) + paging.MapPage(vaddr, paddr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + # Set TSS RSP0 so interrupts from Ring 3 use kernel stack + gdt.set_tss_rsp0(cur_p.kern_stack) + # Set GS kernel_rsp0 for syscall entry from Ring 3 + cpu.set_kernel_rsp0(cur_p.kern_stack) + # Keep interrupts disabled until iretq: drop_to_user_mode's iretq + # will set RFLAGS.IF from the saved frame (0x202) for user mode. + # This prevents timer interrupt preemption between TSS setup and iretq. + # Drop to Ring 3 user mode via iretq + proc.drop_to_user_mode(entry_fn, user_stack_vtop, cur_p.kern_stack) + cur: proc.Process | t.CPtr = proc.ProcessManager.current() + cur.exit() + return 0 + +def spawn_elf(path: t.CConst | str) -> t.CInt: + fat32.lock() + name_start: t.CInt = 0 + si: t.CInt = 0 + while True: + ch: t.CChar = path[si] + if ch == 0: break + if ch == 47: + name_start = si + 1 + si += 1 + + lowest_vaddr: t.CUInt64T = 0 + highest_vaddr_end: t.CUInt64T = 0 + fp_scan: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ) + if fp_scan is None: + serr: t.CUInt32T = fat32.last_error() + seb: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(seb), 64, "[elf] spawn scan open failed err=%d\n", serr) + serial.puts(seb) + fat32.unlock() + return -1 + if fp_scan is not None: + ehdr_buf_s: t.CArray[t.CUInt8T, 64] + string.memset(c.Addr(ehdr_buf_s), 0, 64) + fat32.read(fp_scan, c.Addr(ehdr_buf_s), 64) + ehdr_s: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf_s) + if ehdr_s.e_type == ET_EXEC or ehdr_s.e_type == ET_DYN: + phoff_s: t.CUInt64T = ehdr_s.e_phoff + phentsize_s: t.CUInt16T = ehdr_s.e_phentsize + phnum_s: t.CUInt16T = ehdr_s.e_phnum + if phentsize_s > 0 and phnum_s > 0 and phnum_s <= MAX_PHDRS: + phdr_bufs_s: t.CArray[Elf64_Phdr, MAX_PHDRS] + string.memset(c.Addr(phdr_bufs_s), 0, MAX_PHDRS * 56) + found_load_s: t.CInt = 0 + for pi_s in range(phnum_s): + fat32.seek(fp_scan, t.CUInt32T(phoff_s + t.CUInt64T(pi_s) * t.CUInt64T(phentsize_s))) + fat32.read(fp_scan, c.Addr(phdr_bufs_s[pi_s]), 56) + if phdr_bufs_s[pi_s].p_type == PT_LOAD: + seg_end_s: t.CUInt64T = phdr_bufs_s[pi_s].p_vaddr + phdr_bufs_s[pi_s].p_memsz + if found_load_s == 0: + lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr + highest_vaddr_end = seg_end_s + found_load_s = 1 + else: + if phdr_bufs_s[pi_s].p_vaddr < lowest_vaddr: + lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr + if seg_end_s > highest_vaddr_end: + highest_vaddr_end = seg_end_s + fat32.close(fp_scan) + + isolate_va: t.CUInt64T = lowest_vaddr + if isolate_va == 0: + isolate_va = 0x400000 + + # Load ELF to physical memory WITHOUT mapping in kernel PML4. + # do_map=0: load_elf stores mapping info in globals for spawn_elf to use. + # ROOT CAUSE FIX: previously load_elf mapped ELF vaddr (e.g. 0x400000) into + # kernel PML4, overwriting identity mapping of buddy memory_block headers. + entry: t.CVoid | t.CPtr = load_elf(path, 0) + if entry is None: + serial.puts("[elf] spawn: load failed\n") + fat32.unlock() + return -1 + + # Clone kernel PML4 (identity mapping intact, no ELF mapping yet) + proc_pml4: t.CUInt64T = paging.clone_for_process(isolate_va) + slog1: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(slog1), 80, "[elf] spawn: clone pml4=0x%lx va=0x%lx\n", proc_pml4, isolate_va) + serial.puts(slog1) + if proc_pml4 == 0: + serial.puts("[elf] spawn: clone_for_process failed\n") + fat32.unlock() + return -1 + + # Map ELF pages in the CLONED PML4 (not kernel's). + # This prevents overwriting kernel identity mapping at 0x400000 which + # would corrupt buddy system memory_block headers. + # Must disable interrupts: @pml4 global is used by MapPage and a timer + # interrupt mid-section would call switch_pid, corrupting @pml4. + asm.cli() + _kernel_cr3: t.CUInt64T = 0 + c.Asm(f"""mov {c.AsmOut(_kernel_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3""", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + c.Asm(f"mov cr3, {c.AsmInp(proc_pml4, t.ASM_DESCR.REG_ANY)}", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + paging.set_active_pml4(proc_pml4) + mi: t.CUInt64T + for mi in range(_last_elf_num_map_pages): + _va: t.CUInt64T = _last_elf_virt_page_start + mi * 0x1000 + _pa: t.CUInt64T = _last_elf_phys_page_start + mi * 0x1000 + paging.MapPage(_va, _pa, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + c.Asm(f"mov cr3, {c.AsmInp(_kernel_cr3, t.ASM_DESCR.REG_ANY)}", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + paging.set_active_pml4(_kernel_cr3) + asm.sti() + + p: proc.Process | t.CPtr = proc.ProcessManager.create_process(path[name_start:], entry) + if p is None: + serial.puts("[elf] spawn: create_process failed\n") + fat32.unlock() + return -2 + p.elf_base = t.CUInt64T(entry) + p.pml4_root = proc_pml4 + if p.pid >= 0 and p.pid < MAX_SPAWNED: + _elf_entries[p.pid] = entry + + th: sched.Thread | t.CPtr = sched.Scheduler.create_thread(_elf_entry_wrapper, t.CVoid(t.CUInt64T(p.pid), t.CPtr), p.pid) + if th is None: + serial.puts("[elf] spawn: create_thread failed\n") + p.exit() + fat32.unlock() + return -3 + tid: t.CInt = th.tid + p.addThread(tid) + slog: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(slog), 80, "[elf] spawn: pid=%d tid=%d entry=0x%lx pml4=0x%lx\n", p.pid, tid, t.CUInt64T(entry), p.pml4_root) + serial.puts(slog) + fat32.unlock() + return p.pid + +def _resolve_symbol(name: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CPtr: + nm: t.CInt = 0 + while c.Deref(name + nm) != 0: + nm += 1 + cmp_name: t.CConst | t.CChar | t.CPtr = name + cmp_len: t.CInt = nm + if nm > 9: + sha1_len2: t.CInt = 0 + for si2 in range(nm): + ch2: t.CChar = c.Deref(name + si2) + if ch2 == 46: + if sha1_len2 >= 8: + cmp_name = t.CConst(t.CUInt64T(name) + si2 + 1, t.CPtr) + cmp_len = nm - si2 - 1 + break + if ch2 == 0: + break + if (ch2 >= 48 and ch2 <= 57) or (ch2 >= 97 and ch2 <= 102): + sha1_len2 += 1 + else: + break + if cmp_len == 11: + match: t.CInt = 1 + for i in range(cmp_len): + if c.Deref(cmp_name + i) != c.Deref("serial_puts" + i): + match = 0 + if match: + return t.CVoid(c.Addr(serial.puts), t.CPtr) + return None + +_so_strtab: t.CVoid | t.CPtr +_so_symtab: t.CVoid | t.CPtr +_so_syment: t.CUInt64T +_so_sym_count: t.CInt + +def load_so(path: t.CConst | str) -> t.CVoid | t.CPtr: + fat32.lock() + fp: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ) + if fp is None: + serial.puts("[so] failed to open file\n") + fat32.unlock() + return None + + ehdr_buf: t.CArray[t.CUInt8T, 64] + string.memset(c.Addr(ehdr_buf), 0, 64) + read_len: t.CUInt32T = fat32.read(fp, c.Addr(ehdr_buf), 64) + if read_len < 64: + serial.puts("[so] failed to read ehdr\n") + fat32.close(fp) + fat32.unlock() + return None + + ehdr: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf) + if ehdr.e_ident[0] != ELFMAG0 or ehdr.e_ident[1] != ELFMAG1 or ehdr.e_ident[2] != ELFMAG2 or ehdr.e_ident[3] != ELFMAG3: + serial.puts("[so] bad magic\n") + fat32.close(fp) + fat32.unlock() + return None + if ehdr.e_ident[4] != ELFCLASS64: + serial.puts("[so] not ELF64\n") + fat32.close(fp) + fat32.unlock() + return None + if ehdr.e_type == ET_DYN: + pass + elif ehdr.e_type == ET_EXEC: + pass + else: + serial.puts("[so] not ET_DYN or ET_EXEC\n") + fat32.close(fp) + fat32.unlock() + return None + + phoff: t.CUInt64T = ehdr.e_phoff + phentsize: t.CUInt16T = ehdr.e_phentsize + phnum: t.CUInt16T = ehdr.e_phnum + + phdr_bufs: t.CArray[Elf64_Phdr, MAX_PHDRS] + string.memset(c.Addr(phdr_bufs), 0, MAX_PHDRS * 56) + for pi in range(phnum): + phdr_off: t.CUInt64T = phoff + t.CUInt64T(pi) * t.CUInt64T(phentsize) + fat32.seek(fp, t.CUInt32T(phdr_off)) + fat32.read(fp, c.Addr(phdr_bufs[pi]), 56) + + found_load: t.CInt = 0 + lowest_vaddr: t.CUInt64T = 0 + highest_vaddr_end: t.CUInt64T = 0 + for pi in range(phnum): + if phdr_bufs[pi].p_type == PT_LOAD: + seg_end: t.CUInt64T = phdr_bufs[pi].p_vaddr + phdr_bufs[pi].p_memsz + if found_load == 0: + lowest_vaddr = phdr_bufs[pi].p_vaddr + highest_vaddr_end = seg_end + found_load = 1 + else: + if phdr_bufs[pi].p_vaddr < lowest_vaddr: + lowest_vaddr = phdr_bufs[pi].p_vaddr + if seg_end > highest_vaddr_end: + highest_vaddr_end = seg_end + + if found_load == 0: + serial.puts("[so] no LOAD segments\n") + fat32.close(fp) + fat32.unlock() + return None + + total_memsz: t.CUInt64T = highest_vaddr_end - lowest_vaddr + total_pages: t.CUInt64T = (total_memsz + 0xFFF) // 0x1000 + + so_load_addr: t.CUInt64T = 0x600000 + raw_alloc: t.CVoid | t.CPtr = mm.malloc(total_pages * 0x1000 + 0x1000) + if not raw_alloc: + serial.puts("[so] malloc failed\n") + fat32.close(fp) + fat32.unlock() + return None + + phys_base_aligned: t.CUInt64T = (t.CUInt64T(raw_alloc) + 0xFFF) & ~t.CUInt64T(0xFFF) + phys_base: t.CVoid | t.CPtr = t.CVoid(phys_base_aligned, t.CPtr) + string.memset(phys_base, 0, total_pages * 0x1000) + + for pi in range(phnum): + if phdr_bufs[pi].p_type == PT_LOAD: + vaddr: t.CUInt64T = phdr_bufs[pi].p_vaddr + file_off: t.CUInt64T = phdr_bufs[pi].p_offset + filesz: t.CUInt64T = phdr_bufs[pi].p_filesz + memsz: t.CUInt64T = phdr_bufs[pi].p_memsz + dest_off: t.CUInt64T = vaddr - lowest_vaddr + dest: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(phys_base) + dest_off, t.CPtr) + if filesz > 0: + fat32.seek(fp, t.CUInt32T(file_off)) + fat32.read(fp, dest, t.CUInt32T(filesz)) + if memsz > filesz: + bss_start: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(dest) + filesz, t.CPtr) + bss_len: t.CUInt64T = memsz - filesz + string.memset(bss_start, 0, bss_len) + + virt_page_start: t.CUInt64T = (so_load_addr + lowest_vaddr) & ~t.CUInt64T(0xFFF) + phys_page_start: t.CUInt64T = t.CUInt64T(phys_base) & ~t.CUInt64T(0xFFF) + map_end: t.CUInt64T = (so_load_addr + highest_vaddr_end + 0xFFF) & ~t.CUInt64T(0xFFF) + num_map_pages: t.CUInt64T = (map_end - virt_page_start) // 0x1000 + + for mi in range(num_map_pages): + virt_addr: t.CUInt64T = virt_page_start + mi * 0x1000 + phys_addr: t.CUInt64T = phys_page_start + mi * 0x1000 + paging.MapPage(virt_addr, phys_addr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + + base_vaddr: t.CUInt64T = so_load_addr + lowest_vaddr + + dyn_buf: t.CArray[Elf64_Dyn, MAX_DYNS] + string.memset(c.Addr(dyn_buf), 0, MAX_DYNS * 16) + dyn_count: t.CInt = 0 + for pi in range(phnum): + if phdr_bufs[pi].p_type == PT_DYNAMIC: + fat32.seek(fp, t.CUInt32T(phdr_bufs[pi].p_offset)) + dyn_read: t.CUInt32T = fat32.read(fp, c.Addr(dyn_buf), t.CUInt32T(phdr_bufs[pi].p_filesz)) + dyn_count = t.CInt(dyn_read) // 16 + + strtab_vaddr: t.CUInt64T = 0 + symtab_vaddr: t.CUInt64T = 0 + hash_vaddr: t.CUInt64T = 0 + syment: t.CUInt64T = 0 + rela_vaddr: t.CUInt64T = 0 + relasz: t.CUInt64T = 0 + jmprel_vaddr: t.CUInt64T = 0 + pltrelsz: t.CUInt64T = 0 + for di in range(dyn_count): + if dyn_buf[di].d_tag == DT_NULL: break + if dyn_buf[di].d_tag == DT_STRTAB: strtab_vaddr = dyn_buf[di].d_val + elif dyn_buf[di].d_tag == DT_SYMTAB: symtab_vaddr = dyn_buf[di].d_val + elif dyn_buf[di].d_tag == DT_HASH: hash_vaddr = dyn_buf[di].d_val + elif dyn_buf[di].d_tag == DT_SYMENT: syment = dyn_buf[di].d_val + elif dyn_buf[di].d_tag == DT_RELA: rela_vaddr = dyn_buf[di].d_val + elif dyn_buf[di].d_tag == DT_RELASZ: relasz = dyn_buf[di].d_val + elif dyn_buf[di].d_tag == DT_JMPREL: jmprel_vaddr = dyn_buf[di].d_val + elif dyn_buf[di].d_tag == DT_PLTRELSZ: pltrelsz = dyn_buf[di].d_val + + strtab_ptr: t.CVoid | t.CPtr = t.CVoid(strtab_vaddr + so_load_addr, t.CPtr) + symtab_ptr: t.CVoid | t.CPtr = t.CVoid(symtab_vaddr + so_load_addr, t.CPtr) + + if rela_vaddr != 0 and relasz > 0: + rela_count: t.CUInt64T = relasz // 24 + for ri in range(rela_count): + rela: Elf64_Rela | t.CPtr = t.CVoid(t.CUInt64T(rela_vaddr) + so_load_addr + ri * 24, t.CPtr) + r_type: t.CUInt32T = t.CUInt32T(rela.r_info & 0xFFFFFFFF) + r_sym: t.CUInt32T = t.CUInt32T(rela.r_info >> 32) + target_addr: t.CVoid | t.CPtr = t.CVoid(rela.r_offset + so_load_addr, t.CPtr) + if r_type == R_X86_64_RELATIVE: + val: t.CUInt64T = t.CUInt64T(rela.r_addend) + so_load_addr + string.memcpy(target_addr, c.Addr(val), 8) + elif r_type == R_X86_64_64 or r_type == R_X86_64_GLOB_DAT or r_type == R_X86_64_JUMP_SLOT: + sym: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(symtab_ptr) + t.CUInt64T(r_sym) * syment, t.CPtr) + sym_name: t.CConst | t.CChar | t.CPtr = t.CConst(t.CUInt64T(strtab_ptr) + sym.st_name, t.CPtr) + resolved: t.CVoid | t.CPtr = _resolve_symbol(sym_name) + if resolved is not None: + val2: t.CUInt64T = t.CUInt64T(resolved) + t.CUInt64T(rela.r_addend) + string.memcpy(target_addr, c.Addr(val2), 8) + elif sym.st_value != 0: + val3: t.CUInt64T = sym.st_value + so_load_addr + t.CUInt64T(rela.r_addend) + string.memcpy(target_addr, c.Addr(val3), 8) + + if jmprel_vaddr != 0 and pltrelsz > 0: + jmprel_count: t.CUInt64T = pltrelsz // 24 + for ji in range(jmprel_count): + rela2: Elf64_Rela | t.CPtr = t.CVoid(jmprel_vaddr + so_load_addr + ji * 24, t.CPtr) + r_type2: t.CUInt32T = t.CUInt32T(rela2.r_info & 0xFFFFFFFF) + r_sym2: t.CUInt32T = t.CUInt32T(rela2.r_info >> 32) + target2: t.CVoid | t.CPtr = t.CVoid(rela2.r_offset + so_load_addr, t.CPtr) + if r_type2 == R_X86_64_JUMP_SLOT or r_type2 == R_X86_64_GLOB_DAT: + sym2: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(symtab_ptr) + t.CUInt64T(r_sym2) * syment, t.CPtr) + sym_name2: t.CConst | t.CChar | t.CPtr = t.CConst(t.CUInt64T(strtab_ptr) + sym2.st_name, t.CPtr) + resolved2: t.CVoid | t.CPtr = _resolve_symbol(sym_name2) + if resolved2 is not None: + val4: t.CUInt64T = t.CUInt64T(resolved2) + t.CUInt64T(rela2.r_addend) + string.memcpy(target2, c.Addr(val4), 8) + elif sym2.st_value != 0: + val5: t.CUInt64T = sym2.st_value + so_load_addr + t.CUInt64T(rela2.r_addend) + string.memcpy(target2, c.Addr(val5), 8) + else: + cr3: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(cr3), 80, "[so] WARN: unresolved PLT sym offset=%lu\n", sym2.st_name) + serial.puts(cr3) + + fat32.close(fp) + + _so_strtab = strtab_ptr + _so_symtab = symtab_ptr + _so_syment = syment + _so_sym_count = 0 + if hash_vaddr != 0: + hash_ptr: t.CVoid | t.CPtr = t.CVoid(hash_vaddr + so_load_addr, t.CPtr) + nchain: t.CUInt32T = c.Deref(t.CVoid(t.CUInt64T(hash_ptr) + 4, t.CPtr)) + _so_sym_count = t.CInt(nchain) + elif syment > 0 and strtab_vaddr > symtab_vaddr and symtab_vaddr != 0: + sym_tab_size: t.CUInt64T = strtab_vaddr - symtab_vaddr + _so_sym_count = t.CInt(sym_tab_size // syment) + + cr: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(cr), 80, "[so] loaded at 0x%lx\n", base_vaddr) + serial.puts(cr) + + fat32.unlock() + return t.CVoid(base_vaddr, t.CPtr) + +def get_so_symbol(so_base: t.CVoid | t.CPtr, name: t.CConst | t.CChar | t.CPtr, so_path: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CPtr: + if _so_symtab is None or _so_strtab is None or _so_syment == 0: + return None + if name is None: + return None + for symi in range(_so_sym_count): + sb2: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(_so_symtab) + t.CUInt64T(symi) * _so_syment, t.CPtr) + if sb2.st_value != 0 and sb2.st_shndx != 0: + sym_name_off2: t.CUInt32T = sb2.st_name + cmp_off2: t.CUInt32T = sym_name_off2 + match2: t.CInt = 0 + ni2: t.CInt = 0 + while True: + sc3: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + cmp_off2 + ni2, t.CPtr)) + nc3: t.CChar = c.Deref(name + ni2) + if sc3 != nc3: + break + if sc3 == 0: + match2 = 1 + break + ni2 += 1 + if match2 == 0: + dot_pos3: t.CUInt32T = sym_name_off2 + is_sha1_2: t.CInt = 0 + sha1_len_2: t.CUInt32T = 0 + while True: + dc4: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + dot_pos3, t.CPtr)) + if dc4 == 46: + if sha1_len_2 >= 8: + is_sha1_2 = 1 + break + if dc4 == 0: + break + if (dc4 >= 48 and dc4 <= 57) or (dc4 >= 97 and dc4 <= 102): + sha1_len_2 += 1 + else: + break + dot_pos3 += 1 + if is_sha1_2: + cmp_off2 = dot_pos3 + 1 + match2 = 1 + ni2 = 0 + while True: + sc4: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + cmp_off2 + ni2, t.CPtr)) + nc4: t.CChar = c.Deref(name + ni2) + if sc4 != nc4: + match2 = 0 + break + if sc4 == 0: + break + ni2 += 1 + if match2: + result_addr: t.CUInt64T = t.CUInt64T(so_base) + sb2.st_value + dbg2: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(dbg2), 80, "[so] found '%s' at 0x%lx\n", t.CVoid(t.CUInt64T(_so_strtab) + sym_name_off2, t.CPtr), result_addr) + serial.puts(dbg2) + return t.CVoid(result_addr, t.CPtr) + serial.puts("[so] symbol not found\n") + return None diff --git a/VKernel/Kernel/intr/__init__.py b/VKernel/Kernel/intr/__init__.py new file mode 100644 index 0000000..eede123 --- /dev/null +++ b/VKernel/Kernel/intr/__init__.py @@ -0,0 +1,2 @@ +from . import gdt +from . import idt diff --git a/VKernel/Kernel/intr/gdt.py b/VKernel/Kernel/intr/gdt.py new file mode 100644 index 0000000..524573e --- /dev/null +++ b/VKernel/Kernel/intr/gdt.py @@ -0,0 +1,142 @@ +import t, c + + +@c.Attribute(t.attr.packed) +class gdt_entry: + limit_low: t.CUInt16T + base_low: t.CUInt16T + base_middle: t.CUInt8T + access: t.CUInt8T + granularity: t.CUInt8T + base_high: t.CUInt8T + +@c.Attribute(t.attr.packed) +class gdt_entry64: + limit_low: t.CUInt16T + base_low: t.CUInt16T + base_middle: t.CUInt8T + access: t.CUInt8T + granularity: t.CUInt8T + base_high: t.CUInt8T + base_upper: t.CUInt32T + reserved: t.CUInt32T + +@c.Attribute(t.attr.packed) +class gdt_ptr: + limit: t.CUInt16T + base: t.CUInt64T + +gdt: t.CArray[gdt_entry, 7] = [] +gp: t.CArray[t.CUInt8T, 10] +# TSS buffer: 104 bytes, raw byte array to avoid packed struct issues +# x86_64 TSS layout: reserved0(4) rsp0(8) rsp1(8) rsp2(8) reserved1(8) ist1-ist7(56) reserved2(8) reserved3(2) iomap_base(2) +tss_buf: t.CArray[t.CUInt8T, 104] + +CODE_SEG: t.CDefine | t.CUInt16T = 0x08 +DATA_SEG: t.CDefine | t.CUInt16T = 0x10 +USER_CODE_SEG: t.CDefine | t.CUInt16T = 0x18 +USER_DATA_SEG: t.CDefine | t.CUInt16T = 0x20 +TSS_SEG: t.CDefine | t.CUInt16T = 0x28 + + +def init(): + global gp, gdt, tss_buf + gdt[0].limit_low = 0 + gdt[0].base_low = 0 + gdt[0].base_middle = 0 + gdt[0].access = 0 + gdt[0].granularity = 0 + gdt[0].base_high = 0 + gdt[1].limit_low = 0 + gdt[1].base_low = 0 + gdt[1].base_middle = 0 + gdt[1].access = 0x9A + gdt[1].granularity = 0x20 + gdt[1].base_high = 0 + gdt[2].limit_low = 0 + gdt[2].base_low = 0 + gdt[2].base_middle = 0 + gdt[2].access = 0x92 + gdt[2].granularity = 0xCF + gdt[2].base_high = 0 + gdt[3].limit_low = 0 + gdt[3].base_low = 0 + gdt[3].base_middle = 0 + gdt[3].access = 0xFA + gdt[3].granularity = 0x20 + gdt[3].base_high = 0 + gdt[4].limit_low = 0 + gdt[4].base_low = 0 + gdt[4].base_middle = 0 + gdt[4].access = 0xF2 + gdt[4].granularity = 0xCF + gdt[4].base_high = 0 + # Set up TSS descriptor in GDT entries 5-6 (selector 0x28) + setTSS64(5, t.CUInt64T(c.Addr(tss_buf)), 103) + c.Asm("""lea rax, [rip + gdt] + lea rcx, [rip + gp] + mov word ptr [rcx], 0x37 + mov qword ptr [rcx+2], rax""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX]) + c.Asm("sfence; mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + flush() + # Initialize TSS: set iomap_base = 104 (0x68) at offset 102 + tss_buf[102] = 0x68 + tss_buf[103] = 0x00 + # Load Task Register with TSS selector 0x28 + c.Asm("""mov ax, 0x28 + ltr ax""", + op=[t.ASM_DESCR.CLOBBER_RAX]) + +@c.Attribute(t.attr.naked) +def flush(): + c.Asm("""lea rax, [rip + gp] + lgdt [rax] + push 0x08 + lea rax, [rip+2f] + push rax + .byte 0x48, 0xcb + 2: + mov eax, 0x10 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + ret""", op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + +def set_tss_rsp0(rsp0: t.CUInt64T): + global tss_buf + # TSS RSP0 is at offset 4 (8 bytes) in x86_64 TSS layout + tss_buf[4] = t.CUInt8T(rsp0 & 0xFF) + tss_buf[5] = t.CUInt8T((rsp0 >> 8) & 0xFF) + tss_buf[6] = t.CUInt8T((rsp0 >> 16) & 0xFF) + tss_buf[7] = t.CUInt8T((rsp0 >> 24) & 0xFF) + tss_buf[8] = t.CUInt8T((rsp0 >> 32) & 0xFF) + tss_buf[9] = t.CUInt8T((rsp0 >> 40) & 0xFF) + tss_buf[10] = t.CUInt8T((rsp0 >> 48) & 0xFF) + tss_buf[11] = t.CUInt8T((rsp0 >> 56) & 0xFF) + +def setGate(num: int, base: t.CUInt64T, limit: t.CUInt32T, access: t.CUInt8T, gran: t.CUInt8T): + global gdt + gdt[num].limit_low = (limit & 0xFFFF) + gdt[num].base_low = (base & 0xFFFF) + gdt[num].base_middle = (base >> 16) & 0xFF + gdt[num].base_high = (base >> 24) & 0xFF + gdt[num].granularity = ((limit >> 16) & 0x0F) | (gran & 0xF0) + gdt[num].access = access + +def setTSS64(num: int, base: t.CUInt64T, limit: t.CUInt32T): + global gdt + gdt[num].limit_low = limit & 0xFFFF + gdt[num].base_low = base & 0xFFFF + gdt[num].base_middle = (base >> 16) & 0xFF + gdt[num].access = 0x89 + gdt[num].granularity = 0x00 + gdt[num].base_high = (base >> 24) & 0xFF + gdt[num + 1].limit_low = (base >> 32) & 0xFFFF + gdt[num + 1].base_low = (base >> 48) & 0xFFFF + gdt[num + 1].base_middle = 0 + gdt[num + 1].access = 0 + gdt[num + 1].granularity = 0 + gdt[num + 1].base_high = 0 diff --git a/VKernel/Kernel/intr/idt.py b/VKernel/Kernel/intr/idt.py new file mode 100644 index 0000000..50cbc8f --- /dev/null +++ b/VKernel/Kernel/intr/idt.py @@ -0,0 +1,415 @@ +import platform.pch.pic as pic +import platform.pch.timer as timer +import sched.sched as sched +import drivers.serial.uart.serial as serial +import viperlib +import asm +import t, c + +CODE_SEG: t.CDefine | t.CUInt16T = 0x08 + +@c.Attribute(t.attr.packed) +class idt_entry: + base_low: t.CUInt16T + selector: t.CUInt16T + ist_attr: t.CUInt8T + type_attr: t.CUInt8T + base_middle: t.CUInt16T + base_high: t.CUInt32T + reserved1: t.CUInt32T +idt: t.CArray[idt_entry, 256] = [] + +irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] + +def isr0() -> t.CExtern | t.CVoid | t.State: pass +def isr1() -> t.CExtern | t.CVoid | t.State: pass +def isr2() -> t.CExtern | t.CVoid | t.State: pass +def isr3() -> t.CExtern | t.CVoid | t.State: pass +def isr4() -> t.CExtern | t.CVoid | t.State: pass +def isr5() -> t.CExtern | t.CVoid | t.State: pass +def isr6() -> t.CExtern | t.CVoid | t.State: pass +def isr7() -> t.CExtern | t.CVoid | t.State: pass +def isr8() -> t.CExtern | t.CVoid | t.State: pass +def isr9() -> t.CExtern | t.CVoid | t.State: pass +def isr10() -> t.CExtern | t.CVoid | t.State: pass +def isr11() -> t.CExtern | t.CVoid | t.State: pass +def isr12() -> t.CExtern | t.CVoid | t.State: pass +def isr13() -> t.CExtern | t.CVoid | t.State: pass +def isr14() -> t.CExtern | t.CVoid | t.State: pass +def isr15() -> t.CExtern | t.CVoid | t.State: pass +def isr16() -> t.CExtern | t.CVoid | t.State: pass +def isr17() -> t.CExtern | t.CVoid | t.State: pass +def isr18() -> t.CExtern | t.CVoid | t.State: pass +def isr19() -> t.CExtern | t.CVoid | t.State: pass +def isr20() -> t.CExtern | t.CVoid | t.State: pass +def isr21() -> t.CExtern | t.CVoid | t.State: pass +def isr22() -> t.CExtern | t.CVoid | t.State: pass +def isr23() -> t.CExtern | t.CVoid | t.State: pass +def isr24() -> t.CExtern | t.CVoid | t.State: pass +def isr25() -> t.CExtern | t.CVoid | t.State: pass +def isr26() -> t.CExtern | t.CVoid | t.State: pass +def isr27() -> t.CExtern | t.CVoid | t.State: pass +def isr28() -> t.CExtern | t.CVoid | t.State: pass +def isr29() -> t.CExtern | t.CVoid | t.State: pass +def isr30() -> t.CExtern | t.CVoid | t.State: pass +def isr31() -> t.CExtern | t.CVoid | t.State: pass +def isr32() -> t.CExtern | t.CVoid | t.State: pass +def isr33() -> t.CExtern | t.CVoid | t.State: pass +def isr34() -> t.CExtern | t.CVoid | t.State: pass +def isr35() -> t.CExtern | t.CVoid | t.State: pass +def isr36() -> t.CExtern | t.CVoid | t.State: pass +def isr37() -> t.CExtern | t.CVoid | t.State: pass +def isr38() -> t.CExtern | t.CVoid | t.State: pass +def isr39() -> t.CExtern | t.CVoid | t.State: pass +def isr40() -> t.CExtern | t.CVoid | t.State: pass +def isr41() -> t.CExtern | t.CVoid | t.State: pass +def isr42() -> t.CExtern | t.CVoid | t.State: pass +def isr43() -> t.CExtern | t.CVoid | t.State: pass +def isr44() -> t.CExtern | t.CVoid | t.State: pass +def isr45() -> t.CExtern | t.CVoid | t.State: pass +def isr46() -> t.CExtern | t.CVoid | t.State: pass +def isr47() -> t.CExtern | t.CVoid | t.State: pass + +def isr80() -> t.CExtern | t.CVoid | t.State: pass + +def isr_default() -> t.CExtern | t.CVoid | t.State: pass + +def setGate(num: int, base: t.CUInt64T, sel: t.CUInt16T, flags: t.CUInt8T): + global idt + idt[num].base_low = (base & 0xFFFF) + idt[num].base_middle = (base >> 16) & 0xFFFF + idt[num].base_high = (base >> 32) & 0xFFFFFFFF + idt[num].selector = sel + idt[num].ist_attr = 0 + idt[num].type_attr = flags + idt[num].reserved1 = 0 + +def isrCommonhandler(interrupt: t.CInt): + c.Asm(f"""mov dx, 0x3F8 + mov al, 91 + out dx, al + mov ecx, {c.AsmInp(interrupt, t.ASM_DESCR.REG_ANY)} + mov rax, rcx + shr rax, 4 + and al, 15 + add al, 48 + cmp al, 57 + jle 1f + add al, 7 + 1: + out dx, al + mov rax, rcx + and al, 15 + add al, 48 + cmp al, 57 + jle 2f + add al, 7 + 2: + out dx, al + mov al, 93 + out dx, al""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX]) + # Print CR2 for page faults (ISR 14) + if interrupt == 14: + cr2: t.CUInt64T = 0 + c.Asm(f"""mov {c.AsmOut(cr2, t.ASM_DESCR.OUTPUT_REG)}, cr2""", + op=[t.ASM_DESCR.CLOBBER_RAX]) + fault_rip: t.CUInt64T = 0 + fault_err: t.CUInt64T = 0 + c.Asm(f"""mov {c.AsmOut(fault_rip, t.ASM_DESCR.OUTPUT_REG)}, [_fault_rip] + mov {c.AsmOut(fault_err, t.ASM_DESCR.OUTPUT_REG)}, [_fault_err]""", + op=[t.ASM_DESCR.CLOBBER_RAX]) + buf: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(buf), 80, "[PF] cr2=0x%lx rip=0x%lx err=0x%lx\n", cr2, fault_rip, fault_err) + serial.puts(buf) + elif interrupt == 13: + gp_rip: t.CUInt64T = 0 + gp_err: t.CUInt64T = 0 + c.Asm(f"""mov {c.AsmOut(gp_rip, t.ASM_DESCR.OUTPUT_REG)}, [_fault_rip] + mov {c.AsmOut(gp_err, t.ASM_DESCR.OUTPUT_REG)}, [_fault_err]""", + op=[t.ASM_DESCR.CLOBBER_RAX]) + gp_buf: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(gp_buf), 80, "[GP] rip=0x%lx err=0x%lx\n", gp_rip, gp_err) + serial.puts(gp_buf) + s: sched.Scheduler | t.CPtr = sched._sched_ptr + # 如果调度器未初始化,直接进入安全停机状态 + if s == 0: + serial.puts(" KERNEL PANIC (sched not init)\n") + while True: + asm.sti() + asm.hlt() + tid: t.CInt = s.current_tid + if tid == 0: + serial.puts(" KERNEL PANIC\n") + while True: + asm.sti() + asm.hlt() + buf2: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(buf2), 64, " exc in tid=%d, terminating\n", tid) + serial.puts(buf2) + s.threads[tid].state = 3 + # Restore GS to kernel-normal state before _yield(). + # ISR entry does swapgs for user-mode exceptions (GS_BASE=&_per_cpu). + # Since _yield() switches to another thread and never returns via ISR exit, + # the swapgs at ISR exit is never executed. We must swap back here. + # Check IA32_GS_BASE (MSR 0xC0000101): if non-zero, swapgs was done at entry. + c.Asm(f"""mov ecx, 0xC0000101 + rdmsr + shl rdx, 32 + or rax, rdx + test rax, rax + jz 1f + swapgs + 1:""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX]) + sched.Scheduler._yield() + while True: + asm.sti() + asm.hlt() + +def isr0_handler() -> t.CExport | t.CVoid: isrCommonhandler(0) +def isr1_handler() -> t.CExport | t.CVoid: isrCommonhandler(1) +def isr2_handler() -> t.CExport | t.CVoid: isrCommonhandler(2) +def isr3_handler() -> t.CExport | t.CVoid: isrCommonhandler(3) +def isr4_handler() -> t.CExport | t.CVoid: isrCommonhandler(4) +def isr5_handler() -> t.CExport | t.CVoid: isrCommonhandler(5) +def isr6_handler() -> t.CExport | t.CVoid: isrCommonhandler(6) +def isr7_handler() -> t.CExport | t.CVoid: isrCommonhandler(7) +def isr8_handler() -> t.CExport | t.CVoid: isrCommonhandler(8) +def isr9_handler() -> t.CExport | t.CVoid: isrCommonhandler(9) +def isr10_handler() -> t.CExport | t.CVoid: isrCommonhandler(10) +def isr11_handler() -> t.CExport | t.CVoid: isrCommonhandler(11) +def isr12_handler() -> t.CExport | t.CVoid: isrCommonhandler(12) +def isr13_handler() -> t.CExport | t.CVoid: + isrCommonhandler(13) +def isr14_handler() -> t.CExport | t.CVoid: isrCommonhandler(14) +def isr15_handler() -> t.CExport | t.CVoid: isrCommonhandler(15) +def isr16_handler() -> t.CExport | t.CVoid: isrCommonhandler(16) +def isr17_handler() -> t.CExport | t.CVoid: isrCommonhandler(17) +def isr18_handler() -> t.CExport | t.CVoid: isrCommonhandler(18) +def isr19_handler() -> t.CExport | t.CVoid: isrCommonhandler(19) +def isr20_handler() -> t.CExport | t.CVoid: isrCommonhandler(20) +def isr21_handler() -> t.CExport | t.CVoid: isrCommonhandler(21) +def isr22_handler() -> t.CExport | t.CVoid: isrCommonhandler(22) +def isr23_handler() -> t.CExport | t.CVoid: isrCommonhandler(23) +def isr24_handler() -> t.CExport | t.CVoid: isrCommonhandler(24) +def isr25_handler() -> t.CExport | t.CVoid: isrCommonhandler(25) +def isr26_handler() -> t.CExport | t.CVoid: isrCommonhandler(26) +def isr27_handler() -> t.CExport | t.CVoid: isrCommonhandler(27) +def isr28_handler() -> t.CExport | t.CVoid: isrCommonhandler(28) +def isr29_handler() -> t.CExport | t.CVoid: isrCommonhandler(29) +def isr30_handler() -> t.CExport | t.CVoid: isrCommonhandler(30) +def isr31_handler() -> t.CExport | t.CVoid: isrCommonhandler(31) +def isr32_handler() -> t.CExport | t.CVoid: + timer.timer_handler() + +def isr33_handler() -> t.CExport | t.CVoid: + handler: irq_handler_t = irqGetHandler(1) + if handler: + c.Asm(f"""call {c.AsmInp(handler, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(1) + +def isr34_handler() -> t.CExport | t.CVoid: + handler2: irq_handler_t = irqGetHandler(2) + if handler2: + c.Asm(f"""call {c.AsmInp(handler2, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(2) + +def isr35_handler() -> t.CExport | t.CVoid: + handler3: irq_handler_t = irqGetHandler(3) + if handler3: + c.Asm(f"""call {c.AsmInp(handler3, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(3) + +def isr36_handler() -> t.CExport | t.CVoid: + handler4: irq_handler_t = irqGetHandler(4) + if handler4: + c.Asm(f"""call {c.AsmInp(handler4, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(4) + +def isr37_handler() -> t.CExport | t.CVoid: + handler5: irq_handler_t = irqGetHandler(5) + if handler5: + c.Asm(f"""call {c.AsmInp(handler5, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(5) + +def isr38_handler() -> t.CExport | t.CVoid: + handler6: irq_handler_t = irqGetHandler(6) + if handler6: + c.Asm(f"""call {c.AsmInp(handler6, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(6) + +def isr39_handler() -> t.CExport | t.CVoid: + if pic.isSpurious(7): + return + handler7: irq_handler_t = irqGetHandler(7) + if handler7: + c.Asm(f"""call {c.AsmInp(handler7, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(7) + +def isr40_handler() -> t.CExport | t.CVoid: + handler8: irq_handler_t = irqGetHandler(8) + if handler8: + c.Asm(f"""call {c.AsmInp(handler8, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(8) + +def isr41_handler() -> t.CExport | t.CVoid: + handler9: irq_handler_t = irqGetHandler(9) + if handler9: + c.Asm(f"""call {c.AsmInp(handler9, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(9) + +def isr42_handler() -> t.CExport | t.CVoid: + handler10: irq_handler_t = irqGetHandler(10) + if handler10: + c.Asm(f"""call {c.AsmInp(handler10, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(10) + +def isr43_handler() -> t.CExport | t.CVoid: + handler11: irq_handler_t = irqGetHandler(11) + if handler11: + c.Asm(f"""call {c.AsmInp(handler11, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(11) +def isr44_handler() -> t.CExport | t.CVoid: + handler: irq_handler_t = irqGetHandler(12) + if handler: + c.Asm(f"""call {c.AsmInp(handler, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + pic.eoi(12) + +def isr45_handler() -> t.CExport | t.CVoid: pic.eoi(13) +def isr46_handler() -> t.CExport | t.CVoid: pic.eoi(14) + +def isr47_handler() -> t.CExport | t.CVoid: + if pic.isSpurious(15): + return + pic.eoi(15) + +def init_exceptions(): + setGate(0, t.CUInt64T(isr0), CODE_SEG, 0x8E) + setGate(1, t.CUInt64T(isr1), CODE_SEG, 0x8E) + setGate(2, t.CUInt64T(isr2), CODE_SEG, 0x8E) + setGate(3, t.CUInt64T(isr3), CODE_SEG, 0x8E) + setGate(4, t.CUInt64T(isr4), CODE_SEG, 0x8E) + setGate(5, t.CUInt64T(isr5), CODE_SEG, 0x8E) + setGate(6, t.CUInt64T(isr6), CODE_SEG, 0x8E) + setGate(7, t.CUInt64T(isr7), CODE_SEG, 0x8E) + setGate(8, t.CUInt64T(isr8), CODE_SEG, 0x8E) + setGate(9, t.CUInt64T(isr9), CODE_SEG, 0x8E) + setGate(10, t.CUInt64T(isr10), CODE_SEG, 0x8E) + setGate(11, t.CUInt64T(isr11), CODE_SEG, 0x8E) + setGate(12, t.CUInt64T(isr12), CODE_SEG, 0x8E) + setGate(13, t.CUInt64T(isr13), CODE_SEG, 0x8E) + setGate(14, t.CUInt64T(isr14), CODE_SEG, 0x8E) + setGate(15, t.CUInt64T(isr15), CODE_SEG, 0x8E) + setGate(16, t.CUInt64T(isr16), CODE_SEG, 0x8E) + setGate(17, t.CUInt64T(isr17), CODE_SEG, 0x8E) + setGate(18, t.CUInt64T(isr18), CODE_SEG, 0x8E) + setGate(19, t.CUInt64T(isr19), CODE_SEG, 0x8E) + setGate(20, t.CUInt64T(isr20), CODE_SEG, 0x8E) + setGate(21, t.CUInt64T(isr21), CODE_SEG, 0x8E) + setGate(22, t.CUInt64T(isr22), CODE_SEG, 0x8E) + setGate(23, t.CUInt64T(isr23), CODE_SEG, 0x8E) + setGate(24, t.CUInt64T(isr24), CODE_SEG, 0x8E) + setGate(25, t.CUInt64T(isr25), CODE_SEG, 0x8E) + setGate(26, t.CUInt64T(isr26), CODE_SEG, 0x8E) + setGate(27, t.CUInt64T(isr27), CODE_SEG, 0x8E) + setGate(28, t.CUInt64T(isr28), CODE_SEG, 0x8E) + setGate(29, t.CUInt64T(isr29), CODE_SEG, 0x8E) + setGate(30, t.CUInt64T(isr30), CODE_SEG, 0x8E) + setGate(31, t.CUInt64T(isr31), CODE_SEG, 0x8E) + +def init_irqs(): + setGate(32, t.CUInt64T(isr32), CODE_SEG, 0x8E) + setGate(33, t.CUInt64T(isr33), CODE_SEG, 0x8E) + setGate(34, t.CUInt64T(isr34), CODE_SEG, 0x8E) + setGate(35, t.CUInt64T(isr35), CODE_SEG, 0x8E) + setGate(36, t.CUInt64T(isr36), CODE_SEG, 0x8E) + setGate(37, t.CUInt64T(isr37), CODE_SEG, 0x8E) + setGate(38, t.CUInt64T(isr38), CODE_SEG, 0x8E) + setGate(39, t.CUInt64T(isr39), CODE_SEG, 0x8E) + setGate(40, t.CUInt64T(isr40), CODE_SEG, 0x8E) + setGate(41, t.CUInt64T(isr41), CODE_SEG, 0x8E) + setGate(42, t.CUInt64T(isr42), CODE_SEG, 0x8E) + setGate(43, t.CUInt64T(isr43), CODE_SEG, 0x8E) + setGate(44, t.CUInt64T(isr44), CODE_SEG, 0x8E) + setGate(45, t.CUInt64T(isr45), CODE_SEG, 0x8E) + setGate(46, t.CUInt64T(isr46), CODE_SEG, 0x8E) + setGate(47, t.CUInt64T(isr47), CODE_SEG, 0x8E) + +irq_handlers: t.CStatic | t.CArray[irq_handler_t, 16] = [None] + +def irqInstallHandler(irq: t.CInt, handler: irq_handler_t, name: str) -> t.CInt: + global irq_handlers + if irq < 0 or irq > 15: return -1 + irq_handlers[irq] = handler + return 0 + +def irqGetHandler(irq: t.CInt) -> irq_handler_t: + if irq < 0 or irq > 15: return None + return irq_handlers[irq] + +def init(): + global idt + for i in range(256): + setGate(i, t.CUInt64T(isr_default), CODE_SEG, 0x8E) + pic.init() + init_exceptions() + init_irqs() + # Register ISR 0x80 for int 0x80 syscall interface (user-mode ELF apps) + setGate(0x80, t.CUInt64T(isr80), CODE_SEG, 0xEE) # DPL=3 allows Ring 3 to call int 0x80 + # Build idt_ptr on stack and load directly in one asm block + c.Asm("""lea rax, [rip + idt] + sub rsp, 16 + mov word ptr [rsp], 0x0fff + mov qword ptr [rsp+2], rax + lidt [rsp] + add rsp, 16""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) diff --git a/VKernel/Kernel/intr/isr.s b/VKernel/Kernel/intr/isr.s new file mode 100644 index 0000000..c03757f --- /dev/null +++ b/VKernel/Kernel/intr/isr.s @@ -0,0 +1,595 @@ +.section .bss +.comm _per_cpu, 32, 8 +.comm _proc_mgr, 8192, 16 +.comm _proc_mgr_ptr, 8, 8 +.comm _saved_cr3, 8, 8 +.comm _yield_cnt, 4, 4 +.comm _yield_lock, 4, 4 +.comm _fs_lock, 4, 4 +.comm _fault_rip, 8, 8 +.comm _fault_err, 8, 8 + +.section .text +.intel_syntax noprefix + +.globl isr_default +isr_default: + push rax + push rdx + mov al, 0x20 + out 0xA0, al + out 0x20, al + pop rdx + pop rax + iretq + +# ============================================================================= +# ISR macros with Ring 3 support +# +# When CPU takes interrupt/exception: +# From Ring 0: pushes RFLAGS, CS, RIP (3 qwords) +# From Ring 3: pushes SS, RSP, RFLAGS, CS, RIP (5 qwords) +# CPU also switches to TSS RSP0 stack when coming from Ring 3. +# +# Strategy: +# 1. Check CS RPL to determine if from Ring 3 +# 2. If from Ring 3: swapgs (make gs: point to per_cpu), save user RSP +# 3. Save full interrupt frame into registers +# 4. Re-push in uniform format with is_user flag +# 5. Call handler +# 6. On return, rebuild correct iretq frame based on is_user +# 7. If returning to Ring 3: swapgs back +# ============================================================================= + +.macro ISR_NOERR num +.globl isr\num +isr\num: + # Check CS RPL to determine if from Ring 3 + mov rax, [rsp + 8] # CS from interrupt frame + test al, 3 # RPL bits: 0 = Ring 0, 3 = Ring 3 + jnz isr\num\()_from_user + +isr\num\()_from_kernel: + # From Ring 0: no swapgs needed, already on kernel stack + mov r8, [rsp] # RIP + mov r9, [rsp + 8] # CS + mov r10, [rsp + 16] # RFLAGS + xor r11, r11 # SS = 0 + xor rax, rax # RSP = 0 + add rsp, 24 # remove the 3 CPU-pushed qwords + jmp isr\num\()_frame_ready + +isr\num\()_from_user: + # From Ring 3: swapgs to make gs: accessible, CPU already switched to TSS RSP0 + swapgs + # Save user RSP to per_cpu.user_rsp (gs:[0x0]) + # Note: CPU has already switched RSP to TSS RSP0, so the user RSP is on this stack + mov r8, [rsp] # RIP + mov r9, [rsp + 8] # CS + mov r10, [rsp + 16] # RFLAGS + mov rax, [rsp + 24] # RSP (user) + mov r11, [rsp + 32] # SS (user) + # Save user RSP to per_cpu for potential later use + mov gs:[0x0], rax + add rsp, 40 # remove the 5 CPU-pushed qwords + +isr\num\()_frame_ready: + # r8=RIP, r9=CS, r10=RFLAGS, rax=RSP(0 if kernel), r11=SS(0 if kernel) + # Build uniform frame on stack for iretq later + push r11 # SS + push rax # RSP + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + + # Push is_user flag (1 if from Ring 3, 0 if from Ring 0) + xor ecx, ecx + test r9b, 3 # test CS RPL + setnz cl # cl = 1 if from Ring 3 + push rcx # is_user + + # Push all general-purpose registers + push rax + push rbx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + # Save faulting RIP and error code for debug + mov [_fault_rip], r8 + mov [_fault_err], rdx + call isr\num\()_handler + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rbx + pop rax + + # Pop is_user flag + pop rcx # is_user + + # Pop the uniform frame into registers + pop r8 # RIP + pop r9 # CS + pop r10 # RFLAGS + pop rax # RSP (user) + pop r11 # SS (user) + + test rcx, rcx + jnz isr\num\()_ret_user + +isr\num\()_ret_kernel: + # Return to Ring 0: push RFLAGS, CS, RIP only + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + iretq + +isr\num\()_ret_user: + # Return to Ring 3: swapgs back, then push SS, RSP, RFLAGS, CS, RIP + swapgs + # DIAG+FIX: if RFLAGS.NT (bit 14) is set, iretq performs hardware task-return + # which loads TSS selector 0x28 -> #GP(err=0x28). Detect and clear it. + test r10, 0x4000 + jz 1f + btr r10, 14 + mov dx, 0x3F8 + mov al, 0x4E # 'N' marker = NT was set (now cleared) + out dx, al +1: + cmp r9, 0x1B + jne 8f + cmp r11, 0x23 + jne 8f + push r11 # SS + push rax # RSP + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + iretq +8: + mov dx, 0x3F8 + mov al, 0x42 # 'B' = bad CS/SS + out dx, al + mov al, 0x20 # space + out dx, al + # Output CS (r9) low byte as 2 hex digits + mov rcx, r9 + mov rax, rcx + shr rax, 4 + and rax, 0xF + add al, 0x30 + cmp al, 0x39 + jle 2f + add al, 7 +2: + out dx, al + mov rax, rcx + and rax, 0xF + add al, 0x30 + cmp al, 0x39 + jle 3f + add al, 7 +3: + out dx, al + mov al, 0x20 # space + out dx, al + # Output SS (r11) low byte as 2 hex digits + mov rcx, r11 + mov rax, rcx + shr rax, 4 + and rax, 0xF + add al, 0x30 + cmp al, 0x39 + jle 4f + add al, 7 +4: + out dx, al + mov rax, rcx + and rax, 0xF + add al, 0x30 + cmp al, 0x39 + jle 5f + add al, 7 +5: + out dx, al + mov al, 0x0A # newline + out dx, al + cli +7: + hlt + jmp 7b +.endm + +.macro ISR_ERR num +.globl isr\num +isr\num: + # Stack at entry (with error code): + # From Ring 0: [rsp]=error_code, [rsp+8]=RIP, [rsp+16]=CS, [rsp+24]=RFLAGS + # From Ring 3: [rsp]=error_code, [rsp+8]=RIP, [rsp+16]=CS, [rsp+24]=RFLAGS, [rsp+32]=RSP, [rsp+40]=SS + + # Check CS RPL + mov rax, [rsp + 16] # CS from interrupt frame + test al, 3 + jnz isr\num\()_from_user + +isr\num\()_from_kernel: + # From Ring 0: no swapgs needed + mov rdx, [rsp] # error_code + mov r8, [rsp + 8] # RIP + mov r9, [rsp + 16] # CS + mov r10, [rsp + 24] # RFLAGS + xor r11, r11 # SS = 0 + xor rax, rax # RSP = 0 + add rsp, 32 # remove 4 CPU-pushed qwords (error + 3 frame) + jmp isr\num\()_frame_ready + +isr\num\()_from_user: + # From Ring 3: swapgs, CPU already on TSS RSP0 + swapgs + mov rdx, [rsp] # error_code + mov r8, [rsp + 8] # RIP + mov r9, [rsp + 16] # CS + mov r10, [rsp + 24] # RFLAGS + mov rax, [rsp + 32] # RSP (user) + mov r11, [rsp + 40] # SS (user) + mov gs:[0x0], rax # save user RSP + add rsp, 48 # remove 6 CPU-pushed qwords (error + 5 frame) + +isr\num\()_frame_ready: + # rdx=error_code, r8=RIP, r9=CS, r10=RFLAGS, rax=RSP, r11=SS + # Build uniform frame on stack + push r11 # SS + push rax # RSP + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + push rdx # error_code + + # Push is_user flag + xor ecx, ecx + test r9b, 3 + setnz cl + push rcx # is_user + + # Push all general-purpose registers + push rax + push rbx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + # Save faulting RIP and error code for debug + mov [_fault_rip], r8 + mov [_fault_err], rdx + call isr\num\()_handler + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rbx + pop rax + + # Pop is_user flag + pop rcx # is_user + add rsp, 8 # skip error_code (handler already consumed it) + pop r8 # RIP + pop r9 # CS + pop r10 # RFLAGS + pop rax # RSP (user) + pop r11 # SS (user) + + test rcx, rcx + jnz isr\num\()_ret_user + +isr\num\()_ret_kernel: + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + iretq + +isr\num\()_ret_user: + swapgs + push r11 # SS + push rax # RSP + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + iretq +.endm + +ISR_NOERR 0 +ISR_NOERR 1 +ISR_NOERR 2 +ISR_NOERR 3 +ISR_NOERR 4 +ISR_NOERR 5 +ISR_NOERR 6 +ISR_NOERR 7 +ISR_ERR 8 +ISR_NOERR 9 +ISR_ERR 10 +ISR_ERR 11 +ISR_ERR 12 +ISR_ERR 13 +ISR_ERR 14 +ISR_NOERR 15 +ISR_NOERR 16 +ISR_ERR 17 +ISR_NOERR 18 +ISR_NOERR 19 +ISR_NOERR 20 +ISR_NOERR 21 +ISR_NOERR 22 +ISR_NOERR 23 +ISR_NOERR 24 +ISR_NOERR 25 +ISR_NOERR 26 +ISR_NOERR 27 +ISR_NOERR 28 +ISR_NOERR 29 +ISR_NOERR 30 +ISR_NOERR 31 +ISR_NOERR 32 +ISR_NOERR 33 +ISR_NOERR 34 +ISR_NOERR 35 +ISR_NOERR 36 +ISR_NOERR 37 +ISR_NOERR 38 +ISR_NOERR 39 +ISR_NOERR 40 +ISR_NOERR 41 +ISR_NOERR 42 +ISR_NOERR 43 +ISR_NOERR 44 +ISR_NOERR 45 +ISR_NOERR 46 +ISR_NOERR 47 + +.globl syscall_entry +syscall_entry: + mov rax, rsp + shr rax, 47 + test rax, rax + jnz syscall_from_kernel +syscall_from_user: + swapgs + mov gs:[0x0], rsp + mov rsp, gs:[0x8] + jmp syscall_common +syscall_from_kernel: +syscall_common: + push rcx + push r11 + push rbx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r12 + push r13 + push r14 + push r15 + mov rbp, rsp + and rsp, ~15 + push r9 + mov r9, r8 + mov r8, r10 + mov rcx, rdx + mov rdx, rsi + mov rsi, rdi + mov rdi, rax + call syscall_handler + add rsp, 8 + mov rsp, rbp + pop r15 + pop r14 + pop r13 + pop r12 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rbx + pop r11 + pop rcx + mov rax, rcx + shr rax, 47 + test rax, rax + jnz syscall_ret_kernel +syscall_ret_user: + mov rsp, gs:[0x0] + swapgs + push r11 + popfq + sysretq +syscall_ret_kernel: + push r11 + popfq + jmp rcx + +# ============================================================================= +# ISR 0x80 - int 0x80 syscall handler for user-mode ELF apps +# +# int 0x80 calling convention (ViperOS): +# rax = syscall number +# rbx = arg1 +# rcx = arg2 +# rdx = arg3 +# r10 = arg4 +# +# Translated to syscall_handler calling convention: +# rdi = n (syscall number) +# rsi = arg1 +# rdx = arg2 +# rcx = arg3 +# r8 = arg4 +# r9 = arg5 (0) +# stack = arg6 (0) +# +# Return value in rax. +# ============================================================================= +.globl isr80 +isr80: + # Check CS RPL to determine if from Ring 3 + mov rax, [rsp + 8] # CS from interrupt frame + test al, 3 + jnz isr80_from_user + +isr80_from_kernel: + mov r8, [rsp] # RIP + mov r9, [rsp + 8] # CS + mov r10, [rsp + 16] # RFLAGS + xor r11, r11 # SS = 0 + xor rax, rax # RSP = 0 + add rsp, 24 + jmp isr80_frame_ready + +isr80_from_user: + swapgs + mov r8, [rsp] # RIP + mov r9, [rsp + 8] # CS + mov r10, [rsp + 16] # RFLAGS + mov rax, [rsp + 24] # RSP (user) + mov r11, [rsp + 32] # SS (user) + mov gs:[0x0], rax + add rsp, 40 + +isr80_frame_ready: + # Build uniform frame on stack + push r11 # SS + push rax # RSP + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + + # Save ALL registers FIRST (including original rcx = arg2) + # Order: rax, rbx, rcx, rdx, rsi, rdi, rbp, r8, r9, r10, r11, r12, r13, r14, r15 + push rax # [rsp+112] rax (syscall number) + push rbx # [rsp+104] rbx (arg1) + push rcx # [rsp+96] rcx (arg2) + push rdx # [rsp+88] rdx (arg3) + push rsi # [rsp+80] + push rdi # [rsp+72] + push rbp # [rsp+64] + push r8 # [rsp+56] + push r9 # [rsp+48] + push r10 # [rsp+40] r10 (arg4) + push r11 # [rsp+32] + push r12 # [rsp+24] + push r13 # [rsp+16] + push r14 # [rsp+8] + push r15 # [rsp+0] + + # Calculate is_user from saved CS on stack + # CS is at [rsp + 15*8 + 16] = [rsp + 136] + mov rax, [rsp + 136] # CS from frame + xor ecx, ecx + test al, 3 + setnz cl + push rcx # is_user (pushed AFTER all registers) + + # Stack layout now: + # [rsp+0] = is_user + # [rsp+8] = r15 [rsp+16] = r14 [rsp+24] = r13 [rsp+32] = r12 + # [rsp+40] = r11 [rsp+48] = r10 [rsp+56] = r9 [rsp+64] = r8 + # [rsp+72] = rbp [rsp+80] = rdi [rsp+88] = rsi [rsp+96] = rdx + # [rsp+104] = rcx [rsp+112] = rbx [rsp+120] = rax + + # Translate int 0x80 calling convention to syscall_handler convention + mov rdi, [rsp + 120] # original rax = syscall number -> rdi + mov rsi, [rsp + 112] # original rbx = arg1 -> rsi + mov rdx, [rsp + 104] # original rcx = arg2 -> rdx + mov rcx, [rsp + 96] # original rdx = arg3 -> rcx + mov r8, [rsp + 48] # original r10 = arg4 -> r8 + xor r9, r9 # arg5 = 0 + push 0 # arg6 = 0 (on stack) + + call syscall_handler + + add rsp, 8 # remove arg6 + # Save return value by overwriting saved rax on stack + mov [rsp + 120], rax + + # Pop is_user flag + pop rcx # is_user + + # Restore registers + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx # restore original rcx (arg2) + pop rbx + pop rax # now has the return value + + # Pop frame + pop r8 # RIP + pop r9 # CS + pop r10 # RFLAGS + pop rax # RSP (user) + pop r11 # SS (user) + + test rcx, rcx + jnz isr80_ret_user + +isr80_ret_kernel: + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + iretq + +isr80_ret_user: + swapgs + push r11 # SS + push rax # RSP + push r10 # RFLAGS + push r9 # CS + push r8 # RIP + iretq diff --git a/VKernel/Kernel/intr/syscall.py b/VKernel/Kernel/intr/syscall.py new file mode 100644 index 0000000..d77cd3f --- /dev/null +++ b/VKernel/Kernel/intr/syscall.py @@ -0,0 +1,470 @@ +import drivers.serial.uart.serial as serial +import services.desktop as desktop +import drivers.fs.fat32.fat32 as fat32 +import drivers.fs.fat32.fat32_types as fat32_types +import sched.sched as sched +import sched.process as process +import viperlib +import string +import mm.mm as mm +import paging.paging as paging +import execrunner.elf as elf +import t, c + +CREATE_WINDOW: t.CDefine = 1 +DRAW_TEXT: t.CDefine = 2 +EXIT: t.CDefine = 3 +OPEN: t.CDefine = 4 +CLOSE: t.CDefine = 5 +READ: t.CDefine = 6 +WRITE: t.CDefine = 7 +SEEK: t.CDefine = 8 +TELL: t.CDefine = 9 +SIZE: t.CDefine = 10 +MKDIR: t.CDefine = 11 +REMOVE: t.CDefine = 12 +OPENDIR: t.CDefine = 13 +READDIR: t.CDefine = 14 +CLOSEDIR: t.CDefine = 15 +STAT: t.CDefine = 16 +WAIT_WINDOW: t.CDefine = 17 +SERIAL_PUTS: t.CDefine = 18 +LOAD_SO: t.CDefine = 19 +SO_CALL1: t.CDefine = 20 +GET_WINBUF: t.CDefine = 21 +WIN_FLUSH: t.CDefine = 22 +POLL_EVENT: t.CDefine = 23 +SET_CURSOR: t.CDefine = 24 +YIELD: t.CDefine = 25 +SET_TITLE: t.CDefine = 26 +SET_GEOMETRY: t.CDefine = 27 +SPAWN: t.CDefine = 28 +MMAP: t.CDefine = 29 +MUNMAP: t.CDefine = 30 +SET_RESIZABLE: t.CDefine = 31 +GET_WIN_SIZE: t.CDefine = 32 +THREAD_CREATE: t.CDefine = 33 + +IA32_EFER: t.CDefine = 0xC0000080 +IA32_STAR: t.CDefine = 0xC0000081 +IA32_LSTAR: t.CDefine = 0xC0000082 +IA32_FMASK: t.CDefine = 0xC0000084 + +def wrmsr(msr: t.CUInt32T, val: t.CUInt64T): + lo: t.CUInt32T = t.CUInt32T(val & 0xFFFFFFFF) + hi: t.CUInt32T = t.CUInt32T(val >> 32) + c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)} + mov eax, {c.AsmInp(lo, t.ASM_DESCR.REG_ANY)} + mov edx, {c.AsmInp(hi, t.ASM_DESCR.REG_ANY)} + wrmsr""", + op=[t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RDX]) + +def rdmsr(msr: t.CUInt32T) -> t.CUInt64T: + lo: t.CUInt32T + hi: t.CUInt32T + c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)} + rdmsr""", + out=[c.AsmOut(lo, t.ASM_DESCR.OUTPUT_REG), c.AsmOut(hi, t.ASM_DESCR.OUTPUT_REG)], + op=[t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RDX]) + return t.CUInt64T(lo) | (t.CUInt64T(hi) << 32) + +def _current_proc() -> process.Process | t.CPtr: + return process.ProcessManager.current() + +def _fd_alloc(p: process.Process | t.CPtr) -> t.CInt: + for i in range(process.PROC_MAX_FDS): + if not p.fd_used[i]: + p.fd_used[i] = 1 + return i + return -1 + +def _fd_free(p: process.Process | t.CPtr, fd: t.CInt): + if fd >= 0 and fd < process.PROC_MAX_FDS: + p.fd_used[fd] = 0 + p.fd_table[fd] = t.CVoid(0, t.CPtr) + +def _dir_alloc(p: process.Process | t.CPtr) -> t.CInt: + for i in range(process.PROC_MAX_DIRS): + if not p.dir_used[i]: + p.dir_used[i] = 1 + return i + return -1 + +def _dir_free(p: process.Process | t.CPtr, dd: t.CInt): + if dd >= 0 and dd < process.PROC_MAX_DIRS: + p.dir_used[dd] = 0 + p.dir_table[dd] = t.CVoid(0, t.CPtr) + +def CREATE_WINDOW(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | t.CChar | t.CPtr, style: t.CInt) -> t.CInt: + desktop.set_app_pid(process.current()) + return desktop.create(x, y, w, h, title, style) + +def DRAW_TEXT(win_id: t.CInt, x: t.CInt, y: t.CInt, text: t.CConst | t.CChar | t.CPtr, color: t.CUInt32T) -> t.CInt: + return desktop.draw_text(win_id, x, y, text, color) + +def EXIT(code: t.CInt) -> t.CInt: + serial.puts("[syscall] exit called\n") + desktop.destroy_by_pid(process.current()) + return 0 + +def WAIT_WINDOW(win_id: t.CInt) -> t.CInt: + while desktop.is_active(win_id): + sched.Scheduler.try_reschedule() + return 0 + +def SERIAL_PUTS(str_ptr: t.CVoid | t.CPtr) -> t.CInt: + s: t.CConst | t.CChar | t.CPtr = t.CVoid(str_ptr, t.CPtr) + serial.puts(s) + return 0 + +_loaded_so_base: t.CVoid | t.CPtr = None +_loaded_so_path: t.CArray[t.CChar, 64] + +def LOAD_SO(path_ptr: t.CVoid | t.CPtr) -> t.CUInt64T: + global _loaded_so_base + path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr) + serial.puts("[so] loading: ") + serial.puts(path) + serial.puts("\n") + base: t.CVoid | t.CPtr = elf.load_so(path) + if base is not None: + _loaded_so_base = base + for i in range(63): + ch: t.CChar = c.Deref(path + i) + _loaded_so_path[i] = ch + if ch == 0: break + _loaded_so_path[63] = 0 + return t.CUInt64T(base) + return 0 + +def SO_CALL1(sym_name_ptr: t.CVoid | t.CPtr, arg1: t.CVoid | t.CPtr) -> t.CInt: + if _loaded_so_base is None: return -1 + sym_name: t.CConst | t.CChar | t.CPtr = t.CVoid(sym_name_ptr, t.CPtr) + fn: t.CVoid | t.CPtr = elf.get_so_symbol(_loaded_so_base, sym_name, c.Addr(_loaded_so_path[0])) + if fn is None: + serial.puts("[so] symbol not found\n") + return -2 + c.Asm(f"""mov rdi, {c.AsmInp(arg1, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(fn, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + serial.puts("[so] call returned\n") + return 0 + +def GET_WINBUF(win_id: t.CInt) -> t.CUInt64T: + buf: t.CVoid | t.CPtr = desktop.get_fb(win_id) + if buf is None: + return 0 + return t.CUInt64T(buf) + +def WIN_FLUSH(win_id: t.CInt) -> t.CInt: + desktop.flush(win_id) + return 0 + +def SET_CURSOR(win_id: t.CInt, cursor_t: t.CInt) -> t.CInt: + desktop.set_cursor_type(win_id, cursor_t) + return 0 + +def OPEN(path_ptr: t.CVoid | t.CPtr, mode: t.CUInt32T) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr) + fat32.lock() + fp: t.CVoid | t.CPtr = fat32.open(path, t.CUInt8T(mode)) + if not fp: + fat32.unlock() + return -1 + fd: t.CInt = _fd_alloc(p) + if fd < 0: + fat32.close(fp) + fat32.unlock() + return -1 + p.fd_table[fd] = fp + fat32.unlock() + return fd + +def CLOSE(fd: t.CInt) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + fp: t.CVoid | t.CPtr = p.fd_table[fd] + if not fp: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + fat32.lock() + res: t.CInt = t.CInt(fat32.close(fp)) + fat32.unlock() + _fd_free(p, fd) + return res + +def READ(fd: t.CInt, buf_ptr: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: + return -1 + fp: t.CVoid | t.CPtr = p.fd_table[fd] + if not fp: + return -1 + fat32.lock() + br: t.CUInt32T = fat32.read(fp, buf_ptr, count) + fat32.unlock() + return t.CInt(br) + +def WRITE(fd: t.CInt, buf_ptr: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: + return -1 + fp: t.CVoid | t.CPtr = p.fd_table[fd] + if not fp: + return -1 + fat32.lock() + bw: t.CUInt32T = fat32.write(fp, buf_ptr, count) + fat32.unlock() + return t.CInt(bw) + +def SEEK(fd: t.CInt, offset: t.CUInt32T) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + fp: t.CVoid | t.CPtr = p.fd_table[fd] + if not fp: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + fat32.lock() + r: t.CInt = t.CInt(fat32.seek(fp, offset)) + fat32.unlock() + return r + +def TELL(fd: t.CInt) -> t.CUInt32T: + p: process.Process | t.CPtr = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: + return 0 + fp: t.CVoid | t.CPtr = p.fd_table[fd] + if not fp: + return 0 + fat32.lock() + pos: t.CUInt32T = fat32.tell(fp) + fat32.unlock() + return pos + +def fsize(fd: t.CInt) -> t.CUInt32T: + p: process.Process | t.CPtr = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: + return 0 + fp: t.CVoid | t.CPtr = p.fd_table[fd] + if not fp: + return 0 + fat32.lock() + sz: t.CUInt32T = fat32.size(fp) + fat32.unlock() + return sz + +def MKDIR(path_ptr: t.CVoid | t.CPtr) -> t.CInt: + path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr) + fat32.lock() + r: t.CInt = t.CInt(fat32.mkdir(path)) + fat32.unlock() + return r + +def REMOVE(path_ptr: t.CVoid | t.CPtr) -> t.CInt: + path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr) + fat32.lock() + r: t.CInt = t.CInt(fat32.remove(path)) + fat32.unlock() + return r + +def OPENdir(path_ptr: t.CVoid | t.CPtr) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr) + dd: t.CInt = _dir_alloc(p) + if dd < 0: + return -1 + dp: fat32_types.dirobj | t.CPtr = c.Addr(p.dir_pool[dd]) + fat32.lock() + res: t.CInt = t.CInt(fat32.opendir(path, dp)) + fat32.unlock() + if res != 0: + _dir_free(p, dd) + return -1 + p.dir_table[dd] = t.CVoid(t.CUInt64T(dp), t.CPtr) + return dd + +def READdir(dd: t.CInt, info_ptr: t.CVoid | t.CPtr) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + if dd < 0 or dd >= process.PROC_MAX_DIRS: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + dp: t.CVoid | t.CPtr = p.dir_table[dd] + if not dp: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + info: fat32_types.fileinfo | t.CPtr = info_ptr + fat32.lock() + r: t.CInt = t.CInt(fat32.readdir(dp, info)) + fat32.unlock() + return r + +def CLOSEdir(dd: t.CInt) -> t.CInt: + p: process.Process | t.CPtr = _current_proc() + if dd < 0 or dd >= process.PROC_MAX_DIRS: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + dp: t.CVoid | t.CPtr = p.dir_table[dd] + if not dp: + return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT) + fat32.lock() + fat32.closedir(dp) + fat32.unlock() + _dir_free(p, dd) + return 0 + +def STAT(path_ptr: t.CVoid | t.CPtr, info_ptr: t.CVoid | t.CPtr) -> t.CInt: + path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr) + info: fat32_types.fileinfo | t.CPtr = info_ptr + fat32.lock() + r: t.CInt = t.CInt(fat32.stat(path, info)) + fat32.unlock() + return r + +def SPAWN(path_ptr: t.CVoid | t.CPtr, blocking: t.CInt) -> t.CInt: + path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr) + pid: t.CInt = elf.spawn_elf(path) + if pid < 0: + return pid + if blocking: + p: process.Process | t.CPtr = process.ProcessManager.get_process(pid) + if p: + while p.state != process.PROC_DONE: + sched.Scheduler.try_reschedule() + return pid + +USER_HEAP_BASE: t.CDefine = 0x20000000 + +def MMAP(size: t.CUInt64T) -> t.CUInt64T: + p: process.Process | t.CPtr = _current_proc() + if size == 0: return 0 + aligned_size: t.CUInt64T = (size + 4095) & ~t.CUInt64T(4095) + if p.heap_start == 0: + p.heap_start = USER_HEAP_BASE + heap_end: t.CUInt64T = p.heap_start + p.heap_size + new_end: t.CUInt64T = heap_end + aligned_size + for va in range(heap_end, new_end, 4096): + phys_page: t.CVoid | t.CPtr = mm.malloc(4096) + if phys_page is None: return 0 + paging.MapPage(va, t.CUInt64T(phys_page), paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + p.heap_size = p.heap_size + aligned_size + return heap_end + +def MUNMAP(ptr: t.CUInt64T, size: t.CUInt64T) -> t.CInt: + return 0 + +def syscall_entry() -> t.CExtern | t.CVoid | t.State: pass + +def syscall_handler(num: t.CUInt64T, arg1: t.CUInt64T, arg2: t.CUInt64T, arg3: t.CUInt64T, arg4: t.CUInt64T, arg5: t.CUInt64T, arg6: t.CUInt64T) -> t.CExport | t.CUInt64T: + n: t.CInt = t.CInt(num) + if n == CREATE_WINDOW: + title_ptr: t.CVoid | t.CPtr = t.CVoid(arg5, t.CPtr) + return t.CUInt64T(CREATE_WINDOW(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3), t.CInt(arg4), title_ptr, t.CInt(arg6))) + elif n == DRAW_TEXT: + text_ptr: t.CVoid | t.CPtr = t.CVoid(arg4, t.CPtr) + return t.CUInt64T(DRAW_TEXT(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3), text_ptr, t.CUInt32T(arg5))) + elif n == EXIT: + return t.CUInt64T(EXIT(t.CInt(arg1))) + elif n == OPEN: + path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return t.CUInt64T(OPEN(path_ptr, t.CUInt32T(arg2))) + elif n == CLOSE: + return t.CUInt64T(CLOSE(t.CInt(arg1))) + elif n == READ: + buf_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr) + return t.CUInt64T(READ(t.CInt(arg1), buf_ptr, t.CUInt32T(arg3))) + elif n == WRITE: + buf_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr) + return t.CUInt64T(WRITE(t.CInt(arg1), buf_ptr, t.CUInt32T(arg3))) + elif n == SEEK: + return t.CUInt64T(SEEK(t.CInt(arg1), t.CUInt32T(arg2))) + elif n == TELL: + return t.CUInt64T(TELL(t.CInt(arg1))) + elif n == SIZE: + return t.CUInt64T(fsize(t.CInt(arg1))) + elif n == MKDIR: + path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return t.CUInt64T(MKDIR(path_ptr)) + elif n == REMOVE: + path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return t.CUInt64T(REMOVE(path_ptr)) + elif n == OPENDIR: + path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return t.CUInt64T(OPENdir(path_ptr)) + elif n == READDIR: + info_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr) + return t.CUInt64T(READdir(t.CInt(arg1), info_ptr)) + elif n == CLOSEDIR: + return t.CUInt64T(CLOSEdir(t.CInt(arg1))) + elif n == STAT: + path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + info_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr) + return t.CUInt64T(STAT(path_ptr, info_ptr)) + elif n == WAIT_WINDOW: + if t.CInt(arg2) == 1: + return t.CUInt64T(desktop.is_active(t.CInt(arg1))) + elif t.CInt(arg2) == 2: + sched.Scheduler.try_reschedule() + return 0 + return t.CUInt64T(WAIT_WINDOW(t.CInt(arg1))) + elif n == SERIAL_PUTS: + str_ptr_sp: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return t.CUInt64T(SERIAL_PUTS(str_ptr_sp)) + elif n == LOAD_SO: + path_ptr_so: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return LOAD_SO(path_ptr_so) + elif n == SO_CALL1: + sym_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return t.CUInt64T(SO_CALL1(sym_ptr, arg2)) + elif n == GET_WINBUF: + return GET_WINBUF(t.CInt(arg1)) + elif n == WIN_FLUSH: + return t.CUInt64T(WIN_FLUSH(t.CInt(arg1))) + elif n == POLL_EVENT: + buf_ptr_ev: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + caller_pid_ev: t.CInt = process.current() + return t.CUInt64T(desktop.poll_event(buf_ptr_ev, caller_pid_ev)) + elif n == SET_CURSOR: + desktop.set_cursor_type(t.CInt(arg1), t.CInt(arg2)) + return 0 + elif n == YIELD: + sched.Scheduler.try_reschedule() + return 0 + elif n == SET_TITLE: + title_ptr_st: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr) + return t.CUInt64T(desktop.set_title(t.CInt(arg1), title_ptr_st)) + elif n == SET_GEOMETRY: + return t.CUInt64T(desktop.set_geometry(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3), t.CInt(arg4), t.CInt(arg5))) + elif n == SET_RESIZABLE: + return t.CUInt64T(desktop.set_resizable(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3))) + elif n == GET_WIN_SIZE: + return t.CUInt64T(desktop.get_size(t.CInt(arg1))) + elif n == SPAWN: + path_ptr_sp: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + return t.CUInt64T(SPAWN(path_ptr_sp, t.CInt(arg2))) + elif n == MMAP: + return MMAP(arg1) + elif n == MUNMAP: + return t.CUInt64T(MUNMAP(arg1, arg2)) + elif n == THREAD_CREATE: + func_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr) + arg_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr) + cur_pid: t.CInt = process.current() + th: sched.Thread | t.CPtr = sched.Scheduler.create_thread(func_ptr, arg_ptr, cur_pid) + if th is None: return t.CUInt64T(0xFFFFFFFFFFFFFFFF) + p: process.Process | t.CPtr = process.ProcessManager.get_process(cur_pid) + if p: + p.addThread(th.tid) + return t.CUInt64T(th.tid) + return 0 + +def init(): + efer: t.CUInt64T = rdmsr(IA32_EFER) + efer = efer | 1 + wrmsr(IA32_EFER, efer) + star_val: t.CUInt64T = (t.CUInt64T(0x08) << 32) | (t.CUInt64T(0x1B) << 48) + wrmsr(IA32_STAR, star_val) + wrmsr(IA32_LSTAR, t.CUInt64T(syscall_entry)) + wrmsr(IA32_FMASK, 0x200) + serial.puts("[syscall] MSR init done\n") diff --git a/VKernel/Kernel/main.py b/VKernel/Kernel/main.py new file mode 100644 index 0000000..6d11070 --- /dev/null +++ b/VKernel/Kernel/main.py @@ -0,0 +1,347 @@ +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 t, c +import asm + +kbd_tid: t.CInt = -1 +mse_tid: t.CInt = -1 + +BootInfo: bootinfo.bootinfo | t.CPtr + +@t.Object +# @t.CVTable +class ViperKernel: + BootInfo: bootinfo.bootinfo | t.CPtr + VGA: vga._VGAScreenDriver | t.CPtr + VGA_drv: vga._VGAScreenDriver + + def __init__(self): + global BootInfo + self.BootInfo = BootInfo + BootInfo: bootinfo.bootinfo | t.CPtr = self.BootInfo + serial.init() + serial.puts("===== ViperOS VKernel Test\n =====") + serial.puts("V2.3.8\n") + + intr.gdt.init() + # Set IA32_KERNEL_GS_BASE = &_per_cpu so swapgs in ISR/syscall works. + # _CPUObject is never instantiated, so __init_tss__ never runs; + # without this, GS base stays 0 and gs:[0] writes hit null -> triple fault. + cpu.msr_write(cpu.IA32_KERNEL_GS_BASE, t.CUInt64T(c.Addr(cpu._per_cpu))) + serial.puts("[main] gdt init done\n") + intr.idt.init() + syscall.init() + serial.puts("[main] syscall init done\n") + + # 必须在 sti() 之前初始化调度器,否则中断触发时 _sched_ptr 为 NULL 会导致三重故障 + 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.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: t.CUInt32T = apic.get_cpu_count() + ncpu_cr: t.CArray[t.CChar, 32] + viperlib.snprintf(c.Addr(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: t.CVoid | t.CPtr = BootInfo.framebuffer_addr + width: t.CUInt64T = BootInfo.framebuffer_width + height: t.CUInt64T = BootInfo.framebuffer_height + pitch: t.CUInt64T = BootInfo.framebuffer_pitch + fb_format: t.CUInt64T = BootInfo.framebuffer_format + fb_size: t.CUInt64T = BootInfo.framebuffer_size + if BootInfo is not None: + paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize) + fb_pages: t.CUInt64T = (fb_size + t.CUInt64T(0xFFF)) >> t.CUInt64T(12) + fpi: t.CUInt64T = 0 + while fpi < fb_pages: + fva: t.CUInt64T = t.CUInt64T(fb) + fpi * t.CUInt64T(0x1000) + paging.MapPage(fva, fva, paging.PTE_WRITABLE | paging.PTE_PCD) + fpi += 1 + mm.init(BootInfo.MemmapAddr, BootInfo.MemmapSize, BootInfo.MemmapDescSize, BootInfo.framebuffer_addr, fb_size) + pt_pool_buf: t.CVoid | t.CPtr = mm.malloc(t.CUInt64T(513) * t.CUInt64T(4096)) + paging.init_pool(pt_pool_buf, t.CUInt64T(513) * t.CUInt64T(4096)) + else: + paging.init(0, 0) + mm.init(0, 0, 0, 0, 0) + pt_pool_buf2: t.CVoid | t.CPtr = mm.malloc(t.CUInt64T(513) * t.CUInt64T(4096)) + paging.init_pool(pt_pool_buf2, t.CUInt64T(513) * t.CUInt64T(4096)) + serial.puts("[main] paging and mm init done\n") + + 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: t.CInt = fat32.scan_drives() + ndrives: t.CInt = fat32.get_drive_count() + ndrives_cr: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(ndrives_cr), 64, "[fat32] scan_res=%d found %d drives\n", scan_res, ndrives) + serial.puts(ndrives_cr) + + if ndrives > 0: + mount_res: t.CInt = fat32.mount() + if mount_res == 0: + dp: fat32_types.dirobj + res: t.CInt = fat32.opendir("/", c.Addr(dp)) + if res == 0: + serial.puts("[fat32] listing 0:/\n") + count: t.CInt = 0 + while count < 20: + info: fat32_types.fileinfo + res2: t.CInt = fat32.readdir(c.Addr(dp), c.Addr(info)) + if res2 != 0: break + if info.fname[0] == 0: break + is_dir: t.CInt = 1 if (info.attr & fat32_types.AM_DIR) else 0 + entry_cr: t.CArray[t.CChar, 300] + viperlib.snprintf(c.Addr(entry_cr), 300, " %s size=%lu dir=%d\n", c.Addr(info.fname[0]), info.file_size, is_dir) + serial.puts(entry_cr) + count = count + 1 + fat32.closedir(c.Addr(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: t.CInt = fat32.opendir("/APPS", c.Addr(dp_apps)) + if apps_res == 0: + serial.puts("[main] APPS dir listing:\n") + ac: t.CInt = 0 + while ac < 20: + ai: fat32_types.fileinfo + ar: t.CInt = fat32.readdir(c.Addr(dp_apps), c.Addr(ai)) + if ar != 0: break + if ai.fname[0] == 0: break + ae: t.CArray[t.CChar, 300] + viperlib.snprintf(c.Addr(ae), 300, " %s size=%lu\n", c.Addr(ai.fname[0]), ai.file_size) + serial.puts(ae) + ac = ac + 1 + fat32.closedir(c.Addr(dp_apps)) + else: + serial.puts("[main] APPS dir not found\n") + + sched.Scheduler.disable() + hello_pid: t.CInt = elf.spawn_elf("/APPS/hello.elf") + if hello_pid < 0: + serial.puts("[main] Hello spawn failed\n") + else: + slog: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(slog), 64, "[main] Hello spawned as pid=%d\n", hello_pid) + serial.puts(slog) + term_pid: t.CInt = elf.spawn_elf("/APPS/terminal.elf") + if term_pid < 0: + serial.puts("[main] Terminal spawn failed\n") + else: + tlog: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(tlog), 64, "[main] Terminal spawned as pid=%d\n", term_pid) + serial.puts(tlog) + + garg_pid: t.CInt = elf.spawn_elf("/APPS/gargantua.elf") + if garg_pid < 0: + serial.puts("[main] Gargantua spawn failed\n") + else: + glog: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(glog), 64, "[main] Gargantua spawned as pid=%d\n", garg_pid) + serial.puts(glog) + + s3d_pid: t.CInt = elf.spawn_elf("/APPS/scene3d.elf") + if s3d_pid < 0: + serial.puts("[main] Scene3D spawn failed\n") + else: + s3d_log: t.CArray[t.CChar, 64] + viperlib.snprintf(c.Addr(s3d_log), 64, "[main] Scene3D spawned as pid=%d\n", s3d_pid) + serial.puts(s3d_log) + + so_base: t.CVoid | t.CPtr = 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: t.CVoid | t.CPtr = elf.get_so_symbol(so_base, "log_info", "/LIBS/SLOG.SO") + if log_info_fn is not None: + msg: t.CConst | t.CChar | t.CPtr = "Hello from shared library!" + c.Asm(f"""mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(log_info_fn, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.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 destructive tests removed to prevent disk.img corruption + # (write/rename/delete/mkdir/rmdir permanently modify the disk image) + + fat32.unmount() + serial.puts("[fat32] unmounted\n") + 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: t.CInt = devfs.devfs_open("/dev/serial0", 0) + buf: t.CArray[t.CChar, 64] + string.memset(c.Addr(buf), 0, 64) + devfs.devfs_write(fd, "hello from devfs!", 17) + devfs.devfs_read(fd, c.Addr(buf), 64) + devfs.devfs_close(fd) + + cr: t.CArray[t.CChar, 30] + string.memset(c.Addr(cr), 0, 30) + viperlib.snprintf(c.Addr(cr), 30, "hello %d %.2f", 42, float(0.24)) + serial.puts(cr) + serial.puts("\n") + + bootinfo_cr: t.CArray[t.CChar, 256] + string.memset(c.Addr(bootinfo_cr), 0, 256) + viperlib.snprintf(c.Addr(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: t.CConst | str = "Hello" + serial.puts(c.Addr(a[4])) # o + serial.puts("\n") + d: t.CConst | int = 42 + b: t.CConst | int | t.CPtr = c.Addr(d) + string.memset(c.Addr(cr), 0, 4) + viperlib.snprintf(c.Addr(cr), 30, "%d", b[0]) # 42 + serial.puts(cr) + serial.puts("\n") + + # paging and mm already initialized before FAT32 + + usb.init() + + 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") + + self.VGA_drv = vga._VGAScreenDriver(fb, width, height, pitch, fb_format) + self.VGA = c.Addr(self.VGA_drv) + self.VGA.Print(100, 100, "ViperOS VKernel Test") + serial.puts("你好\n") + + serial.puts("[main] starting desktop\n") + 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: t.CUInt32T = 0 + loop_cnt: t.CUInt32T = 0 + while True: + cur_sec: t.CUInt32T = 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 | t.CPtr +@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16)) +def _start() -> t.CInt: + global VKP, BootInfo + saved_rdi: t.CUnsignedLong + c.Asm(f"mov {c.AsmOut(saved_rdi, t.ASM_DESCR.OUTPUT_REG)}, rdi", + op = [t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RDI]) + asm.BSSClean() + BootInfo = saved_rdi # 隐式转为 bootinfo* + VKP = c.Addr(ViperKernel()) + while True: + asm.sti() + asm.hlt() + sched.Scheduler._yield() + return 0 + diff --git a/VKernel/Kernel/mm/mm.py b/VKernel/Kernel/mm/mm.py new file mode 100644 index 0000000..35a8dcf --- /dev/null +++ b/VKernel/Kernel/mm/mm.py @@ -0,0 +1,762 @@ +import bootinfo +import paging.paging as paging +import viperstring as string +import viperlib +import t, c + + +# 伙伴系统常量定义 +MIN_BLOCK_SIZE: t.CDefine = 4096 # 最小块大小(4KB) +MAX_BLOCK_SIZE: t.CDefine = 268435456 # 最大块大小(256MB) +MAX_ORDER: t.CDefine = 18 # 最大阶数(2^18 = 262144) + +# 内存块状态 +BLOCK_FREE: t.CDefine = 0 # 空闲 +BLOCK_USED: t.CDefine = 1 # 已使用 + +# 内存类型定义 +MEMORY_TYPE_AVAILABLE: t.CUInt64T = 7 +MEMORY_TYPE_RESERVED: t.CUInt64T = 0 +MEMORY_TYPE_LOADER_CODE: t.CUInt64T = 1 +MEMORY_TYPE_LOADER_DATA: t.CUInt64T = 2 +MEMORY_TYPE_BOOT_SERVICES_CODE: t.CUInt64T = 3 +MEMORY_TYPE_BOOT_SERVICES_DATA: t.CUInt64T = 4 +MEMORY_TYPE_RUNTIME_SERVICES_CODE: t.CUInt64T = 5 +MEMORY_TYPE_RUNTIME_SERVICES_DATA: t.CUInt64T = 6 +MEMORY_TYPE_ACPI_RECLAIMABLE: t.CUInt64T = 9 +MEMORY_TYPE_ACPI_NVS: t.CUInt64T = 10 +MEMORY_TYPE_BAD_MEMORY: t.CUInt64T = 8 + +# 内存区域结构 +class memory_region(t.CStruct): + start: t.CUInt64T + end: t.CUInt64T + size: t.CUInt64T + type: t.CUInt32T + next: 'memory_region' | t.CPtr + +# 全局内存区域链表 +memory_regions: t.CStatic | memory_region | t.CPtr = None + +# 内存块结构 +class memory_block(t.CStruct): + size: t.CUInt64T # 块大小 + state: t.CUInt8T # 块状态 + order: t.CUInt8T # 块阶数 + next: 'memory_block' | t.CPtr # 下一个块 + prev: 'memory_block' | t.CPtr # 上一个块 + +# 伙伴系统结构 +class __buddy_system(t.CStruct): + free_lists: t.CArray[memory_block | t.CPtr, MAX_ORDER + 1] + total_memory: t.CUInt64T # 总内存大小 + used_memory: t.CUInt64T # 已使用内存大小 + free_memory: t.CUInt64T # 空闲内存大小 + start_addr: t.CVoid | t.CPtr # 内存起始地址 + end_addr: t.CVoid | t.CPtr # 内存结束地址 + + +SLAB_MAGIC: t.CDefine = 0x534C4142 +SLAB_NUM_SIZES: t.CDefine = 6 +SLAB_THRESHOLD: t.CDefine = 1024 + +class slab_page(t.CStruct): + magic: t.CUInt32T + obj_size: t.CUInt16T + free_count: t.CUInt16T + data_offset: t.CUInt16T + total_objs: t.CUInt16T + free_head: t.CVoid | t.CPtr + block_ptr: memory_block | t.CPtr + next: 'slab_page' | t.CPtr + prev: 'slab_page' | t.CPtr + + +# 4K对齐函数 +def align_4k(addr: t.CUInt64T) -> t.CUInt64T: + return (addr + 0xFFF) & ~0xFFF + +# 计算阶数 +def get_order(size: t.CUInt64T) -> t.CStatic | t.CInt: + order: t.CInt = 0 + while size > MIN_BLOCK_SIZE: + size >>= 1 + order += 1 + return order + +# 计算块大小 +def get_block_size(order: t.CInt) -> t.CStatic | t.CUInt64T: + return MIN_BLOCK_SIZE << order + + +@t.Object +class _BuddySystemObject: + # 全局伙伴系统实例 + buddy: __buddy_system + # 初始化伙伴系统 + def __init__(self, start_addr: t.CVoid | t.CPtr, size: t.CUInt64T): + for i in range(MAX_ORDER + 1): + self.buddy.free_lists[i] = None + self.buddy.total_memory = size + self.buddy.used_memory = 0 + self.buddy.free_memory = size + self.buddy.start_addr = start_addr + self.buddy.end_addr = t.CVoid(t.CUInt64T(start_addr) + size, t.CPtr) + remaining: t.CUInt64T = size + cur_addr: t.CUInt64T = t.CUInt64T(start_addr) + while remaining >= MIN_BLOCK_SIZE: + order: t.CInt = 0 + tmp: t.CUInt64T = remaining + while tmp > MIN_BLOCK_SIZE: + tmp >>= 1 + order += 1 + block_size: t.CUInt64T = get_block_size(order) + while block_size > remaining and order > 0: + order -= 1 + block_size = get_block_size(order) + if block_size > remaining: break + block: memory_block | t.CPtr = cur_addr + block.size = block_size + block.state = BLOCK_FREE + block.order = order + block.next = None + block.prev = None + if order <= MAX_ORDER: + if self.buddy.free_lists[order]: + block.next = self.buddy.free_lists[order] + self.buddy.free_lists[order].prev = block + self.buddy.free_lists[order] = block + cur_addr += block_size + remaining -= block_size + # DEBUG: print free_lists status after init + import drivers.serial.uart.serial as serial + _dmsg: t.CArray[t.CChar, 40] + serial.puts("[mm] buddy init:") + _di: t.CInt = 0 + while _di <= MAX_ORDER: + if self.buddy.free_lists[_di]: + viperlib.snprintf(c.Addr(_dmsg), 40, " [%d]=0x%lx", _di, t.CUInt64T(self.buddy.free_lists[_di])) + serial.puts(_dmsg) + _di += 1 + serial.puts("\n") + + # 分割内存块 + def split_block(self, block: memory_block | t.CPtr, target_order: t.CInt) -> memory_block | t.CPtr: + current_order: t.CInt = block.order + # 从当前阶数向下分割,直到达到目标阶数 + while current_order > target_order: + current_order -= 1 + new_block_size: t.CUInt64T = get_block_size(current_order) + # 创建新块 + new_block: memory_block | t.CPtr = t.CUInt64T(block) + new_block_size + new_block.size = new_block_size + new_block.state = BLOCK_FREE + new_block.order = current_order + new_block.next = None + new_block.prev = None + # 更新原块大小和阶数 + block.size = new_block_size + block.order = current_order + # 将新块添加到对应阶数的空闲链表 + if current_order <= MAX_ORDER: + new_block.next = self.buddy.free_lists[current_order] + if self.buddy.free_lists[current_order]: + self.buddy.free_lists[current_order].prev = new_block + self.buddy.free_lists[current_order] = new_block + return block + + # 合并内存块 + def merge_blocks(self, block: memory_block | t.CPtr) -> t.CStatic | memory_block | t.CPtr: + order: t.CInt = block.order + while order < MAX_ORDER: + block_size: t.CUInt64T = get_block_size(order) + block_addr: t.CUInt64T = t.CUInt64T(block) - t.CUInt64T(self.buddy.start_addr) + buddy_addr: t.CUInt64T = block_addr ^ block_size + # 计算伙伴块地址 + buddy: memory_block | t.CPtr = t.CUInt64T(self.buddy.start_addr) + buddy_addr + # 检查伙伴块是否存在且空闲 + if buddy >= t.CType(self.buddy.end_addr, memory_block, t.CPtr): + break # 伙伴块超出内存范围 + if buddy.state != BLOCK_FREE or buddy.order != order: + break # 伙伴块不空闲或阶数不匹配 + # 从空闲链表中移除伙伴块 + if buddy.prev: buddy.prev.next = buddy.next + else: self.buddy.free_lists[order] = buddy.next + if buddy.next: + buddy.next.prev = buddy.prev + # 合并块 + merged_block: memory_block | t.CPtr = block if block < buddy else buddy + merged_block.size = get_block_size(order + 1) + merged_block.order = order + 1 + block = merged_block + order += 1 + return block + + # 分配内存块 + def allocate_block(self, size: t.CUInt64T) -> t.CStatic | memory_block | t.CPtr: + # 计算所需阶数 + order: t.CInt = get_order(size) + if order > MAX_ORDER: return None # 所需块大小超过最大限制 + # 查找合适的空闲块 + found_order: t.CInt = -1 + for i in range(order, MAX_ORDER + 1): + if self.buddy.free_lists[i]: + found_order = i + break + # DEBUG: trace allocate_block + import drivers.serial.uart.serial as serial + _dba: t.CArray[t.CChar, 120] + if found_order == -1: + viperlib.snprintf(c.Addr(_dba), 120, "[mm] alloc FAIL size=%lu order=%d\n", size, order) + serial.puts(_dba) + _dbf: t.CArray[t.CChar, 200] + _fi: t.CInt = 0 + _fo: t.CInt = 0 + while _fi <= MAX_ORDER: + if self.buddy.free_lists[_fi]: + _fs: t.CInt = viperlib.snprintf(c.Addr(_dbf) + _fo, 200 - _fo, "[%d]=0x%lx ", _fi, t.CUInt64T(self.buddy.free_lists[_fi])) + _fo = _fo + _fs + _fi += 1 + if _fo == 0: + serial.puts("[mm] all free_lists EMPTY\n") + else: + serial.puts(_dbf) + serial.puts("\n") + return None + if found_order == -1: return None # 没有合适的空闲块 + # 取出空闲块 + block: memory_block | t.CPtr = self.buddy.free_lists[found_order] + # 从空闲链表中移除块 + if block.next: block.next.prev = None + self.buddy.free_lists[found_order] = block.next + # 如果块大小大于所需大小,分割块 + if found_order > order: + block = self.split_block(block, order) + # 标记块为已使用 + block.state = BLOCK_USED + # 更新内存统计 + self.buddy.used_memory += block.size + self.buddy.free_memory -= block.size + viperlib.snprintf(c.Addr(_dba), 120, "[mm] alloc OK size=%lu order=%d found=%d block=0x%lx\n", size, order, found_order, t.CUInt64T(block)) + serial.puts(_dba) + return block + + # 释放内存块 + def free_block(self, block: memory_block | t.CPtr) -> t.CStatic | t.CVoid: + # 标记块为空闲 + block.state = BLOCK_FREE + # 更新内存统计 + self.buddy.used_memory -= block.size + self.buddy.free_memory += block.size + # 尝试合并块 + block = self.merge_blocks(block) + # 将块添加到对应阶数的空闲链表 + order: t.CInt = block.order + if order <= MAX_ORDER: + block.next = self.buddy.free_lists[order] + block.prev = None + if self.buddy.free_lists[order]: + self.buddy.free_lists[order].prev = block + self.buddy.free_lists[order] = block +BuddySystemObject: _BuddySystemObject + + +def _slab_obj_size(size_class: t.CInt) -> t.CUInt16T: + if size_class == 0: return 32 + if size_class == 1: return 64 + if size_class == 2: return 128 + if size_class == 3: return 256 + if size_class == 4: return 512 + return 1024 + +def _slab_size_class(size: t.CUInt64T) -> t.CInt: + if size <= 32: return 0 + if size <= 64: return 1 + if size <= 128: return 2 + if size <= 256: return 3 + if size <= 512: return 4 + if size <= 1024: return 5 + return -1 + +def _slab_read_ptr(addr: t.CVoid | t.CPtr) -> t.CVoid | t.CPtr: + result: t.CVoid | t.CPtr + c.Asm(f"""mov rax, [{c.AsmInp(addr, t.ASM_DESCR.REG_ANY)}] + mov {c.AsmOut(result, t.ASM_DESCR.OUTPUT_REG)}, rax""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + return result + +def _slab_write_ptr(addr: t.CVoid | t.CPtr, val: t.CVoid | t.CPtr): + c.Asm(f"""mov rax, {c.AsmInp(val, t.ASM_DESCR.REG_ANY)} + mov [{c.AsmInp(addr, t.ASM_DESCR.REG_ANY)}], rax""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_MEMORY]) + +def _slab_find_page(ptr: t.CVoid | t.CPtr) -> slab_page | t.CPtr: + if ptr is None: return None + hdr_off: t.CUInt64T = memory_block.__sizeof__() + page_addr: t.CUInt64T = t.CUInt64T(ptr) & ~t.CUInt64T(0xFFF) + page: slab_page | t.CPtr = page_addr + hdr_off + if page.magic == SLAB_MAGIC: + os: t.CUInt16T = page.obj_size + if os == 32 or os == 64 or os == 128 or os == 256 or os == 512 or os == 1024: + return page + page_addr2: t.CUInt64T = page_addr - 4096 + page2: slab_page | t.CPtr = page_addr2 + hdr_off + if page2.magic == SLAB_MAGIC: + os2: t.CUInt16T = page2.obj_size + if os2 == 32 or os2 == 64 or os2 == 128 or os2 == 256 or os2 == 512 or os2 == 1024: + return page2 + return None + +@t.Object +class _SlabAllocator: + caches: t.CArray[slab_page | t.CPtr, SLAB_NUM_SIZES] + + def __init__(self): + for i in range(SLAB_NUM_SIZES): + self.caches[i] = None + + def _create_page(self, size_class: t.CInt) -> slab_page | t.CPtr: + obj_size: t.CUInt16T = _slab_obj_size(size_class) + aligned_size: t.CUInt16T = t.CUInt16T((obj_size + 15) & ~15) + block: memory_block | t.CPtr = BuddySystemObject.allocate_block(4096 + memory_block.__sizeof__()) + if not block: return None + hdr_off: t.CUInt64T = memory_block.__sizeof__() + page: slab_page | t.CPtr = t.CUInt64T(block) + hdr_off + page.magic = SLAB_MAGIC + page.obj_size = aligned_size + d_off: t.CUInt16T = t.CUInt16T((hdr_off + slab_page.__sizeof__() + 15) & ~15) + page.data_offset = d_off + total: t.CUInt16T = t.CUInt16T((block.size - d_off) / aligned_size) + page.total_objs = total + page.free_count = total + page.block_ptr = block + page.next = None + page.prev = None + data_start: t.CUInt64T = t.CUInt64T(block) + d_off + page.free_head = t.CVoid(data_start, t.CPtr) + for i in range(1, total): + cur: t.CVoid | t.CPtr = t.CVoid(data_start + t.CUInt64T(i - 1) * aligned_size, t.CPtr) + nxt: t.CVoid | t.CPtr = t.CVoid(data_start + t.CUInt64T(i) * aligned_size, t.CPtr) + _slab_write_ptr(cur, nxt) + last: t.CVoid | t.CPtr = t.CVoid(data_start + t.CUInt64T(total - 1) * aligned_size, t.CPtr) + _slab_write_ptr(last, t.CVoid(0, t.CPtr)) + return page + + def alloc(self, size_class: t.CInt) -> t.CVoid | t.CPtr: + page: slab_page | t.CPtr = self.caches[size_class] + while page: + if page.free_count > 0: break + page = page.next + if not page: + page = self._create_page(size_class) + if not page: return None + page.next = self.caches[size_class] + if self.caches[size_class]: + self.caches[size_class].prev = page + self.caches[size_class] = page + obj: t.CVoid | t.CPtr = page.free_head + page.free_head = _slab_read_ptr(obj) + page.free_count -= 1 + return obj + + def free_obj(self, ptr: t.CVoid | t.CPtr, page: slab_page | t.CPtr): + _slab_write_ptr(ptr, page.free_head) + page.free_head = ptr + page.free_count += 1 + if page.free_count == page.total_objs: + if page.prev: + page.prev.next = page.next + else: + sc: t.CInt = _slab_size_class(page.obj_size) + if sc >= 0: + self.caches[sc] = page.next + if page.next: + page.next.prev = page.prev + page.magic = 0 + BuddySystemObject.free_block(page.block_ptr) + +SlabAllocator: _SlabAllocator + + +# 内存分配函数 +def malloc(size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport: + if size == 0: return None + if size <= SLAB_THRESHOLD: + sc: t.CInt = _slab_size_class(size) + if sc >= 0: + result: t.CVoid | t.CPtr = SlabAllocator.alloc(sc) + return result + hdr: t.CUInt64T = memory_block.__sizeof__() + if size >= 4096: + total: t.CUInt64T = size + 4096 + total = align_4k(total) + order: t.CInt = get_order(total) + if order > MAX_ORDER: return None + block: memory_block | t.CPtr = BuddySystemObject.allocate_block(total) + import drivers.serial.uart.serial as serial + _dbm: t.CArray[t.CChar, 120] + viperlib.snprintf(c.Addr(_dbm), 120, "[mm] malloc buddy size=%lu block=0x%lx\n", size, t.CUInt64T(block)) + serial.puts(_dbm) + if not block: return None + user_ptr: t.CUInt64T = (t.CUInt64T(block) + hdr + 4095) & ~t.CUInt64T(4095) + real_hdr: t.CUInt64T = user_ptr - 8 + c.Asm(f"""mov rax, {c.AsmInp(block, t.ASM_DESCR.REG_ANY)} + mov [{c.AsmInp(t.CVoid(real_hdr, t.CPtr), t.ASM_DESCR.REG_ANY)}], rax""", + op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_MEMORY]) + viperlib.snprintf(c.Addr(_dbm), 120, "[mm] malloc ret user_ptr=0x%lx\n", user_ptr) + serial.puts(_dbm) + return t.CVoid(user_ptr, t.CPtr) + total2: t.CUInt64T = size + hdr + total2 = align_4k(total2) + order2: t.CInt = get_order(total2) + if order2 > MAX_ORDER: return None + block2: memory_block | t.CPtr = BuddySystemObject.allocate_block(total2) + if not block2: return None + return t.CVoid(t.CUInt64T(block2) + hdr, t.CPtr) + + +def malloc_direct(size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport: + if size == 0: return None + hdr: t.CUInt64T = memory_block.__sizeof__() + total: t.CUInt64T = size + hdr + total = align_4k(total) + order: t.CInt = get_order(total) + if order > MAX_ORDER: return None + block: memory_block | t.CPtr = BuddySystemObject.allocate_block(total) + if not block: return None + return t.CVoid(t.CUInt64T(block) + hdr, t.CPtr) + + +def free(ptr: t.CVoid | t.CPtr) -> t.CExport: + if ptr is None: return + sp: slab_page | t.CPtr = _slab_find_page(ptr) + if sp: + SlabAllocator.free_obj(ptr, sp) + return + if (t.CUInt64T(ptr) & 0xFFF) == 0: + real_hdr: t.CUInt64T = t.CUInt64T(ptr) - 8 + block: memory_block | t.CPtr + c.Asm(f"""mov rax, [{c.AsmInp(t.CVoid(real_hdr, t.CPtr), t.ASM_DESCR.REG_ANY)}] + mov {c.AsmOut(block, t.ASM_DESCR.OUTPUT_REG)}, rax""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + BuddySystemObject.free_block(block) + return + block2: memory_block | t.CPtr = t.CUInt64T(ptr) - memory_block.__sizeof__() + BuddySystemObject.free_block(block2) + +def calloc(nmemb: t.CUInt64T, size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport: + total_size: t.CUInt64T = nmemb * size + ptr: t.CVoid | t.CPtr = malloc(total_size) + if ptr: + string.memset(ptr, 0, total_size) + return ptr + +def realloc(ptr: t.CVoid | t.CPtr, size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport: + if ptr is None: return malloc(size) + if size == 0: + free(ptr) + return None + new_ptr: t.CVoid | t.CPtr = malloc(size) + if not new_ptr: return None + sp: slab_page | t.CPtr = _slab_find_page(ptr) + if sp: + old_size: t.CUInt64T = sp.obj_size + elif (t.CUInt64T(ptr) & 0xFFF) == 0: + real_hdr_r: t.CUInt64T = t.CUInt64T(ptr) - 8 + block_r: memory_block | t.CPtr + c.Asm(f"""mov rax, [{c.AsmInp(t.CVoid(real_hdr_r, t.CPtr), t.ASM_DESCR.REG_ANY)}] + mov {c.AsmOut(block_r, t.ASM_DESCR.OUTPUT_REG)}, rax""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + old_size = block_r.size - 4096 + else: + block2: memory_block | t.CPtr = t.CUInt64T(ptr) - memory_block.__sizeof__() + old_size = block2.size - memory_block.__sizeof__() + copy_size: t.CUInt64T = old_size if old_size < size else size + string.memcpy(new_ptr, ptr, copy_size) + free(ptr) + return new_ptr + + +# 回收内存区域链表 +def free_memory_regions() -> t.CStatic | t.CVoid: + global memory_regions + current: memory_region | t.CPtr = memory_regions + while current: + next_region: memory_region | t.CPtr = current.next + free(current) + current = next_region + memory_regions = None + +# 初始化内存管理系统 +def init(MemmapAddr: t.CUInt64T, MemmapSize: t.CUInt64T, MemmapDescSize: t.CUInt64T, FbAddr: t.CUInt64T = 0, FbSize: t.CUInt64T = 0): + global BuddySystemObject + import drivers.serial.uart.serial as serial + start_addr: t.CVoid | t.CPtr = None + size: t.CUInt64T = 0 + if MemmapAddr != 0: + if MemmapSize > 0: + if MemmapDescSize > 0: + num_entries: t.CUInt64T = MemmapSize / MemmapDescSize + max_memory: t.CUInt64T = 0 + best_start: t.CUInt64T = 0 + best_avail_size: t.CUInt64T = 0 + best_avail_start: t.CUInt64T = 0 + best_bs_size: t.CUInt64T = 0 + best_bs_start: t.CUInt64T = 0 + best_ld_size: t.CUInt64T = 0 + best_ld_start: t.CUInt64T = 0 + i: t.CUInt64T + for i in range(num_entries): + entry: bootinfo.memory_map_entry | t.CPtr = t.CVoid(MemmapAddr + i * MemmapDescSize, t.CPtr) + etype: t.CUInt64T = entry.type + start: t.CUInt64T = align_4k(entry.physical_start) + np: t.CUInt64T = entry.num_pages + end: t.CUInt64T = entry.physical_start + np * 4096 + end = align_4k(end - 1) + region_size: t.CUInt64T = end - start + if FbAddr != 0 and FbSize != 0: + fb_end: t.CUInt64T = FbAddr + FbSize + if start < fb_end and end > FbAddr: + continue + if etype == MEMORY_TYPE_AVAILABLE: + if region_size > best_avail_size: + best_avail_size = region_size + best_avail_start = start + if etype == MEMORY_TYPE_BOOT_SERVICES_DATA: + if region_size > best_bs_size: + best_bs_size = region_size + best_bs_start = start + if etype == MEMORY_TYPE_LOADER_DATA: + if region_size > best_ld_size: + best_ld_size = region_size + best_ld_start = start + min_blk: t.CUInt64T = MIN_BLOCK_SIZE + if best_avail_size >= min_blk: + best_start = best_avail_start + max_memory = best_avail_size + elif best_bs_size >= min_blk: + best_start = best_bs_start + max_memory = best_bs_size + elif best_ld_size >= min_blk: + best_start = best_ld_start + max_memory = best_ld_size + cr4: t.CArray[t.CChar, 128] + string.memset(c.Addr(cr4), 0, 128) + viperlib.snprintf(c.Addr(cr4), 128, "mm: best=0x%lx max=%lu entries=%lu avail=%lu bs=%lu ld=%lu\n", + best_start, max_memory, num_entries, best_avail_size, best_bs_size, best_ld_size) + serial.puts(cr4) + if best_start != 0: + if max_memory >= min_blk: + start_addr = t.CVoid(best_start, t.CPtr) + size = max_memory + if start_addr is None: + size = detect_memory_size() + size = align_4k(size) + # 起始地址 4MB(内核之后),避免超出物理 RAM + # 旧值 0x10000000 在 256MB QEMU 中超出物理 RAM 边界 + start_addr = t.CVoid(align_4k(0x400000), t.CPtr) + if size > 0x400000: + size = size - 0x400000 + size = align_4k(size) + min_blk2: t.CUInt64T = MIN_BLOCK_SIZE + if size < min_blk2: + size = 33554432 + cr2: t.CArray[t.CChar, 128] + string.memset(c.Addr(cr2), 0, 128) + viperlib.snprintf(c.Addr(cr2), 128, "mm.init: start=0x%lx size=%lu bytes\n", + t.CUInt64T(start_addr), size) + serial.puts(cr2) + BuddySystemObject.__init__(start_addr, size) + SlabAllocator.__init__() + + +# 虚拟内存映射结构 +class vm_area(t.CStruct): + VirtAddr: t.CUInt64T + PhysAddr: t.CUInt64T + size: t.CUInt64T + flags: t.CUInt32T + next: 'vm_area' | t.CPtr + +# 全局虚拟内存区域链表 +vm_areas: vm_area | t.CPtr = None + +# 博弈机制参数 +ALLOCATION_RETRIES: t.CDefine = 5 # 分配重试次数 +FRAGMENTATION_THRESHOLD: t.CDefine = 0.7 # 碎片率阈值 + +# 获取内存使用统计 +@c.CReturn(t.CUInt64T, t.CUInt64T, t.CUInt64T) +def GetMemoryStats(): + return (BuddySystemObject.buddy.total_memory, BuddySystemObject.buddy.used_memory, BuddySystemObject.buddy.free_memory) + +# 计算内存碎片率 +def calculate_fragmentation() -> t.CDouble: + # 计算空闲块的数量和总大小 + free_blocks: t.CInt = 0 + free_size: t.CUInt64T = 0 + + for i in range(MAX_ORDER + 1): + block: memory_block | t.CPtr = BuddySystemObject.buddy.free_lists[i] + while block: + free_blocks += 1 + free_size += block.size + block = block.next + + # 如果没有空闲内存,返回0 + if free_size == 0: + return 0.0 + + # 计算碎片率 + # 碎片率 = 1 - (最大空闲块大小 / 总空闲内存大小) + max_free_block: t.CUInt64T = 0 + for i in range(MAX_ORDER, -1, -1): + if BuddySystemObject.buddy.free_lists[i]: + max_free_block = get_block_size(i) + break + + if max_free_block == 0: + return 1.0 + + return 1.0 - (t.CDouble(max_free_block) / t.CDouble(free_size)) + +# 内存整理函数 +def compact_memory() -> t.CStatic | t.CInt: + freed_pages: t.CInt = 0 + for sc in range(SLAB_NUM_SIZES): + page: slab_page | t.CPtr = SlabAllocator.caches[sc] + while page: + next_page: slab_page | t.CPtr = page.next + if page.free_count == page.total_objs: + if page.prev: + page.prev.next = page.next + else: + SlabAllocator.caches[sc] = page.next + if page.next: + page.next.prev = page.prev + page.magic = 0 + BuddySystemObject.free_block(page.block_ptr) + freed_pages += 1 + page = next_page + return freed_pages + +# 虚拟内存分配函数 +def vm_alloc(size: t.CUInt64T, flags: t.CUInt32T) -> t.CUInt64T: + # 4K对齐 + size = align_4k(size) + + # 尝试分配物理内存,使用博弈机制 + PhysAddr: t.CVoid | t.CPtr = None + retries: t.CInt = 0 + + while retries < ALLOCATION_RETRIES: + PhysAddr = malloc(size) + if PhysAddr: + break + + # 内存不足,尝试进行内存整理 + if calculate_fragmentation() > FRAGMENTATION_THRESHOLD: + compact_memory() + + retries += 1 + + if not PhysAddr: + return 0 + + # 分配虚拟地址 + # 简单的虚拟地址分配策略:从0x100000000开始 + VirtAddr: t.CUInt64T = 0x100000000 + current: vm_area | t.CPtr = vm_areas + + # 寻找合适的虚拟地址空间 + while current: + if VirtAddr + size <= current.VirtAddr: + break + VirtAddr = current.VirtAddr + current.size + current = current.next + + # 创建虚拟内存区域 + area: vm_area | t.CPtr = malloc(vm_area.__sizeof__()) + if area: + area.VirtAddr = VirtAddr + area.PhysAddr = t.CUInt64T(PhysAddr) + area.size = size + area.flags = flags + area.next = vm_areas + vm_areas = area + + # 映射虚拟地址到物理地址 + # 计算需要映射的页数 + pages: t.CUInt64T = size / 4096 + i: t.CUInt64T + for i in range(pages): + page_virt: t.CUInt64T = VirtAddr + (i * 4096) + page_phys: t.CUInt64T = t.CUInt64T(PhysAddr) + (i * 4096) + # 调用paging模块的映射函数 + paging.MapPage(page_virt, page_phys, flags) + + return VirtAddr + +# 虚拟内存释放函数 +def vm_free(VirtAddr: t.CUInt64T): + # 查找虚拟内存区域 + current: vm_area | t.CPtr = vm_areas + prev: vm_area | t.CPtr = None + + while current: + if current.VirtAddr == VirtAddr: + # 取消虚拟地址映射 + pages: t.CUInt64T = current.size / 4096 + i: t.CUInt64T + for i in range(pages): + page_virt: t.CUInt64T = current.VirtAddr + (i * 4096) + paging.UnMapPage(page_virt) + + # 释放物理内存 + free(t.CVoid(current.PhysAddr, t.CPtr)) + if prev: + prev.next = current.next + else: + vm_areas = current.next + free(current) + return + prev = current + current = current.next + +# CMOS读取函数 +def cmos_read(reg: t.CUInt8T) -> t.CUInt8T: + c.Asm(f"mov al, {c.AsmInp(reg)}\nout 0x70, al") + val: t.CUInt8T + c.Asm(f"in al, 0x71\nmov {c.AsmOut(val)}, al") + return val + +# 内存大小测试函数 +def detect_memory_size() -> t.CUInt64T: + # 读取 1MB-16MB 扩展内存(KB 单位) + low: t.CUInt8T = cmos_read(0x15) + high: t.CUInt8T = cmos_read(0x16) + mem_kb: t.CUInt32T = (t.CUInt32T(high) << 8) | low + + # 读取 16MB 以上扩展内存(64KB 块为单位) + low2: t.CUInt8T = cmos_read(0x17) + high2: t.CUInt8T = cmos_read(0x18) + mem_above_16mb: t.CUInt32T = (t.CUInt32T(high2) << 8) | low2 + + # 两个寄存器都为 0 时使用默认值 + if mem_kb == 0 and mem_above_16mb == 0: + return 0x10000000 # 256MB + + # 计算总内存:1MB 基础 + 1MB-16MB + 16MB 以上 + total_size: t.CUInt64T = 1048576 # 前 1MB + if mem_kb > 0: + total_size = total_size + t.CUInt64T(mem_kb) * 1024 + if mem_above_16mb > 0: + total_size = total_size + t.CUInt64T(mem_above_16mb) * 65536 + + total_size = align_4k(total_size) + + # 确保内存大小至少为32MB + if total_size < 33554432: # 32MB + total_size = 33554432 + + return total_size diff --git a/VKernel/Kernel/paging/paging.py b/VKernel/Kernel/paging/paging.py new file mode 100644 index 0000000..e31dcac --- /dev/null +++ b/VKernel/Kernel/paging/paging.py @@ -0,0 +1,296 @@ +import drivers.serial.uart.serial as serial +import viperlib +import t, c + + +PTE_PRESENT: t.CDefine = (1 << 0) +PTE_WRITABLE: t.CDefine = (1 << 1) +PTE_USER: t.CDefine = (1 << 2) +PTE_PWT: t.CDefine = (1 << 3) +PTE_PCD: t.CDefine = (1 << 4) +PTE_ACCESSED: t.CDefine = (1 << 5) +PTE_DIRTY: t.CDefine = (1 << 6) +PTE_PS: t.CDefine = (1 << 7) +PTE_GLOBAL: t.CDefine = (1 << 8) + +class _p: + entries: t.CArray[t.CUInt64T, 512] + +PT_POOL_PAGES: t.CDefine = 512 +pt_pool_raw: t.CVoid | t.CPtr = None +pt_pool_start: t.CUInt64T = 0 +pt_pool_used: t.CUInt64T = 0 + +AllocatedPages: t.CUInt64T = 0 +pml4: _p | t.CPtr = None +pdp: _p | t.CPtr +pd: _p | t.CPtr +pt: _p | t.CPtr + +def init(MemmapAddr: t.CUInt64T, MemmapSize: t.CUInt64T): + global pml4 + pml4 = GetCurrentPml4() + c.Asm(f""" + mov rax, cr0 + and rax, ~0x10000 + mov cr0, rax + """) + +def init_pool(buf: t.CVoid | t.CPtr, buf_size: t.CUInt64T): + global pt_pool_raw, pt_pool_start, pt_pool_used + pt_pool_raw = buf + if buf is None: return + raw_addr: t.CUInt64T = t.CUInt64T(buf) + aligned_addr: t.CUInt64T = (raw_addr + t.CUInt64T(0xFFF)) & ~t.CUInt64T(0xFFF) + pt_pool_start = aligned_addr + pt_pool_used = 0 + +def AllocPage() -> t.CVoid | t.CPtr: + global pt_pool_used, pml4, pdp, pd, pt + if pt_pool_raw is None: return None + if pt_pool_used >= t.CUInt64T(PT_POOL_PAGES): return None + page: t.CVoid | t.CPtr = t.CVoid(pt_pool_start + pt_pool_used * t.CUInt64T(4096), t.CPtr) + pt_pool_used = pt_pool_used + t.CUInt64T(1) + c.Asm(f"""mov al, 0 + mov ecx, 4096 + mov rdi, {c.AsmInp(page, t.ASM_DESCR.REG_ANY)} + rep stosb""", + op = [t.ASM_DESCR.CLOBBER_AL, t.ASM_DESCR.CLOBBER_ECX, t.ASM_DESCR.CLOBBER_RDI]) + AllocatedPages = AllocatedPages + t.CUInt64T(1) + return page + +def FreePage(page: t.CVoid | t.CPtr): + global pml4, pdp, pd, pt + if page is None: return + if AllocatedPages > t.CUInt64T(0): AllocatedPages = AllocatedPages - t.CUInt64T(1) + +def MapPage(VirtAddr: t.CUInt64T, PhysAddr: t.CUInt64T, flags: t.CUInt64T): + global pml4, pdp, pd, pt + if VirtAddr == 0 or PhysAddr == 0: return + VirtAddr = VirtAddr & ~t.CUInt64T(0xFFF) + PhysAddr = PhysAddr & ~t.CUInt64T(0xFFF) + Pml4Index: t.CUInt64T = (VirtAddr >> 39) & 0x1FF + PdpIndex: t.CUInt64T = (VirtAddr >> 30) & 0x1FF + PdIndex: t.CUInt64T = (VirtAddr >> 21) & 0x1FF + PtIndex: t.CUInt64T = (VirtAddr >> 12) & 0x1FF + + if not pml4: + pml4 = AllocPage() + if not pml4: return + + if not (pml4.entries[Pml4Index] & PTE_PRESENT): + pdp: _p | t.CPtr = AllocPage() + if not pdp: return + pml4.entries[Pml4Index] = (t.CUInt64T(pdp) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER) + else: + if (flags & PTE_USER) and not (pml4.entries[Pml4Index] & PTE_USER): + pml4.entries[Pml4Index] = pml4.entries[Pml4Index] | PTE_USER + pdp: _p | t.CPtr = t.CType(pml4.entries[Pml4Index] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + + if not (pdp.entries[PdpIndex] & PTE_PRESENT): + pd: _p | t.CPtr = AllocPage() + if not pd: return + pdp.entries[PdpIndex] = (t.CUInt64T(pd) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER) + else: + if (flags & PTE_USER) and not (pdp.entries[PdpIndex] & PTE_USER): + pdp.entries[PdpIndex] = pdp.entries[PdpIndex] | PTE_USER + pd: _p | t.CPtr = t.CType(pdp.entries[PdpIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + + if pd.entries[PdIndex] & 1: + if pd.entries[PdIndex] & 0x80: + big_phys: t.CUInt64T = pd.entries[PdIndex] & ~t.CUInt64T(0x1FFFFF) + inherit_flags: t.CUInt64T = pd.entries[PdIndex] & (PTE_PCD | PTE_PWT) + pt: _p | t.CPtr = AllocPage() + if not pt: return + split_i: t.CInt = 0 + while split_i < 512: + pt.entries[split_i] = (big_phys + t.CUInt64T(split_i) * 0x1000) | PTE_PRESENT | PTE_WRITABLE | inherit_flags + split_i += 1 + pd.entries[PdIndex] = (t.CUInt64T(pt) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER) + else: + # PD entry exists as a regular PT (e.g. cloned identity mapping). + # Must propagate PTE_USER to the PD entry so user-mode access is + # allowed at all paging levels, otherwise #PF err=0x5. + if (flags & PTE_USER) and not (pd.entries[PdIndex] & PTE_USER): + pd.entries[PdIndex] = pd.entries[PdIndex] | PTE_USER + else: + pt: _p | t.CPtr = AllocPage() + if not pt: return + pd.entries[PdIndex] = (t.CUInt64T(pt) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER) + pt: _p | t.CPtr = t.CType(pd.entries[PdIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + + pt.entries[PtIndex] = PhysAddr | flags | PTE_PRESENT + c.Asm(f"invlpg [{c.AsmInp(VirtAddr, t.ASM_DESCR.REG_ANY)}]") + +def MapLargePage(VirtAddr: t.CUInt64T, PhysAddr: t.CUInt64T, flags: t.CUInt64T) -> t.CVoid: + global pml4, pdp, pd, pt + VirtAddr = VirtAddr & ~t.CUInt64T(0x1FFFFF) + PhysAddr = PhysAddr & ~t.CUInt64T(0x1FFFFF) + Pml4Index: t.CUInt64T = (VirtAddr >> 39) & 0x1FF + PdpIndex: t.CUInt64T = (VirtAddr >> 30) & 0x1FF + PdIndex: t.CUInt64T = (VirtAddr >> 21) & 0x1FF + + if not pml4: + pml4 = AllocPage() + if not pml4: return + + if not pml4.entries[Pml4Index] & PTE_PRESENT: + pdp: _p | t.CPtr = AllocPage() + if not pdp: return + pml4.entries[Pml4Index] = (t.CUInt64T(pdp) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE + pdp: _p | t.CPtr = t.CType(pml4.entries[Pml4Index] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + + if not (pdp.entries[PdpIndex] & PTE_PRESENT): + pd: _p | t.CPtr = AllocPage() + if not pd: return + pdp.entries[PdpIndex] = (t.CUInt64T(pd) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE + pd: _p | t.CPtr = t.CType(pdp.entries[PdpIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + + pd.entries[PdIndex] = PhysAddr | flags | PTE_PRESENT | PTE_PS + c.Asm(f"invlpg [{c.AsmInp(VirtAddr, t.ASM_DESCR.REG_ANY)}]") + +def UnMapPage(VirtAddr: t.CUInt64T): + global pml4, pdp, pd, pt + if VirtAddr == 0: return + if not pml4: return + Pml4Index: t.CUInt64T = (VirtAddr >> 39) & 0x1FF + PdpIndex: t.CUInt64T = (VirtAddr >> 30) & 0x1FF + PdIndex: t.CUInt64T = (VirtAddr >> 21) & 0x1FF + PtIndex: t.CUInt64T = (VirtAddr >> 12) & 0x1FF + + if not pml4.entries[Pml4Index] & PTE_PRESENT: return + + pdp: _p | t.CPtr = t.CType(pml4.entries[Pml4Index] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + if not pdp.entries[PdpIndex] & PTE_PRESENT: return + + pd: _p | t.CPtr = t.CType(pdp.entries[PdpIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + + if not pd.entries[PdIndex] & PTE_PRESENT: return + + if pd.entries[PdIndex] & PTE_PS: + pd.entries[PdIndex] = 0 + else: + pt: _p | t.CPtr = t.CType(pd.entries[PdIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + pt.entries[PtIndex] = 0 + + c.Asm(f"invlpg [{c.AsmInp(VirtAddr, t.ASM_DESCR.REG_ANY)}]") + +def EnablePaging(): + global pml4, pdp, pd, pt + c.Asm(f"mov cr3, {c.AsmInp(t.CUInt64T(pml4), t.ASM_DESCR.REG_ANY)}") + +def GetCurrentPml4() -> _p | t.CPtr: + global pml4, pdp, pd, pt + cr3: t.CUInt64T + c.Asm(f"mov {c.AsmOut(cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3") + return t.CType(cr3 & ~t.CUInt64T(0xFFF), _p, t.CPtr) + +def create_process_page_table() -> t.CUInt64T: + global pml4 + if not pml4: return 0 + new_pml4: _p | t.CPtr = AllocPage() + if not new_pml4: return 0 + i: t.CUInt64T + for i in range(512): + entry: t.CUInt64T = pml4.entries[i] + if entry & PTE_PRESENT: + new_pml4.entries[i] = entry + return t.CUInt64T(new_pml4) + +def clone_for_process(isolate_vaddr: t.CUInt64T) -> t.CUInt64T: + global pml4 + _dbg: t.CArray[t.CChar, 120] + viperlib.snprintf(c.Addr(_dbg), 120, "[clone] pml4=0x%lx pool_used=%lu iso_va=0x%lx\n", t.CUInt64T(pml4), pt_pool_used, isolate_vaddr) + serial.puts(_dbg) + if not pml4: return 0 + new_pml4_page: _p | t.CPtr = AllocPage() + if not new_pml4_page: + serial.puts("[clone] AllocPage pml4 failed\n") + return 0 + _dbg2: t.CArray[t.CChar, 120] + viperlib.snprintf(c.Addr(_dbg2), 120, "[clone] new_pml4=0x%lx\n", t.CUInt64T(new_pml4_page)) + serial.puts(_dbg2) + i: t.CUInt64T + for i in range(512): + new_pml4_page.entries[i] = pml4.entries[i] + pml4_idx: t.CUInt64T = (isolate_vaddr >> 39) & 0x1FF + _dbg3: t.CArray[t.CChar, 120] + viperlib.snprintf(c.Addr(_dbg3), 120, "[clone] pml4_idx=%lu entry0=0x%lx\n", pml4_idx, pml4.entries[pml4_idx]) + serial.puts(_dbg3) + if not (pml4.entries[pml4_idx] & PTE_PRESENT): return t.CUInt64T(new_pml4_page) + kernel_pdp: _p | t.CPtr = t.CType(pml4.entries[pml4_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + new_pdp_page: _p | t.CPtr = AllocPage() + if not new_pdp_page: return t.CUInt64T(new_pml4_page) + for i in range(512): + new_pdp_page.entries[i] = kernel_pdp.entries[i] + new_pml4_page.entries[pml4_idx] = (t.CUInt64T(new_pdp_page) & ~t.CUInt64T(0xFFF)) | (pml4.entries[pml4_idx] & t.CUInt64T(0xFFF)) + pdp_idx: t.CUInt64T = (isolate_vaddr >> 30) & 0x1FF + if not (kernel_pdp.entries[pdp_idx] & PTE_PRESENT): return t.CUInt64T(new_pml4_page) + kernel_pd: _p | t.CPtr = t.CType(kernel_pdp.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + new_pd_page: _p | t.CPtr = AllocPage() + if not new_pd_page: return t.CUInt64T(new_pml4_page) + for i in range(512): + new_pd_page.entries[i] = kernel_pd.entries[i] + new_pdp_page.entries[pdp_idx] = (t.CUInt64T(new_pd_page) & ~t.CUInt64T(0xFFF)) | (kernel_pdp.entries[pdp_idx] & t.CUInt64T(0xFFF)) + pd_idx: t.CUInt64T = (isolate_vaddr >> 21) & 0x1FF + if not (kernel_pd.entries[pd_idx] & PTE_PRESENT): return t.CUInt64T(new_pml4_page) + if kernel_pd.entries[pd_idx] & PTE_PS: return t.CUInt64T(new_pml4_page) + kernel_pt: _p | t.CPtr = t.CType(kernel_pd.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + new_pt_page: _p | t.CPtr = AllocPage() + if not new_pt_page: return t.CUInt64T(new_pml4_page) + for i in range(512): + new_pt_page.entries[i] = kernel_pt.entries[i] + new_pd_page.entries[pd_idx] = (t.CUInt64T(new_pt_page) & ~t.CUInt64T(0xFFF)) | (kernel_pd.entries[pd_idx] & t.CUInt64T(0xFFF)) + return t.CUInt64T(new_pml4_page) + +def save_pt_range(vaddr_start: t.CUInt64T, vaddr_end: t.CUInt64T, save_buf: t.CUInt64T | t.CPtr, save_count: t.CUInt32T | t.CPtr) -> t.CInt: + global pml4 + if not pml4: return -1 + c.Set(save_count, t.CUInt32T(0)) + va: t.CUInt64T = vaddr_start & ~t.CUInt64T(0xFFF) + while va < vaddr_end: + pml4_idx: t.CUInt64T = (va >> 39) & 0x1FF + if not (pml4.entries[pml4_idx] & PTE_PRESENT): va += 0x1000; continue + pdp_: _p | t.CPtr = t.CType(pml4.entries[pml4_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + pdp_idx: t.CUInt64T = (va >> 30) & 0x1FF + if not (pdp_.entries[pdp_idx] & PTE_PRESENT): va += 0x1000; continue + pd_: _p | t.CPtr = t.CType(pdp_.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + pd_idx: t.CUInt64T = (va >> 21) & 0x1FF + if not (pd_.entries[pd_idx] & PTE_PRESENT): va += 0x1000; continue + if pd_.entries[pd_idx] & PTE_PS: va += 0x200000; continue + pt_: _p | t.CPtr = t.CType(pd_.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + pt_idx: t.CUInt64T = (va >> 12) & 0x1FF + saved: t.CUInt32T = c.DerefAs(save_count) + entry_ptr: t.CUInt64T | t.CPtr = save_buf + t.CUInt64T(saved) * 16 + c.DerefAs(entry_ptr, va) + c.DerefAs(entry_ptr + 8, pt_.entries[pt_idx]) + c.Set(save_count, t.CUInt32T(saved + 1)) + va += 0x1000 + return 0 + +def restore_pt_range(save_buf: t.CUInt64T | t.CPtr, save_count: t.CUInt32T): + global pml4 + if not pml4: return + i: t.CUInt32T = 0 + while i < save_count: + entry_ptr: t.CUInt64T | t.CPtr = save_buf + t.CUInt64T(i) * 16 + va: t.CUInt64T = c.DerefAs(entry_ptr) + orig_pte: t.CUInt64T = c.DerefAs(entry_ptr + 8) + pml4_idx: t.CUInt64T = (va >> 39) & 0x1FF + if not (pml4.entries[pml4_idx] & PTE_PRESENT): i += 1; continue + pdp_: _p | t.CPtr = t.CType(pml4.entries[pml4_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + pdp_idx: t.CUInt64T = (va >> 30) & 0x1FF + if not (pdp_.entries[pdp_idx] & PTE_PRESENT): i += 1; continue + pd_: _p | t.CPtr = t.CType(pdp_.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + pd_idx: t.CUInt64T = (va >> 21) & 0x1FF + if not (pd_.entries[pd_idx] & PTE_PRESENT): i += 1; continue + if pd_.entries[pd_idx] & PTE_PS: i += 1; continue + pt_: _p | t.CPtr = t.CType(pd_.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr) + pt_idx: t.CUInt64T = (va >> 12) & 0x1FF + pt_.entries[pt_idx] = orig_pte + c.Asm(f"invlpg [{c.AsmInp(va, t.ASM_DESCR.REG_ANY)}]", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + i += 1 + +def set_active_pml4(cr3_val: t.CUInt64T): + global pml4 + pml4 = t.CType(cr3_val & ~t.CUInt64T(0xFFF), _p, t.CPtr) diff --git a/VKernel/Kernel/platform/pch/__init__.py b/VKernel/Kernel/platform/pch/__init__.py new file mode 100644 index 0000000..e1581f9 --- /dev/null +++ b/VKernel/Kernel/platform/pch/__init__.py @@ -0,0 +1,4 @@ +from . import pic +from . import pit +from . import timer +from . import rtc diff --git a/VKernel/Kernel/platform/pch/pic.py b/VKernel/Kernel/platform/pch/pic.py new file mode 100644 index 0000000..239fd69 --- /dev/null +++ b/VKernel/Kernel/platform/pch/pic.py @@ -0,0 +1,120 @@ +import asm +import t, c + + +# PIC端口定义 +PIC1_COMMAND: t.CDefine = 0x20 +PIC1_DATA: t.CDefine = 0x21 +PIC2_COMMAND: t.CDefine = 0xA0 +PIC2_DATA: t.CDefine = 0xA1 + +# ICW1 - 初始化命令字1 +ICW1_ICW4: t.CDefine = 0x01 # 需要ICW4 +ICW1_SINGLE: t.CDefine = 0x02 # 单级模式 +ICW1_INTERVAL4: t.CDefine = 0x04 # 间隔4字节 +ICW1_LEVEL: t.CDefine = 0x08 # 电平触发模式 +ICW1_INIT: t.CDefine = 0x10 # 初始化 + +# ICW4 - 初始化命令字4 +ICW4_8086: t.CDefine = 0x01 # 8086/88模式 +ICW4_AUTO: t.CDefine = 0x02 # 自动EOI +ICW4_BUF_SLAVE: t.CDefine = 0x08 # 缓冲模式(从片) +ICW4_BUF_MASTER: t.CDefine = 0x0C # 缓冲模式(主片) +ICW4_SFNM: t.CDefine = 0x10 # 特殊全嵌套模式 + +# OCW2 - 操作命令字2 +OCW2_EOI: t.CDefine = 0x20 # 结束中断 + +# OCW3 - 操作命令字3 +OCW3_READ_ISR: t.CDefine = 0x0B # 读取中断服务寄存器(ISR) + +# 初始化PIC +def init(): + # 发送ICW1:开始初始化,需要ICW4 + asm.outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4) + asm.io_wait() + asm.outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4) + asm.io_wait() + + # 发送ICW2:中断向量偏移 + asm.outb(PIC1_DATA, 0x20) # IRQ0-7映射到0x20-0x27 + asm.io_wait() + asm.outb(PIC2_DATA, 0x28) # IRQ8-15映射到0x28-0x2F + asm.io_wait() + + # 发送ICW3:级联信息 + asm.outb(PIC1_DATA, 0x04) # 主PIC的IRQ2连接从PIC + asm.io_wait() + asm.outb(PIC2_DATA, 0x02) # 从PIC连接到主PIC的IRQ2 + asm.io_wait() + + # 发送ICW4:8086模式 + asm.outb(PIC1_DATA, ICW4_8086) + asm.io_wait() + asm.outb(PIC2_DATA, ICW4_8086) + asm.io_wait() + + # 设置初始屏蔽字:只启用IRQ0(定时器)、IRQ1(键盘)、IRQ2(级联) + # 主PIC: 屏蔽所有除了IRQ0, IRQ1, IRQ2 (0xF8 = 11111000) + # 从PIC: 屏蔽所有 (0xFF = 11111111),鼠标中断由驱动程序自己启用 + asm.outb(PIC1_DATA, 0xF8) # 启用IRQ0, IRQ1, IRQ2 + asm.io_wait() + asm.outb(PIC2_DATA, 0xFF) # 屏蔽所有从PIC中断 + asm.io_wait() + +# 发送EOI信号 +def eoi(irq: t.CUInt8T): + if irq >= 8: asm.outb(PIC2_COMMAND, OCW2_EOI) + asm.outb(PIC1_COMMAND, OCW2_EOI) + + +# 检查是否是虚假中断(spurious interrupt) +# 返回 1 表示是虚假中断,0 表示是真实中断 +def isSpurious(irq: t.CInt) -> t.CInt: + # 边界检查 + if irq < 0 or irq > 15: return 0 + isr: t.CUInt8T + if irq < 8: + # 发送OCW3,读取ISR(Interrupt Service Register) + asm.outb(PIC1_COMMAND, OCW3_READ_ISR) + asm.io_wait() + isr = asm.inb(PIC1_COMMAND) + # 如果ISR中对应位为0,说明是虚假中断 + return not (isr & (1 << irq)) + else: + # 发送OCW3,读取从PIC的ISR + asm.outb(PIC2_COMMAND, OCW3_READ_ISR) + asm.io_wait() + isr = asm.inb(PIC2_COMMAND) + # 如果ISR中对应位为0,说明是虚假中断 + return not (isr & (1 << (irq - 8))) + +# 设置IRQ屏蔽 +def setMask(irq: t.CUInt8T): + # 边界检查 + if irq > 15: return + port: t.CUInt16T + value: t.CUInt8T + if irq < 8: + port = PIC1_DATA + else: + port = PIC2_DATA + irq -= 8 + value = asm.inb(port) | (1 << irq) + asm.outb(port, value) + asm.io_wait() # 确保命令生效 + +# 清除IRQ屏蔽 +def clearMask(irq: t.CUInt8T): + # 边界检查 + if irq > 15: return + port: t.CUInt16T + value: t.CUInt8T + if irq < 8: + port = PIC1_DATA + else: + port = PIC2_DATA + irq -= 8 + value = asm.inb(port) & ~(1 << irq) + asm.outb(port, value) + asm.io_wait() # 确保命令生效 diff --git a/VKernel/Kernel/platform/pch/pit.py b/VKernel/Kernel/platform/pch/pit.py new file mode 100644 index 0000000..f4a76a3 --- /dev/null +++ b/VKernel/Kernel/platform/pch/pit.py @@ -0,0 +1,26 @@ +import asm +import t, c + +PIT_CHANNEL0: t.CDefine = 0x40 +PIT_CHANNEL1: t.CDefine = 0x41 +PIT_CHANNEL2: t.CDefine = 0x42 +PIT_COMMAND: t.CDefine = 0x43 + +def pit_init(frequency: t.CInt): + # 参数验证:确保频率为正数 + if frequency <= 0: return + + # 计算除数,使用16位无符号整数 + divisor: t.CUInt32T = 1193180 / frequency + + # 发送命令字:通道0,先低后高,模式3(方波),二进制计数 + asm.outb(PIT_COMMAND, 0x36) + asm.io_wait() + + # 写入低字节 + asm.outb(PIT_CHANNEL0, divisor & 0xFF) + asm.io_wait() + + # 写入高字节 + asm.outb(PIT_CHANNEL0, (divisor >> 8) & 0xFF) + asm.io_wait() diff --git a/VKernel/Kernel/platform/pch/rtc.py b/VKernel/Kernel/platform/pch/rtc.py new file mode 100644 index 0000000..52d9850 --- /dev/null +++ b/VKernel/Kernel/platform/pch/rtc.py @@ -0,0 +1,97 @@ +from stdint import * +import t, c +import asm + + +RTC_CMOS_ADDR: t.CUInt16T = 0x70 +RTC_CMOS_DATA: t.CUInt16T = 0x71 + +RTC_REG_SECONDS: t.CDefine = 0x00 +RTC_REG_MINUTES: t.CDefine = 0x02 +RTC_REG_HOURS: t.CDefine = 0x04 +RTC_REG_DAY: t.CDefine = 0x07 +RTC_REG_MONTH: t.CDefine = 0x08 +RTC_REG_YEAR: t.CDefine = 0x09 +RTC_REG_STATUS_A: t.CDefine = 0x0A +RTC_REG_STATUS_B: t.CDefine = 0x0B + +RTC_TZ_OFFSET: t.CDefine = 8 + +rtc_seconds: t.CStatic | t.CVolatile | t.CUInt8T = 0 +rtc_minutes: t.CStatic | t.CVolatile | t.CUInt8T = 0 +rtc_hours: t.CStatic | t.CVolatile | t.CUInt8T = 0 +rtc_day: t.CStatic | t.CVolatile | t.CUInt8T = 0 +rtc_month: t.CStatic | t.CVolatile | t.CUInt8T = 0 +rtc_year: t.CStatic | t.CVolatile | t.CUInt16T = 0 + +def cmos_read(reg: t.CUInt8T) -> t.CUInt8T: + asm.outb(RTC_CMOS_ADDR, reg) + return asm.inb(RTC_CMOS_DATA) + +def bcd_to_bin(bcd: t.CUInt8T) -> t.CUInt8T: + return (bcd >> 4) * 10 + (bcd & 0x0F) + +def rtc_read_time(): + while cmos_read(RTC_REG_STATUS_A) & 0x80: pass + sec: t.CUInt8T = cmos_read(RTC_REG_SECONDS) + min_: t.CUInt8T = cmos_read(RTC_REG_MINUTES) + hour: t.CUInt8T = cmos_read(RTC_REG_HOURS) + day: t.CUInt8T = cmos_read(RTC_REG_DAY) + mon: t.CUInt8T = cmos_read(RTC_REG_MONTH) + year: t.CUInt8T = cmos_read(RTC_REG_YEAR) + reg_b: t.CUInt8T = cmos_read(RTC_REG_STATUS_B) + if not (reg_b & 0x04): + sec = bcd_to_bin(sec) + min_ = bcd_to_bin(min_) + hour = bcd_to_bin(hour) + day = bcd_to_bin(day) + mon = bcd_to_bin(mon) + year = bcd_to_bin(year) + if not (reg_b & 0x02) and (hour & 0x80): + hour = ((hour & 0x7F) + 12) % 24 + hour = hour + RTC_TZ_OFFSET + if hour >= 24: + hour = hour - 24 + day = day + 1 + max_day: t.CUInt8T = 31 + if mon == 4 or mon == 6 or mon == 9 or mon == 11: + max_day = 30 + elif mon == 2: + max_day = 28 + y4: t.CUInt16T = t.CUInt16T(year) + 2000 + if (y4 % 4 == 0 and y4 % 100 != 0) or (y4 % 400 == 0): + max_day = 29 + if day > max_day: + day = 1 + mon = mon + 1 + if mon > 12: + mon = 1 + year = year + 1 + global rtc_seconds, rtc_minutes, rtc_hours, rtc_day, rtc_month, rtc_year + rtc_seconds = sec + rtc_minutes = min_ + rtc_hours = hour + rtc_day = day + rtc_month = mon + rtc_year = t.CUInt16T(year) + 2000 + +def rtc_init(): + rtc_read_time() + +def rtc_get_hours() -> t.CUInt8T: + return rtc_hours + +def rtc_get_minutes() -> t.CUInt8T: + return rtc_minutes + +def rtc_get_seconds() -> t.CUInt8T: + return rtc_seconds + +def rtc_get_day() -> t.CUInt8T: + return rtc_day + +def rtc_get_month() -> t.CUInt8T: + return rtc_month + +def rtc_get_year() -> t.CUInt16T: + return rtc_year diff --git a/VKernel/Kernel/platform/pch/timer.py b/VKernel/Kernel/platform/pch/timer.py new file mode 100644 index 0000000..9b45895 --- /dev/null +++ b/VKernel/Kernel/platform/pch/timer.py @@ -0,0 +1,102 @@ +import platform.pch.pic as pic +import platform.pch.pit as pit +import sched.sched as sched +import asm +import t, c + +TIMER_FREQUENCY: t.CDefine = 1000 +TICKS_PER_SECOND: t.CDefine = TIMER_FREQUENCY +MILLISECONDS_PER_TICK: t.CDefine = (1000 / TIMER_FREQUENCY) + +ticks: t.CStatic | t.CVolatile | t.CUInt64T = 0 +seconds: t.CStatic | t.CVolatile | t.CUInt32T = 0 +milliseconds: t.CStatic | t.CVolatile | t.CUInt32T = 0 +timer_running: t.CStatic | t.CVolatile | bool = False + +def timer_init(): + global ticks, seconds, milliseconds, timer_running + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + ticks = 0 + seconds = 0 + milliseconds = 0 + timer_running = True + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + pit.pit_init(TIMER_FREQUENCY) + +def timer_handler(): + global ticks, seconds, milliseconds, timer_running + if not timer_running: + pic.eoi(0) + return + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + ticks = ticks + 1 + milliseconds = milliseconds + MILLISECONDS_PER_TICK + if milliseconds >= 1000: + seconds = seconds + 1 + milliseconds = milliseconds - 1000 + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + sched.Scheduler.sched_tick() + pic.eoi(0) + +def timer_start(): + global timer_running + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + timer_running = True + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + +def timer_stop(): + global timer_running + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + timer_running = False + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + +def timer_reset(): + global ticks, seconds, milliseconds + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + ticks = 0 + seconds = 0 + milliseconds = 0 + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + +def timer_get_ticks() -> t.CUInt64T: + current_ticks: t.CUInt64T + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + current_ticks = ticks + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + return current_ticks + +def timer_get_seconds() -> t.CUInt32T: + current_seconds: t.CUInt32T + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + current_seconds = seconds + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + return current_seconds + +def timer_get_milliseconds() -> t.CUInt32T: + current_milliseconds: t.CUInt32T + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + current_milliseconds = milliseconds + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + return current_milliseconds + +def timer_get_time(out_seconds: t.CUInt32T | t.CPtr, out_milliseconds: t.CUInt32T | t.CPtr): + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + if out_seconds: + c.Set(c.Deref(out_seconds), seconds) + if out_milliseconds: + c.Set(c.Deref(out_milliseconds), milliseconds) + c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY]) + +def timer_msleep(ms: t.CUInt32T): + target: t.CUInt64T = ticks + (ms + MILLISECONDS_PER_TICK - 1) / MILLISECONDS_PER_TICK + while ticks < target: + asm.sti() + asm.hlt() + sched.Scheduler._yield() + +def timer_sleep(sec: t.CUInt32T): + target: t.CUInt64T = ticks + sec * TICKS_PER_SECOND + while ticks < target: + asm.sti() + asm.hlt() + sched.Scheduler._yield() diff --git a/VKernel/Kernel/sched/coroutine.py b/VKernel/Kernel/sched/coroutine.py new file mode 100644 index 0000000..506e6a8 --- /dev/null +++ b/VKernel/Kernel/sched/coroutine.py @@ -0,0 +1,202 @@ +import mm +import drivers.serial.uart.serial as serial +import asm +import t, c + +CORO_READY: t.CDefine = 0 +CORO_RUNNING: t.CDefine = 1 +CORO_YIELDED: t.CDefine = 2 +CORO_DONE: t.CDefine = 3 +MAX_COROUTINES: t.CDefine = 32 +CORO_STACK_SIZE: t.CDefine = 8192 + + +@t.Object +class Coroutine: + rsp: t.CUInt64T + stack: t.CVoid | t.CPtr + stack_size: t.CUInt64T + func: t.CVoid | t.CPtr + arg: t.CVoid | t.CPtr + state: t.CInt + cid: t.CInt + caller_rsp: t.CUInt64T + ret_val: t.CInt + + def resume(self) -> t.CInt: + global _coro_old_rsp, _coro_new_rsp + m: CoroutineManager | t.CPtr = _coro_mgr_ptr + if self.state == CORO_DONE: return self.ret_val + if self.state != CORO_READY and self.state != CORO_YIELDED: return -1 + prev_cid: t.CInt = m.current_cid + m.current_cid = self.cid + self.state = CORO_RUNNING + _coro_old_rsp = c.Addr(m.coroutines[prev_cid].rsp) + _coro_new_rsp = c.Addr(self.rsp) + self.caller_rsp = t.CUInt64T(_coro_old_rsp) + _coro_switch() + if self.state == CORO_DONE: + return self.ret_val + return 0 + + @staticmethod + def _yield(): + global _coro_old_rsp, _coro_new_rsp + m: CoroutineManager | t.CPtr = _coro_mgr_ptr + cid: t.CInt = m.current_cid + if cid < 0: return + m.coroutines[cid].state = CORO_YIELDED + prev_cid: t.CInt = -1 + i: t.CInt + for i in range(m.count): + if m.coroutines[i].state == CORO_RUNNING and m.coroutines[i].caller_rsp != 0: + prev_cid = i + break + if prev_cid < 0: return + m.current_cid = prev_cid + _coro_old_rsp = c.Addr(m.coroutines[cid].rsp) + _coro_new_rsp = c.Addr(m.coroutines[prev_cid].rsp) + _coro_switch() + + @property + def is_done(self) -> t.CInt: + if self.state == CORO_DONE: + return 1 + return 0 + + @property + def is_yielded(self) -> t.CInt: + if self.state == CORO_YIELDED: + return 1 + return 0 + + @property + def retval(self) -> t.CInt: + return self.ret_val + + +@t.Object +class CoroutineManager: + coroutines: t.CArray[Coroutine, MAX_COROUTINES] + current_cid: t.CInt + count: t.CInt + + def __init__(): + global _coro_mgr_ptr + _coro_mgr_ptr = c.Addr(_coro_mgr) + m: CoroutineManager | t.CPtr = _coro_mgr_ptr + m.current_cid = 0 + m.count = 1 + m.coroutines[0].state = CORO_RUNNING + m.coroutines[0].cid = 0 + m.coroutines[0].caller_rsp = 0 + m.coroutines[0].ret_val = 0 + m.coroutines[0].rsp = 0 + m.coroutines[0].stack = None + m.coroutines[0].stack_size = 0 + m.coroutines[0].func = None + m.coroutines[0].arg = None + main_rsp_ptr: t.CVoid | t.CPtr = c.Addr(m.coroutines[0].rsp) + c.Asm(f"""mov qword ptr [{c.AsmInp(main_rsp_ptr, t.ASM_DESCR.REG_ANY)}], rsp""", + op=[t.ASM_DESCR.CLOBBER_MEMORY]) + + @staticmethod + def current() -> Coroutine | t.CPtr: + m: CoroutineManager | t.CPtr = _coro_mgr_ptr + return c.Addr(m.coroutines[m.current_cid]) + + @staticmethod + def get_coroutine(cid: t.CInt) -> Coroutine | t.CPtr: + m: CoroutineManager | t.CPtr = _coro_mgr_ptr + if cid < 0 or cid >= m.count: return None + return c.Addr(m.coroutines[cid]) + + @staticmethod + def create(func: t.CVoid | t.CPtr, arg: t.CVoid | t.CPtr) -> Coroutine | t.CPtr: + m: CoroutineManager | t.CPtr = _coro_mgr_ptr + if m.count >= MAX_COROUTINES: return None + cid: t.CInt = m.count + m.count += 1 + co: Coroutine | t.CPtr = c.Addr(m.coroutines[cid]) + co.state = CORO_READY + co.cid = cid + co.func = func + co.arg = arg + co.caller_rsp = 0 + co.ret_val = 0 + stack_ptr: t.CVoid | t.CPtr = mm.malloc(CORO_STACK_SIZE) + if not stack_ptr: return None + co.stack = stack_ptr + co.stack_size = CORO_STACK_SIZE + stack_top: t.CUInt64T = t.CUInt64T(stack_ptr) + CORO_STACK_SIZE - 16 + stack_top = stack_top & ~t.CUInt64T(0xF) + entry_addr: t.CUInt64T = t.CUInt64T(c.Addr(_coro_entry)) + sp: t.CPtr = t.CPtr(stack_top) + c.Asm(f"""mov rax, {c.AsmInp(sp, t.ASM_DESCR.REG_ANY)} + mov qword ptr [rax - 56], 0 + mov qword ptr [rax - 48], 0 + mov qword ptr [rax - 40], 0 + mov qword ptr [rax - 32], 0 + mov qword ptr [rax - 24], 0 + mov qword ptr [rax - 16], 0 + mov qword ptr [rax - 8], 0 + mov qword ptr [rax], {c.AsmInp(entry_addr, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + stack_top = stack_top - 56 + co.rsp = stack_top + return co + + +_coro_mgr: CoroutineManager = CoroutineManager() +_coro_mgr_ptr: CoroutineManager | t.CPtr = c.Addr(_coro_mgr) +_coro_old_rsp: t.CVoid | t.CPtr +_coro_new_rsp: t.CVoid | t.CPtr + +@c.Attribute(t.attr.naked) +def _coro_switch(): + c.Asm(f"""push rax + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + mov rax, qword ptr [rip + _coro_old_rsp] + mov qword ptr [rax], rsp + mov rax, qword ptr [rip + _coro_new_rsp] + mov rsp, qword ptr [rax] + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + pop rax + ret""", + op=[t.ASM_DESCR.CLOBBER_MEMORY]) + +@c.Attribute(t.attr.naked) +def _coro_entry(): + c.Asm(f"""sub rsp, 8 + mov rax, qword ptr [rip + _coro_mgr_ptr] + mov ecx, dword ptr [rax + 1056] + movsxd rcx, ecx + shl rcx, 5 + mov rdi, qword ptr [rax + rcx + 24] + mov rax, qword ptr [rax + rcx + 16] + call rax + mov rax, qword ptr [rip + _coro_mgr_ptr] + mov ecx, dword ptr [rax + 1056] + movsxd rcx, ecx + shl rcx, 5 + mov dword ptr [rax + rcx + 32], 3 + mov dword ptr [rax + rcx + 48], eax + add rsp, 8""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + Coroutine._yield() + while True: + asm.sti() + asm.hlt() diff --git a/VKernel/Kernel/sched/process.py b/VKernel/Kernel/sched/process.py new file mode 100644 index 0000000..6b46a84 --- /dev/null +++ b/VKernel/Kernel/sched/process.py @@ -0,0 +1,277 @@ +import mm +import mm.mm as mm_mm +import paging.paging as paging +import drivers.serial.uart.serial as serial +import drivers.fs.fat32.fat32 as fat32 +import drivers.fs.fat32.fat32_types as fat32_types +import drivers.core.cpu.cpu as cpu +import intr.gdt as gdt +import viperlib +import string +import t, c + +PROC_READY: t.CDefine = 0 +PROC_RUNNING: t.CDefine = 1 +PROC_BLOCKED: t.CDefine = 2 +PROC_DONE: t.CDefine = 3 +MAX_PROCESSES: t.CDefine = 8 +MAX_PROC_THREADS: t.CDefine = 8 +PROC_MAX_FDS: t.CDefine = 16 +PROC_MAX_DIRS: t.CDefine = 8 +KERN_STACK_SIZE: t.CDefine = 8192 +USER_STACK_SIZE: t.CDefine = 65536 + + +@t.Object +class Process: + pid: t.CInt + pml4_root: t.CUInt64T + threads: t.CArray[t.CInt, 8] + thread_count: t.CInt + state: t.CInt + entry: t.CVoid | t.CPtr + elf_base: t.CUInt64T + heap_start: t.CUInt64T + heap_size: t.CUInt64T + name: t.CArray[t.CChar, 32] + fd_table: t.CArray[t.CVoid | t.CPtr, PROC_MAX_FDS] + fd_used: t.CArray[t.CUInt8T, PROC_MAX_FDS] + dir_table: t.CArray[t.CVoid | t.CPtr, PROC_MAX_DIRS] + dir_used: t.CArray[t.CUInt8T, PROC_MAX_DIRS] + dir_pool: t.CArray[fat32_types.dirobj, PROC_MAX_DIRS] + kern_stack: t.CUInt64T + user_stack: t.CUInt64T + + def addThread(self, tid: t.CInt) -> t.CInt: + if self.thread_count >= MAX_PROC_THREADS: return -1 + self.threads[self.thread_count] = tid + self.thread_count += 1 + return 0 + + def exit(self): + self.state = PROC_DONE + for i in range(PROC_MAX_FDS): + if self.fd_used[i]: + fp: t.CVoid | t.CPtr = self.fd_table[i] + if fp: + fat32.close(fp) + self.fd_used[i] = 0 + self.fd_table[i] = t.CVoid(0, t.CPtr) + for i in range(PROC_MAX_DIRS): + if self.dir_used[i]: + dp: t.CVoid | t.CPtr = self.dir_table[i] + if dp: + fat32.closedir(dp) + self.dir_used[i] = 0 + self.dir_table[i] = t.CVoid(0, t.CPtr) + m: ProcessManager | t.CPtr = _proc_mgr_ptr + if m.current_pid == self.pid: + m.current_pid = 0 + m.processes[0].state = PROC_RUNNING + if self.pml4_root != 0: + kernel_cr3: t.CUInt64T = m.processes[0].pml4_root + if kernel_cr3 != 0: + c.Asm(f"mov cr3, {c.AsmInp(kernel_cr3, t.ASM_DESCR.REG_ANY)}", + op=[t.ASM_DESCR.CLOBBER_MEMORY]) + paging.set_active_pml4(kernel_cr3) + + @property + def is_running(self) -> t.CInt: + if self.state == PROC_RUNNING: + return 1 + return 0 + + @property + def is_done(self) -> t.CInt: + if self.state == PROC_DONE: + return 1 + return 0 + + +@t.Object +class ProcessManager: + processes: t.CArray[Process, MAX_PROCESSES] + current_pid: t.CInt + count: t.CInt + + def __init__(): + global _proc_mgr_ptr + _proc_mgr_ptr = c.Addr(_proc_mgr) + m: ProcessManager | t.CPtr = _proc_mgr_ptr + m.current_pid = 0 + m.count = 1 + p: Process | t.CPtr = c.Addr(m.processes[0]) + p.pid = 0 + p.state = PROC_RUNNING + p.thread_count = 0 + p.pml4_root = 0 + p.entry = None + p.elf_base = 0 + p.heap_start = 0 + p.heap_size = 0 + name_str: str = "kernel" + i: t.CInt = 0 + for ch in name_str: + if i >= 31: break + p.name[i] = ch + i += 1 + p.name[i] = 0 + j: t.CInt + for j in range(MAX_PROC_THREADS): + p.threads[j] = -1 + for j in range(PROC_MAX_FDS): + p.fd_used[j] = 0 + p.fd_table[j] = t.CVoid(0, t.CPtr) + for j in range(PROC_MAX_DIRS): + p.dir_used[j] = 0 + p.dir_table[j] = t.CVoid(0, t.CPtr) + c.Asm(f"""mov {c.AsmOut(p.pml4_root, t.ASM_DESCR.OUTPUT_REG)}, cr3""", + op=[t.ASM_DESCR.CLOBBER_MEMORY]) + + @staticmethod + def _switch(pid: t.CInt) -> t.CInt: + global _saved_cr3 + m: ProcessManager | t.CPtr = _proc_mgr_ptr + if pid < 0 or pid >= m.count: return -1 + p: Process | t.CPtr = c.Addr(m.processes[pid]) + if p.state == PROC_DONE: return -1 + old_pid: t.CInt = m.current_pid + if old_pid == pid: return 0 + if old_pid >= 0 and old_pid < m.count: + m.processes[old_pid].state = PROC_READY + p.state = PROC_RUNNING + m.current_pid = pid + c.Asm(f"""mov {c.AsmOut(_saved_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3 + mov rax, {c.AsmInp(p.pml4_root, t.ASM_DESCR.REG_ANY)} + mov cr3, rax""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + return 0 + + @staticmethod + def current() -> Process | t.CPtr: + m: ProcessManager | t.CPtr = _proc_mgr_ptr + return c.Addr(m.processes[m.current_pid]) + + @staticmethod + def get_process(pid: t.CInt) -> Process | t.CPtr: + m: ProcessManager | t.CPtr = _proc_mgr_ptr + if pid < 0 or pid >= m.count: return None + return c.Addr(m.processes[pid]) + + @staticmethod + def create_process(name: str, entry: t.CVoid | t.CPtr) -> Process | t.CPtr: + m: ProcessManager | t.CPtr = _proc_mgr_ptr + _dbp: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(_dbp), 80, "[process] create_process count=%d MAX=%d\n", m.count, MAX_PROCESSES) + serial.puts(_dbp) + if m.count >= MAX_PROCESSES: return None + pid: t.CInt = m.count + m.count += 1 + p: Process | t.CPtr = c.Addr(m.processes[pid]) + p.pid = pid + p.state = PROC_READY + p.entry = entry + p.elf_base = 0 + p.thread_count = 0 + p.heap_start = 0 + p.heap_size = 0 + string.memset(c.Addr(p.name), 0, 32) + i: t.CInt = 0 + for ch in name: + if i >= 31: break + p.name[i] = ch + i += 1 + p.name[i] = 0 + j: t.CInt + for j in range(MAX_PROC_THREADS): + p.threads[j] = -1 + for j in range(PROC_MAX_FDS): + p.fd_used[j] = 0 + p.fd_table[j] = t.CVoid(0, t.CPtr) + for j in range(PROC_MAX_DIRS): + p.dir_used[j] = 0 + p.dir_table[j] = t.CVoid(0, t.CPtr) + ks: t.CVoid | t.CPtr = mm_mm.malloc(KERN_STACK_SIZE) + if ks is None: + serial.puts("[process] kernel stack alloc failed\n") + m.count = m.count - 1 + return None + ks_size: t.CUInt64T = KERN_STACK_SIZE + p.kern_stack = t.CUInt64T(ks) + ks_size + us: t.CVoid | t.CPtr = mm_mm.malloc(USER_STACK_SIZE) + if us is None: + serial.puts("[process] user stack alloc failed\n") + mm_mm.free(ks) + m.count = m.count - 1 + return None + us_size: t.CUInt64T = USER_STACK_SIZE + p.user_stack = t.CUInt64T(us) + us_size + c.Asm(f"""mov {c.AsmOut(p.pml4_root, t.ASM_DESCR.OUTPUT_REG)}, cr3""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + return p + + +_proc_mgr: ProcessManager = ProcessManager() +_proc_mgr_ptr: ProcessManager | t.CPtr = c.Addr(_proc_mgr) +_saved_cr3: t.CUInt64T + +def current() -> t.CInt: + m: ProcessManager | t.CPtr = _proc_mgr_ptr + return m.current_pid + +def switch_pid(new_pid: t.CInt): + m: ProcessManager | t.CPtr = _proc_mgr_ptr + if m.current_pid == new_pid: return + if m.current_pid >= 0 and m.current_pid < m.count: + old_p: Process | t.CPtr = c.Addr(m.processes[m.current_pid]) + if old_p.state != PROC_DONE: + old_p.state = PROC_READY + if new_pid >= 0 and new_pid < m.count: + new_p: Process | t.CPtr = c.Addr(m.processes[new_pid]) + new_p.state = PROC_RUNNING + new_cr3: t.CUInt64T = new_p.pml4_root + if new_cr3 != 0: + c.Asm(f"mov cr3, {c.AsmInp(new_cr3, t.ASM_DESCR.REG_ANY)}", + op=[t.ASM_DESCR.CLOBBER_MEMORY]) + paging.set_active_pml4(new_cr3) + # Update TSS RSP0 and GS kernel_rsp0 for the new process + # This is needed so interrupts from Ring 3 use the correct kernel stack + if new_p.kern_stack != 0: + gdt.set_tss_rsp0(new_p.kern_stack) + cpu.set_kernel_rsp0(new_p.kern_stack) + m.current_pid = new_pid + +def get_pid(tid: t.CInt) -> t.CInt: + m: ProcessManager | t.CPtr = _proc_mgr_ptr + i: t.CInt + for i in range(m.count): + p: Process | t.CPtr = c.Addr(m.processes[i]) + j: t.CInt + for j in range(p.thread_count): + if p.threads[j] == tid: + return p.pid + return -1 + +def drop_to_user_mode(entry: t.CVoid | t.CPtr, user_rsp: t.CUInt64T, kern_rsp: t.CUInt64T): + # NOTE: Do NOT swapgs here. This OS uses reverse GS convention: + # GS_BASE = 0 (kernel & user), KERNEL_GS_BASE = &_per_cpu + # syscall/ISR entry does swapgs to get GS_BASE = &_per_cpu + # syscall/ISR return does swapgs to restore GS_BASE = 0 + # GS state is restored in isrCommonhandler before _yield() if needed. + c.Asm(f"""cli + mov rsp, {c.AsmInp(kern_rsp, t.ASM_DESCR.REG_ANY)} + mov rdx, {c.AsmInp(user_rsp, t.ASM_DESCR.REG_ANY)} + mov rcx, {c.AsmInp(entry, t.ASM_DESCR.REG_ANY)} + mov r11, 0x202 + push 0x23 + push rdx + push r11 + push 0x1B + push rcx + iretq""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_R8, + t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10, + t.ASM_DESCR.CLOBBER_RBP]) diff --git a/VKernel/Kernel/sched/sched.py b/VKernel/Kernel/sched/sched.py new file mode 100644 index 0000000..b6fe78e --- /dev/null +++ b/VKernel/Kernel/sched/sched.py @@ -0,0 +1,299 @@ +import mm +import asm +import sched.process as proc +import drivers.serial.uart.serial as serial +import viperlib +import t, c + + +THREAD_READY: t.CDefine = 0 +THREAD_RUNNING: t.CDefine = 1 +THREAD_BLOCKED: t.CDefine = 2 +THREAD_DONE: t.CDefine = 3 +MAX_THREADS: t.CDefine = 16 +THREAD_STACK_SIZE: t.CDefine = 65536 +SCHED_QUANTUM: t.CDefine = 10 + +_yield_cnt: t.CUInt32T = 0 +_yield_lock: t.CInt = 0 + + +@t.Object +class Thread: + rsp: t.CUInt64T + stack: t.CVoid | t.CPtr + stack_size: t.CUInt64T + func: t.CVoid | t.CPtr + arg: t.CVoid | t.CPtr + state: t.CInt + tid: int + pid: t.CInt + + def block(self): + self.state = THREAD_BLOCKED + Scheduler._yield() + + def unblock(self): + if self.state == THREAD_BLOCKED: + self.state = THREAD_READY + + @property + def is_running(self) -> t.CInt: + if self.state == THREAD_RUNNING: + return 1 + return 0 + + @property + def is_blocked(self) -> t.CInt: + if self.state == THREAD_BLOCKED: + return 1 + return 0 + + @property + def is_done(self) -> t.CInt: + if self.state == THREAD_DONE: + return 1 + return 0 + + +@t.Object +class Scheduler: + threads: t.CArray[Thread, MAX_THREADS] + current_tid: t.CInt + thread_count: t.CInt + enabled: t.CInt + needs_reschedule: t.CInt + tick_count: t.CInt + + def __init__(): + global _sched_ptr + _sched_ptr = c.Addr(_sched_storage) + s: Scheduler | t.CPtr = _sched_ptr + s.current_tid = 0 + s.thread_count = 1 + s.enabled = 0 + s.needs_reschedule = 0 + s.tick_count = 0 + i: int + for i in range(MAX_THREADS): + s.threads[i].rsp = 0 + s.threads[i].stack = None + s.threads[i].stack_size = 0 + s.threads[i].func = None + s.threads[i].arg = None + s.threads[i].state = THREAD_DONE + s.threads[i].tid = i + s.threads[i].pid = 0 + s.enabled = 1 + s.threads[0].state = THREAD_RUNNING + s.threads[0].tid = 0 + main_rsp_ptr: t.CVoid | t.CPtr = c.Addr(s.threads[0].rsp) + c.Asm(f"""mov qword ptr [{c.AsmInp(main_rsp_ptr, t.ASM_DESCR.REG_ANY)}], rsp""", + op=[t.ASM_DESCR.CLOBBER_MEMORY]) + + @staticmethod + def _yield(): + global _switch_old_rsp, _switch_new_rsp, _yield_cnt, _yield_lock + _if_saved: t.CInt = 0 + c.Asm(f"""pushfq + pop rax + shr rax, 9 + and eax, 1 + mov {c.AsmOut(_if_saved, t.ASM_DESCR.OUTPUT_REG)}, eax""", + op=[t.ASM_DESCR.CLOBBER_RAX]) + asm.cli() + if _yield_lock: + if _if_saved: + asm.sti() + return + _yield_lock = 1 + s: Scheduler | t.CPtr = _sched_ptr + s.needs_reschedule = 0 + s.tick_count = 0 + old_tid: t.CInt = s.current_tid + next_tid: t.CInt = old_tid + 1 + if next_tid >= s.thread_count: + next_tid = 0 + found: t.CInt = 0 + count: t.CInt = 0 + while count < s.thread_count: + st: t.CInt = s.threads[next_tid].state + if st == THREAD_READY or st == THREAD_RUNNING: + found = 1 + break + next_tid += 1 + if next_tid >= s.thread_count: + next_tid = 0 + count += 1 + if found == 0: + _yield_lock = 0 + if _if_saved: + asm.sti() + return + if next_tid == old_tid: + _yield_lock = 0 + if _if_saved: + asm.sti() + return + if s.threads[old_tid].state == THREAD_RUNNING: + s.threads[old_tid].state = THREAD_READY + s.threads[next_tid].state = THREAD_RUNNING + _switch_old_rsp = c.Addr(s.threads[old_tid].rsp) + _switch_new_rsp = c.Addr(s.threads[next_tid].rsp) + s.current_tid = next_tid + next_pid: t.CInt = s.threads[next_tid].pid + _yield_cnt += 1 + proc.switch_pid(next_pid) + _yield_lock = 0 + _do_switch() + if _if_saved: + asm.sti() + + @staticmethod + def sched_tick(): + s: Scheduler | t.CPtr = _sched_ptr + if not s.enabled: return + if s.thread_count <= 1: return + s.tick_count += 1 + if s.tick_count >= SCHED_QUANTUM: + s.tick_count = 0 + s.needs_reschedule = 1 + + @staticmethod + def try_reschedule(): + s: Scheduler | t.CPtr = _sched_ptr + if s.needs_reschedule: + s.needs_reschedule = 0 + Scheduler._yield() + + @staticmethod + def disable(): + s: Scheduler | t.CPtr = _sched_ptr + s.enabled = 0 + + @staticmethod + def enable(): + s: Scheduler | t.CPtr = _sched_ptr + s.enabled = 1 + + @staticmethod + def current() -> Thread | t.CPtr: + s: Scheduler | t.CPtr = _sched_ptr + return c.Addr(s.threads[s.current_tid]) + + @staticmethod + def get_thread(tid: t.CInt) -> Thread | t.CPtr: + s: Scheduler | t.CPtr = _sched_ptr + if tid < 0 or tid >= s.thread_count: return None + return c.Addr(s.threads[tid]) + + @staticmethod + def create_thread(func: t.CVoid | t.CPtr, arg: t.CVoid | t.CPtr, thread_pid: t.CInt = 0) -> Thread | t.CPtr: + s: Scheduler | t.CPtr = _sched_ptr + if s.thread_count >= MAX_THREADS: return None + tid: int = s.thread_count + s.thread_count += 1 + th: Thread | t.CPtr = c.Addr(s.threads[tid]) + th.state = THREAD_READY + th.tid = tid + th.pid = thread_pid + th.func = func + th.arg = arg + stack_size: t.CUInt64T = THREAD_STACK_SIZE + stack_ptr: t.CVoid | t.CPtr = mm.malloc(stack_size) + if not stack_ptr: return None + th.stack = stack_ptr + th.stack_size = stack_size + stack_top: t.CUInt64T = t.CUInt64T(stack_ptr) + stack_size - 16 + stack_top = stack_top & ~t.CUInt64T(0xF) + entry_addr: t.CUInt64T = t.CUInt64T(c.Addr(_thread_entry)) + sp: t.CPtr = t.CPtr(stack_top) + c.Asm(f"""mov rax, {c.AsmInp(sp, t.ASM_DESCR.REG_ANY)} + mov qword ptr [rax - 64], 0x202 + mov qword ptr [rax - 56], 0 + mov qword ptr [rax - 48], 0 + mov qword ptr [rax - 40], 0 + mov qword ptr [rax - 32], 0 + mov qword ptr [rax - 24], 0 + mov qword ptr [rax - 16], 0 + mov qword ptr [rax - 8], 0 + mov qword ptr [rax], {c.AsmInp(entry_addr, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) + stack_top = stack_top - 64 + th.rsp = stack_top + return th + + +_sched_storage: Scheduler = Scheduler() +_sched_ptr: Scheduler | t.CPtr = c.Addr(_sched_storage) +_switch_old_rsp: t.CVoid | t.CPtr +_switch_new_rsp: t.CVoid | t.CPtr + +@c.Attribute(t.attr.naked) +def _do_switch(): + c.Asm(f"""push rax + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + pushfq + mov rax, qword ptr [rip + _switch_old_rsp] + mov qword ptr [rax], rsp + mov rax, qword ptr [rip + _switch_new_rsp] + mov rsp, qword ptr [rax] + popfq + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + pop rax + ret""", + op=[t.ASM_DESCR.CLOBBER_MEMORY]) + +@c.Attribute(t.attr.naked) +def _thread_entry(): + c.Asm(f"""sub rsp, 8 + mov rax, qword ptr [rip + _sched_ptr] + mov ecx, dword ptr [rax + 896] + movsxd rcx, ecx + imul rcx, rcx, 56 + mov rdi, qword ptr [rax + rcx + 32] + mov rax, qword ptr [rax + rcx + 24] + call rax + mov rax, qword ptr [rip + _sched_ptr] + mov ecx, dword ptr [rax + 896] + movsxd rcx, ecx + imul rcx, rcx, 56 + mov dword ptr [rax + rcx + 40], 3 + add rsp, 8""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + Scheduler._yield() + while True: + asm.sti() + asm.hlt() + Scheduler._yield() + +def thread_current() -> t.CInt: + s: Scheduler | t.CPtr = _sched_ptr + return s.current_tid + +def needs_reschedule() -> t.CInt: + s: Scheduler | t.CPtr = _sched_ptr + return s.needs_reschedule + +def sched_dump(): + s: Scheduler | t.CPtr = _sched_ptr + dl: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(dl), 80, "[sched] dump: cur=%d cnt=%d yields=%u\n", s.current_tid, s.thread_count, _yield_cnt) + serial.puts(dl) + for i in range(s.thread_count): + sl: t.CArray[t.CChar, 80] + viperlib.snprintf(c.Addr(sl), 80, " t%d: st=%d pid=%d rsp=0x%lx\n", i, s.threads[i].state, s.threads[i].pid, s.threads[i].rsp) + serial.puts(sl) diff --git a/VKernel/Kernel/services/desktop.py b/VKernel/Kernel/services/desktop.py new file mode 100644 index 0000000..6f34f75 --- /dev/null +++ b/VKernel/Kernel/services/desktop.py @@ -0,0 +1,2714 @@ +from stdint import * +import asm +import drivers.video.vesafb.gfx as gfx +import drivers.video.vesafb.vga as vga +import drivers.input.mouse.mouse as mouse +import drivers.input.keyboard.keyboard as keyboard +import drivers.usb.hid.usb_mouse as usb_mouse +import drivers.usb.hid.usb_kbd as usb_kbd +import drivers.serial.uart.serial as serial +import platform.pch.pic as pic +import platform.pch.timer as timer +import platform.pch.rtc as rtc +import sched.sched as sched +import sched.process as process +import mm.mm as mm +import string +import viperlib +import t, c + + +TASKBAR_HEIGHT: t.CDefine = 50 +CURSOR_SIZE: t.CDefine = 16 +CURSOR_W: t.CDefine = 16 +CURSOR_H: t.CDefine = 16 +CURSOR_SAVE_SIZE: t.CDefine = 256 +TITLE_HEIGHT_CLASSIC: t.CDefine = 24 +TITLE_HEIGHT_WIN: t.CDefine = 32 +MAX_APP_WINDOWS: t.CDefine = 16 +CLOSE_BTN_SIZE: t.CDefine = 16 +CLOSE_BTN_W_WIN: t.CDefine = 46 +CLOSE_BTN_W7_W: t.CDefine = 28 +CLOSE_BTN_W7_H: t.CDefine = 20 +CLOSE_BTN_W7_R: t.CDefine = 3 +CLOSE_BTN_XP_SZ: t.CDefine = 21 +TITLE_BTN_W_WIN: t.CDefine = 46 +TITLE_BTN_W7: t.CDefine = 28 +TITLE_BTN_XP: t.CDefine = 21 +TASKBAR_ICON_SZ: t.CDefine = 36 +TASKBAR_ICON_PAD: t.CDefine = 6 +TASKBAR_LOGO_SZ: t.CDefine = 32 +WIN_RADIUS: t.CDefine = 8 +SHADOW_ENABLED: t.CDefine = False +SHADOW_LEFT: t.CDefine = 8 +SHADOW_RIGHT: t.CDefine = 12 +SHADOW_TOP: t.CDefine = 4 +SHADOW_BOTTOM: t.CDefine = 14 +SHADOW_ALPHA: t.CDefine = 100 +CURSOR_SHADOW_ALPHA: t.CDefine = 50 + +STYLE_CLASSIC: t.CDefine = 0 +STYLE_WIN10: t.CDefine = 1 +STYLE_WIN11: t.CDefine = 2 +STYLE_WIN7: t.CDefine = 3 +STYLE_WINXP: t.CDefine = 4 + +_fb: UINT32PTR +_screen_w: t.CInt = 0 +_screen_h: t.CInt = 0 +_fb_pitch: t.CInt = 0 +_cursor_x: t.CInt = 512 +_cursor_y: t.CInt = 300 +_cursor_buttons: t.CUInt8T = 0 +_back_fb: UINT32PTR +_last_time_sec: t.CUInt32T = 0 +_row_buf: UINT32PTR +_bg_row_buf: UINT32PTR +@t.Object +class AppWindow: + active: t.CInt + x: t.CInt + y: t.CInt + w: t.CInt + h: t.CInt + title: t.CArray[t.CChar, 32] + orig_title: t.CArray[t.CChar, 32] + content_h: t.CInt + fb: UINT32PTR + fb_w: t.CInt + fb_h: t.CInt + z_order: t.CInt + style: t.CInt + close_hover: t.CInt + min_hover: t.CInt + max_hover: t.CInt + is_maximized: t.CInt + orig_x: t.CInt + orig_y: t.CInt + orig_w: t.CInt + orig_h: t.CInt + pid: t.CInt + cursor_type: t.CInt + app_cursor_type: t.CInt + ping_pending: t.CInt + no_resp: t.CInt + no_resp_sec: t.CUInt32T + minimized: t.CInt + resizable_w: t.CInt + resizable_h: t.CInt + resize_w: t.CInt + resize_h: t.CInt + old_fb: UINT32PTR + +_wins: t.CArray[AppWindow, MAX_APP_WINDOWS] +_win_count: t.CInt = 0 +_next_z: t.CInt = 1 +_current_app_pid: t.CInt = 0 +HB_INTERVAL_SEC: t.CUInt32T = 5 +HB_NO_RESP_SEC: t.CUInt32T = 30 +_hb_last_sec: t.CUInt32T = 0 +_dirty_rows: t.CUInt8T | t.CPtr = t.CUInt8T(0, t.CPtr) +_dirty_count: t.CInt = 0 +_dirty_y_min: t.CInt = 0 +_dirty_y_max: t.CInt = -1 +_vblank_ok: t.CInt = -1 +_prev_cursor_x: t.CInt = 512 +_prev_cursor_y: t.CInt = 300 +_cursor_save_buf: t.CArray[t.CUInt32T, 256] +_cursor_saved: t.CInt = 0 + +_CURSOR_ARROW: t.CArray[t.CUInt8T, 256] = [ + 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0, + 1,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0, + 1,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0, + 1,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0, + 1,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0, + 1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0, + 1,2,2,2,1,1,1,1,1,1,0,0,0,0,0,0, + 1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +] + +_CURSOR_IBEAM: t.CArray[t.CUInt8T, 256] = [ + 0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0, +] + +_CURSOR_RESIZE_EW: t.CArray[t.CUInt8T, 256] = [ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0, + 0,0,0,1,2,2,1,1,1,2,2,1,0,0,0,0, + 0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0, + 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0, + 0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0, + 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0, + 0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0, + 0,0,0,1,2,2,1,1,1,2,2,1,0,0,0,0, + 0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +] + +_CURSOR_RESIZE_NS: t.CArray[t.CUInt8T, 256] = [ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0, + 0,0,1,2,2,2,2,1,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,1,2,2,2,2,1,0,0,0,0,0,0,0,0, + 0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +] + +_CURSOR_RESIZE_NWSE: t.CArray[t.CUInt8T, 256] = [ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, + 0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,1,0,0,0,0,0,0,0, + 0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,0, + 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0, + 0,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0, + 0,0,0,0,0,0,0,0,0,1,2,2,1,0,0,0, + 0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +] +_last_clock_sec: t.CUInt32T = 0 +_prev_btn: t.CUInt8T = 0 + +EVENT_QUEUE_SIZE: t.CDefine = 32 +EVENT_TYPE_NONE: t.CDefine = 0 +EVENT_TYPE_CLICK: t.CDefine = 1 +EVENT_TYPE_HOVER: t.CDefine = 2 +EVENT_TYPE_RELEASE: t.CDefine = 3 +EVENT_TYPE_HOVER_LEAVE: t.CDefine = 4 +EVENT_TYPE_KEY: t.CDefine = 5 +EVENT_TYPE_KEY_RELEASE: t.CDefine = 6 +EVENT_TYPE_PING: t.CDefine = 7 +EVENT_TYPE_RESIZE: t.CDefine = 8 + +RESIZE_EDGE_NONE: t.CDefine = 0 +RESIZE_EDGE_RIGHT: t.CDefine = 1 +RESIZE_EDGE_BOTTOM: t.CDefine = 2 +RESIZE_EDGE_CORNER: t.CDefine = 3 +RESIZE_BORDER: t.CDefine = 6 +RESIZE_MIN_W: t.CDefine = 100 +RESIZE_MIN_H: t.CDefine = 60 + +CURSOR_DEFAULT: t.CDefine = 0 +CURSOR_IBEAM: t.CDefine = 1 +CURSOR_RESIZE_EW: t.CDefine = 2 +CURSOR_RESIZE_NS: t.CDefine = 3 +CURSOR_RESIZE_NWSE: t.CDefine = 4 + +KEY_STATE_PRESS: t.CDefine = 1 +KEY_STATE_RELEASE: t.CDefine = 2 +KEY_STATE_REPEAT: t.CDefine = 3 + +TYPEMATIC_DELAY: t.CDefine = 500 +TYPEMATIC_RATE: t.CDefine = 40 + +HELD_KEY_MAX: t.CDefine = 8 + +class held_key_info(t.CStruct): + keycode: t.CUInt8T + is_usb: t.CUInt8T + press_time: t.CUInt64T + last_repeat: t.CUInt64T + +_held_keys: t.CArray[held_key_info, HELD_KEY_MAX] +_held_key_count: t.CInt = 0 + +@c.Attribute(t.attr.packed) +class InputEvent: + type: t.CUInt32T + win_id: t.CInt + x: t.CInt + y: t.CInt + button: t.CUInt8T + _pad0: t.CUInt8T + _pad1: t.CUInt8T + _pad2: t.CUInt8T + +INPUT_EVENT_SIZE: t.CDefine = 20 + +_event_queue: t.CArray[InputEvent, EVENT_QUEUE_SIZE] +_event_head: t.CInt = 0 +_event_tail: t.CInt = 0 +_last_hover_win: t.CInt = -1 +_focused_win: t.CInt = -1 + +KBD_DEV_PS2: t.CDefine = 0 +KBD_DEV_USB: t.CDefine = 1 + +_ps2_shift_l: t.CUInt8T = 0 +_ps2_shift_r: t.CUInt8T = 0 +_ps2_caps: t.CUInt8T = 0 +_ps2_e0_pending: t.CUInt8T = 0 +_kbd_irq_masked: t.CInt = 0 + +VK_LEFT: t.CDefine = -1 +VK_RIGHT: t.CDefine = -2 +VK_UP: t.CDefine = -3 +VK_DOWN: t.CDefine = -4 +VK_HOME: t.CDefine = -5 +VK_END: t.CDefine = -6 +VK_DELETE: t.CDefine = -7 + +_ps2_set1: t.CArray[t.CChar, 128] = [ + '\0', '\x1b', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', + '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', + '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '`', '\0', + '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' ', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '\0', '\0', '\0', '\0', + '\0', '+', '\0', '\0', '\0', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0' +] + +_ps2_set1_shift: t.CArray[t.CChar, 128] = [ + '\0', '\x1b', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', + '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', + '\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', '\0', + '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', '\0', '\0', '\0', ' ', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '\0', '\0', '\0', '\0', + '\0', '+', '\0', '\0', '\0', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0' +] + +def _ps2_scancode_to_ascii(scancode: t.CUInt8T) -> t.CInt: + code: t.CUInt8T = scancode & t.CUInt8T(0x7F) + if code >= 128: return 0 + shifted: t.CInt = 0 + if (_ps2_shift_l or _ps2_shift_r) != 0: + shifted = 1 + if _ps2_caps != 0: + if code >= 0x10 and code <= 0x32: + shifted = shifted ^ 1 + if shifted: + return t.CInt(_ps2_set1_shift[code]) + return t.CInt(_ps2_set1[code]) + + +def _push_event(evt: InputEvent): + global _event_head, _event_tail + if evt.type == EVENT_TYPE_RESIZE: + i: t.CInt = _event_head + while i != _event_tail: + if _event_queue[i].type == EVENT_TYPE_RESIZE and _event_queue[i].win_id == evt.win_id: + _event_queue[i].type = EVENT_TYPE_NONE + i = (i + 1) % EVENT_QUEUE_SIZE + next_tail: t.CInt = (_event_tail + 1) % EVENT_QUEUE_SIZE + if next_tail == _event_head: + _event_head = (_event_head + 1) % EVENT_QUEUE_SIZE + string.memcpy(c.Addr(_event_queue[_event_tail]), c.Addr(evt), INPUT_EVENT_SIZE) + _event_tail = next_tail + +def poll_event(buf_ptr: t.CVoid | t.CPtr, caller_pid: t.CInt) -> t.CInt: + global _event_head, _event_tail + while _event_head != _event_tail: + if _event_queue[_event_head].type == EVENT_TYPE_NONE: + _event_head = (_event_head + 1) % EVENT_QUEUE_SIZE + continue + evt_win: t.CInt = _event_queue[_event_head].win_id + evt_pid: t.CInt = 0 + win_ok: t.CInt = 0 + if evt_win >= 0 and evt_win < _win_count: + evt_pid = _wins[evt_win].pid + win_ok = 1 + if evt_pid == caller_pid: + string.memcpy(buf_ptr, c.Addr(_event_queue[_event_head]), INPUT_EVENT_SIZE) + _event_head = (_event_head + 1) % EVENT_QUEUE_SIZE + return 1 + if win_ok and not _wins[evt_win].active: + _event_head = (_event_head + 1) % EVENT_QUEUE_SIZE + continue + scan: t.CInt = (_event_head + 1) % EVENT_QUEUE_SIZE + while scan != _event_tail: + if _event_queue[scan].type == EVENT_TYPE_NONE: + scan = (scan + 1) % EVENT_QUEUE_SIZE + continue + evt_win2: t.CInt = _event_queue[scan].win_id + evt_pid2: t.CInt = 0 + if evt_win2 >= 0 and evt_win2 < _win_count: + evt_pid2 = _wins[evt_win2].pid + if evt_pid2 == caller_pid: + string.memcpy(buf_ptr, c.Addr(_event_queue[scan]), INPUT_EVENT_SIZE) + i: t.CInt = scan + while i != _event_tail: + next_i: t.CInt = (i + 1) % EVENT_QUEUE_SIZE + if next_i != _event_head: + string.memcpy(c.Addr(_event_queue[i]), c.Addr(_event_queue[next_i]), INPUT_EVENT_SIZE) + i = next_i + if _event_tail == 0: + _event_tail = EVENT_QUEUE_SIZE - 1 + else: + _event_tail = _event_tail - 1 + return 1 + scan = (scan + 1) % EVENT_QUEUE_SIZE + break + return 0 + +def _get_title_height(style: t.CInt) -> t.CInt: + if style == STYLE_CLASSIC: + return TITLE_HEIGHT_CLASSIC + return TITLE_HEIGHT_WIN + +def _mark_rows_dirty(start_y: t.CInt, count: t.CInt): + global _dirty_count, _dirty_y_min, _dirty_y_max + for i in range(start_y, start_y + count): + if i >= 0 and i < _screen_h: + if not _dirty_rows[i]: + _dirty_rows[i] = 1 + _dirty_count += 1 + if i < _dirty_y_min or _dirty_count == 1: + _dirty_y_min = i + if i > _dirty_y_max or _dirty_count == 1: + _dirty_y_max = i + +def _mark_all_dirty(): + global _dirty_count, _dirty_y_min, _dirty_y_max + string.memset(_dirty_rows, 1, _screen_h) + _dirty_count = _screen_h + _dirty_y_min = 0 + _dirty_y_max = _screen_h - 1 + +def _has_dirty_rows() -> t.CInt: + return _dirty_count + +def _clear_dirty_flags(): + global _dirty_count, _dirty_y_min, _dirty_y_max + string.memset(_dirty_rows, 0, _screen_h) + _dirty_count = 0 + _dirty_y_min = _screen_h + _dirty_y_max = -1 + +def _wait_vblank(): + global _vblank_ok + if _vblank_ok == 0: + return + timeout: t.CInt = 50000 + while (asm.inb(0x3DA) & 0x08) != 0: + timeout -= 1 + if timeout <= 0: + _vblank_ok = 0 + return + while (asm.inb(0x3DA) & 0x08) == 0: + timeout -= 1 + if timeout <= 0: + _vblank_ok = 0 + return + _vblank_ok = 1 + +def _fill_rect(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T): + if rx < 0: rw += rx; rx = 0 + if ry < 0: rh += ry; ry = 0 + if rx + rw > bw: rw = bw - rx + if ry + rh > bh: rh = bh - ry + if rw <= 0 or rh <= 0: return + if _row_buf: + for i in range(rw): + _row_buf[i] = color + row_bytes: t.CInt = rw * 4 + for py in range(ry, ry + rh): + string.memcpy(t.CVoid(t.CUInt64T(buf) + (py * bw + rx) * 4, t.CPtr), _row_buf, row_bytes) + +def _alpha_blend(bg: t.CUInt32T, fg: t.CUInt32T, alpha: t.CInt) -> t.CUInt32T: + a: t.CInt = alpha + ia: t.CInt = 255 - a + bg_r: t.CInt = t.CInt(bg & 0xFF) + bg_g: t.CInt = t.CInt((bg >> 8) & 0xFF) + bg_b: t.CInt = t.CInt((bg >> 16) & 0xFF) + fg_r: t.CInt = t.CInt(fg & 0xFF) + fg_g: t.CInt = t.CInt((fg >> 8) & 0xFF) + fg_b: t.CInt = t.CInt((fg >> 16) & 0xFF) + r: t.CInt = (fg_r * a + bg_r * ia) >> 8 + g: t.CInt = (fg_g * a + bg_g * ia) >> 8 + b: t.CInt = (fg_b * a + bg_b * ia) >> 8 + return t.CUInt32T(r | (g << 8) | (b << 16) | 0xFF000000) + +def _is_in_corner(px: t.CInt, py: t.CInt, cx: t.CInt, cy: t.CInt, r: t.CInt) -> t.CInt: + dx: t.CInt = px - cx + dy: t.CInt = py - cy + if dx < 0: dx = -dx + if dy < 0: dy = -dy + if dx <= r and dy <= r: + dist2: t.CInt = dx * dx + dy * dy + r2: t.CInt = r * r + if dist2 > r2: + return 0 + return 1 + +def _is_on_border(rx: t.CInt, ry: t.CInt, ww: t.CInt, wh: t.CInt, r: t.CInt) -> t.CInt: + if rx == 0 or rx == ww - 1 or ry == 0 or ry == wh - 1: + return 1 + if ry < r: + if rx < r: + dx: t.CInt = rx - r + dy: t.CInt = ry - r + dist2: t.CInt = dx * dx + dy * dy + if dist2 >= (r - 1) * (r - 1): return 1 + elif rx >= ww - r: + dx = rx - (ww - r - 1) + dy = ry - r + dist2 = dx * dx + dy * dy + if dist2 >= (r - 1) * (r - 1): return 1 + elif ry >= wh - r: + if rx < r: + dx = rx - r + dy = ry - (wh - r - 1) + dist2 = dx * dx + dy * dy + if dist2 >= (r - 1) * (r - 1): return 1 + elif rx >= ww - r: + dx = rx - (ww - r - 1) + dy = ry - (wh - r - 1) + dist2 = dx * dx + dy * dy + if dist2 >= (r - 1) * (r - 1): return 1 + return 0 + +def _corner_alpha(px: t.CInt, py: t.CInt, cx: t.CInt, cy: t.CInt, r: t.CInt) -> t.CInt: + dx: t.CInt = px - cx + dy: t.CInt = py - cy + if dx < 0: dx = -dx + if dy < 0: dy = -dy + if dx <= r and dy <= r: + dist2: t.CInt = dx * dx + dy * dy + r2: t.CInt = r * r + if dist2 > r2: + return 0 + outer: t.CInt = (r + 1) * (r + 1) + if dist2 >= outer: + return 160 + return 255 + return 255 + +def _draw_char(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, x: t.CInt, y: t.CInt, ch: t.CChar, color: t.CUInt32T): + cval: t.CUInt8T = t.CUInt8T(ch) + if cval >= 128: cval = 0 + for fy in range(16): + if y + fy < 0 or y + fy >= bh: continue + row: t.CUInt8T = vga.font[cval][fy] + if row == 0: continue + for fx in range(8): + if row & (0x80 >> fx): + px: t.CInt = x + fx + if px >= 0 and px < bw: + buf[(y + fy) * bw + px] = color + +def _draw_text(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, x: t.CInt, y: t.CInt, s: t.CConst | str, color: t.CUInt32T): + cx: t.CInt = x + cy: t.CInt = y + for ch in s: + if ch == 10: + cy += 16 + cx = x + else: + _draw_char(buf, bw, bh, cx, cy, ch, color) + cx += 8 + +_drag_idx: t.CInt = -1 + +def _eff_w(aw2: AppWindow | t.CPtr) -> t.CInt: + if aw2.resize_w != 0: return aw2.resize_w + return aw2.w + +def _eff_h(aw2: AppWindow | t.CPtr) -> t.CInt: + if aw2.resize_h != 0: return aw2.resize_h + return aw2.content_h +_drag_off_x: t.CInt = 0 +_drag_off_y: t.CInt = 0 +_rsz_drag_idx: t.CInt = -1 +_rsz_edge: t.CInt = 0 +_rsz_start_x: t.CInt = 0 +_rsz_start_y: t.CInt = 0 +_rsz_start_w: t.CInt = 0 +_rsz_start_h: t.CInt = 0 +_rsz_last_w: t.CInt = 0 +_rsz_last_h: t.CInt = 0 +_rsz_last_tick: t.CUInt64T = 0 +_mouse_moved: t.CInt = 0 + +def _bring_to_front(idx: t.CInt): + global _next_z + if idx < 0 or idx >= _win_count: return + aw: AppWindow | t.CPtr = c.Addr(_wins[idx]) + if not aw.active or aw.minimized: return + _next_z += 1 + aw.z_order = _next_z + +def _hit_test_window(mx: t.CInt, my: t.CInt) -> t.CInt: + best: t.CInt = -1 + best_z: t.CInt = -1 + for ai in range(_win_count): + if _wins[ai].active and not _wins[ai].minimized: + ax: t.CInt = _wins[ai].x + ay: t.CInt = _wins[ai].y + aw: t.CInt = _eff_w(c.Addr(_wins[ai])) + ah: t.CInt = _wins[ai].h + if mx >= ax and mx < ax + aw and my >= ay and my < ay + ah: + z: t.CInt = _wins[ai].z_order + if z > best_z: + best_z = z + best = ai + return best + +def _hit_test_resize(mx: t.CInt, my: t.CInt, idx: t.CInt) -> t.CInt: + if idx < 0 or idx >= _win_count: return RESIZE_EDGE_NONE + aw: AppWindow | t.CPtr = c.Addr(_wins[idx]) + if not aw.active or aw.minimized: return RESIZE_EDGE_NONE + if not aw.resizable_w and not aw.resizable_h: return RESIZE_EDGE_NONE + on_right: t.CInt = 0 + on_bottom: t.CInt = 0 + if aw.resizable_w: + if mx >= aw.x + _eff_w(aw) - RESIZE_BORDER and mx < aw.x + _eff_w(aw): + on_right = 1 + if aw.resizable_h: + if my >= aw.y + aw.h - RESIZE_BORDER and my < aw.y + aw.h: + on_bottom = 1 + if on_right and on_bottom: return RESIZE_EDGE_CORNER + if on_right: return RESIZE_EDGE_RIGHT + if on_bottom: return RESIZE_EDGE_BOTTOM + return RESIZE_EDGE_NONE + +def _hid_to_ascii(keycode: t.CUInt8T, modifier: t.CUInt8T) -> t.CInt: + global _ps2_caps + shift: t.CInt = 0 + if (modifier & 0x22) != 0: + shift = 1 + kc: t.CInt = t.CInt(keycode) + if kc >= 4 and kc <= 29: + if _ps2_caps != 0: + shift = shift ^ 1 + if shift: + return kc - 4 + 65 + return kc - 4 + 97 + if kc >= 30 and kc <= 38: + if shift: + shifts9: t.CArray[t.CInt, 9] + shifts9[0] = 33; shifts9[1] = 64; shifts9[2] = 35 + shifts9[3] = 36; shifts9[4] = 37; shifts9[5] = 94 + shifts9[6] = 38; shifts9[7] = 42; shifts9[8] = 40 + return shifts9[kc - 30] + return kc - 30 + 49 + if kc == 39: + if shift: return 41 + return 48 + if kc == 44: return 32 + if kc == 40: return 13 + if kc == 42: return 8 + if kc == 43: return 9 + if kc == 41: return 27 + if kc == 45: + if shift: return 95 + return 45 + if kc == 46: + if shift: return 43 + return 61 + if kc == 47: + if shift: return 123 + return 91 + if kc == 48: + if shift: return 125 + return 93 + if kc == 49: + if shift: return 124 + return 92 + if kc == 51: + if shift: return 58 + return 59 + if kc == 52: + if shift: return 34 + return 39 + if kc == 53: + if shift: return 126 + return 96 + if kc == 54: + if shift: return 60 + return 44 + if kc == 55: + if shift: return 62 + return 46 + if kc == 56: + if shift: return 63 + return 47 + return 0 + +def _held_key_add(keycode: t.CUInt8T, is_usb: t.CUInt8T): + global _held_key_count + if _held_key_count >= HELD_KEY_MAX: return + for i in range(_held_key_count): + if _held_keys[i].keycode == keycode and _held_keys[i].is_usb == is_usb: + return + now: t.CUInt64T = timer.timer_get_ticks() + _held_keys[_held_key_count].keycode = keycode + _held_keys[_held_key_count].is_usb = is_usb + _held_keys[_held_key_count].press_time = now + _held_keys[_held_key_count].last_repeat = now + _held_key_count += 1 + +def _held_key_remove(keycode: t.CUInt8T, is_usb: t.CUInt8T): + global _held_key_count + for i in range(_held_key_count): + if _held_keys[i].keycode == keycode and _held_keys[i].is_usb == is_usb: + for j in range(i, _held_key_count - 1): + _held_keys[j].keycode = _held_keys[j + 1].keycode + _held_keys[j].is_usb = _held_keys[j + 1].is_usb + _held_keys[j].press_time = _held_keys[j + 1].press_time + _held_keys[j].last_repeat = _held_keys[j + 1].last_repeat + _held_key_count -= 1 + return + +def _check_key_repeat(): + global _held_key_count + if _focused_win < 0 or _focused_win >= _win_count: return + if not _wins[_focused_win].active: return + now: t.CUInt64T = timer.timer_get_ticks() + for i in range(_held_key_count): + hk: held_key_info | t.CPtr = c.Addr(_held_keys[i]) + elapsed: t.CUInt64T = now - hk.press_time + if elapsed < t.CUInt64T(TYPEMATIC_DELAY): + continue + since_last: t.CUInt64T = now - hk.last_repeat + if since_last >= t.CUInt64T(TYPEMATIC_RATE): + hk.last_repeat = now + ascii_ch: t.CInt = 0 + if hk.is_usb: + mod: t.CUInt8T = 0 + if _ps2_shift_l or _ps2_shift_r: + mod = mod | 0x22 + if _ps2_caps: + mod = mod | 0x01 + ascii_ch = _hid_to_ascii(hk.keycode, mod) + else: + ascii_ch = _ps2_scancode_to_ascii(hk.keycode) + if ascii_ch != 0: + rep_evt: InputEvent + rep_evt.type = EVENT_TYPE_KEY + rep_evt.win_id = _focused_win + rep_evt.x = ascii_ch + rep_evt.y = t.CInt(hk.keycode) + rep_evt.button = KEY_STATE_REPEAT + rep_evt._pad0 = 0 + rep_evt._pad1 = 0 + rep_evt._pad2 = 0 + _push_event(rep_evt) + +def _update_keyboard(): + global _focused_win, _kbd_irq_masked + global _ps2_shift_l, _ps2_shift_r, _ps2_caps + global _ps2_e0_pending + # Quick check: skip if no PS/2 data and no USB data + ps2_status: t.CUInt8T = asm.inb(0x64) + if (ps2_status & 0x01) == 0 and not usb_kbd.has_data(): + _check_key_repeat() + return + if _kbd_irq_masked == 0: + pic.clearMask(1) + _kbd_irq_masked = 1 + kbd_cnt: t.CInt = 0 + while usb_kbd.has_data() and kbd_cnt < 32: + kbd_cnt += 1 + kevt: usb_kbd.usb_kbd_event + usb_kbd.read_event(c.Addr(kevt)) + if kevt.keycode == 0x39 and kevt.event_type == usb_kbd.KBD_EVT_PRESS: + _ps2_caps = _ps2_caps ^ t.CUInt8T(1) + if kevt.event_type == usb_kbd.KBD_EVT_PRESS: + _held_key_add(kevt.keycode, 1) + usb_ascii: t.CInt = _hid_to_ascii(kevt.keycode, kevt.modifier) + if usb_ascii == 0: + kc2: t.CInt = t.CInt(kevt.keycode) + if kc2 == 0x50: usb_ascii = VK_LEFT + elif kc2 == 0x4F: usb_ascii = VK_RIGHT + elif kc2 == 0x52: usb_ascii = VK_UP + elif kc2 == 0x51: usb_ascii = VK_DOWN + elif kc2 == 0x4A: usb_ascii = VK_HOME + elif kc2 == 0x4D: usb_ascii = VK_END + elif kc2 == 0x4C: usb_ascii = VK_DELETE + if usb_ascii != 0 and _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active: + usb_evt: InputEvent + usb_evt.type = EVENT_TYPE_KEY + usb_evt.win_id = _focused_win + usb_evt.x = usb_ascii + usb_evt.y = t.CInt(kevt.keycode) + usb_evt.button = KEY_STATE_PRESS + usb_evt._pad0 = 0 + usb_evt._pad1 = 0 + usb_evt._pad2 = 0 + _push_event(usb_evt) + elif kevt.event_type == usb_kbd.KBD_EVT_RELEASE: + _held_key_remove(kevt.keycode, 1) + if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active: + rel_evt: InputEvent + rel_evt.type = EVENT_TYPE_KEY_RELEASE + rel_evt.win_id = _focused_win + rel_evt.x = 0 + rel_evt.y = t.CInt(kevt.keycode) + rel_evt.button = KEY_STATE_RELEASE + rel_evt._pad0 = 0 + rel_evt._pad1 = 0 + rel_evt._pad2 = 0 + _push_event(rel_evt) + ps2_poll_count: t.CInt = 0 + while ps2_poll_count < 16: + ps2_status: t.CUInt8T = asm.inb(0x64) + if (ps2_status & 0x01) == 0: + break + if (ps2_status & 0x20) != 0: + asm.inb(0x60) + ps2_poll_count += 1 + continue + sc: t.CUInt8T = asm.inb(0x60) + ps2_poll_count += 1 + if sc == 0x00 or sc == 0xFA or sc == 0xFE or sc == 0xFF: + _ps2_e0_pending = 0 + continue + if sc == 0xE0: + _ps2_e0_pending = 1 + continue + if sc == 0xE1: + _ps2_e0_pending = 0 + continue + if _ps2_e0_pending != 0: + _ps2_e0_pending = 0 + e0_code: t.CUInt8T = sc & t.CUInt8T(0x7F) + if (sc & 0x80) != 0: + _held_key_remove(e0_code | 0x80, 0) + if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active: + ps2_rel2: InputEvent + ps2_rel2.type = EVENT_TYPE_KEY_RELEASE + ps2_rel2.win_id = _focused_win + ps2_rel2.x = 0 + ps2_rel2.y = t.CInt(e0_code | 0x80) + ps2_rel2.button = KEY_STATE_RELEASE + ps2_rel2._pad0 = 0 + ps2_rel2._pad1 = 0 + ps2_rel2._pad2 = 0 + _push_event(ps2_rel2) + continue + vk: t.CInt = 0 + if e0_code == 0x4B: vk = VK_LEFT + elif e0_code == 0x4D: vk = VK_RIGHT + elif e0_code == 0x48: vk = VK_UP + elif e0_code == 0x50: vk = VK_DOWN + elif e0_code == 0x47: vk = VK_HOME + elif e0_code == 0x4F: vk = VK_END + elif e0_code == 0x53: vk = VK_DELETE + if vk != 0: + _held_key_add(e0_code | 0x80, 0) + if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active: + e0_evt: InputEvent + e0_evt.type = EVENT_TYPE_KEY + e0_evt.win_id = _focused_win + e0_evt.x = vk + e0_evt.y = t.CInt(e0_code | 0x80) + e0_evt.button = KEY_STATE_PRESS + e0_evt._pad0 = 0 + e0_evt._pad1 = 0 + e0_evt._pad2 = 0 + _push_event(e0_evt) + continue + if sc == 0x2A: + _ps2_shift_l = 1 + continue + if sc == 0xAA: + _ps2_shift_l = 0 + continue + if sc == 0x36: + _ps2_shift_r = 1 + continue + if sc == 0xB6: + _ps2_shift_r = 0 + continue + if sc == 0x3A: + _ps2_caps = _ps2_caps ^ t.CUInt8T(1) + continue + if (sc & 0x80) != 0: + release_sc: t.CUInt8T = sc & t.CUInt8T(0x7F) + _held_key_remove(release_sc, 0) + if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active: + ps2_rel: InputEvent + ps2_rel.type = EVENT_TYPE_KEY_RELEASE + ps2_rel.win_id = _focused_win + ps2_rel.x = 0 + ps2_rel.y = t.CInt(release_sc) + ps2_rel.button = KEY_STATE_RELEASE + ps2_rel._pad0 = 0 + ps2_rel._pad1 = 0 + ps2_rel._pad2 = 0 + _push_event(ps2_rel) + continue + _held_key_add(sc, 0) + ascii_ch: t.CInt = _ps2_scancode_to_ascii(sc) + if ascii_ch != 0 and _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active: + push_evt: InputEvent + push_evt.type = EVENT_TYPE_KEY + push_evt.win_id = _focused_win + push_evt.x = ascii_ch + push_evt.y = t.CInt(sc) + push_evt.button = KEY_STATE_PRESS + push_evt._pad0 = 0 + push_evt._pad1 = 0 + push_evt._pad2 = 0 + _push_event(push_evt) + _check_key_repeat() + +def set_cursor_type(win_id: t.CInt, cursor_t: t.CInt): + if win_id >= 0 and win_id < _win_count: + _wins[win_id].app_cursor_type = cursor_t + _wins[win_id].cursor_type = cursor_t + +def _update_mouse() -> t.CInt: + global _cursor_x, _cursor_y, _cursor_buttons + global _drag_idx, _drag_off_x, _drag_off_y + global _mouse_moved, _prev_btn, _last_hover_win, _focused_win + global _rsz_drag_idx, _rsz_edge, _rsz_start_x, _rsz_start_y + global _rsz_start_w, _rsz_start_h, _rsz_last_w, _rsz_last_h, _rsz_last_tick + moved: t.CInt = 0 + poll_cnt: t.CInt = 0 + while (mouse.has_data() or usb_mouse.has_data()) and poll_cnt < 32: + poll_cnt += 1 + pkt_dx: t.CInt32T = 0 + pkt_dy: t.CInt32T = 0 + pkt_btn: t.CUInt8T = 0 + if mouse.has_data(): + pkt: mouse.mouse_packet + mouse.read_packet(c.Addr(pkt)) + pkt_dx = t.CInt32T(pkt.dx) + pkt_dy = -t.CInt32T(pkt.dy) + pkt_btn = pkt.buttons + elif usb_mouse.has_data(): + upkt: usb_mouse.usb_mouse_packet + usb_mouse.read_packet(c.Addr(upkt)) + pkt_dx = t.CInt32T(upkt.dx) + pkt_dy = -t.CInt32T(upkt.dy) + pkt_btn = upkt.buttons + if pkt_dx != 0 or pkt_dy != 0: + _cursor_x = _cursor_x + pkt_dx + _cursor_y = _cursor_y + pkt_dy + if _cursor_x < 0: _cursor_x = 0 + if _cursor_y < 0: _cursor_y = 0 + if _cursor_x >= _screen_w: _cursor_x = _screen_w - 1 + if _cursor_y >= _screen_h: _cursor_y = _screen_h - 1 + moved = 1 + _mouse_moved = 1 + btn_down: t.CUInt8T = pkt_btn & 0x01 + prev_down: t.CUInt8T = _prev_btn & 0x01 + _cursor_buttons = pkt_btn + _prev_btn = pkt_btn + if btn_down and not prev_down: + hit_app: t.CInt = _hit_test_window(_cursor_x, _cursor_y) + if hit_app >= 0: + aw: AppWindow | t.CPtr = c.Addr(_wins[hit_app]) + th: t.CInt = _get_title_height(aw.style) + btn_w: t.CInt = CLOSE_BTN_SIZE + btn_h: t.CInt = CLOSE_BTN_SIZE + ew: t.CInt = _eff_w(aw) + btn_x: t.CInt = aw.x + ew - btn_w - 4 + btn_y: t.CInt = aw.y + (th - btn_h) // 2 + if aw.style == STYLE_WIN7: + btn_w = CLOSE_BTN_W7_W + btn_h = CLOSE_BTN_W7_H + btn_x = aw.x + ew - btn_w - 5 + btn_y = aw.y + (th - btn_h) // 2 + elif aw.style == STYLE_WINXP: + btn_w = CLOSE_BTN_XP_SZ + btn_h = CLOSE_BTN_XP_SZ + btn_x = aw.x + ew - btn_w - 5 + btn_y = aw.y + (th - btn_h) // 2 + elif aw.style == STYLE_WIN10 or aw.style == STYLE_WIN11: + btn_w = CLOSE_BTN_W_WIN + btn_h = th - 2 + btn_x = aw.x + ew - btn_w + btn_y = aw.y + 1 + if _cursor_x >= btn_x and _cursor_x < btn_x + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h: + aw.active = 0 + if _focused_win == hit_app: + _focused_win = -1 + j: t.CInt = _win_count - 1 + while j >= 0: + if _wins[j].active and not _wins[j].minimized: + _focused_win = j + break + j -= 1 + if _last_hover_win == hit_app: + _last_hover_win = -1 + _mark_all_dirty() + else: + max_bx2: t.CInt = btn_x - btn_w + if aw.style == STYLE_WIN7 or aw.style == STYLE_WINXP: + max_bx2 = btn_x - btn_w - 1 + if _cursor_x >= max_bx2 and _cursor_x < max_bx2 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h: + if aw.is_maximized: + aw.is_maximized = 0 + aw.x = aw.orig_x + aw.y = aw.orig_y + aw.h = aw.orig_h + aw.resize_w = aw.orig_w + aw.resize_h = aw.orig_h - _get_title_height(aw.style) - 2 + else: + aw.is_maximized = 1 + aw.orig_x = aw.w + aw.orig_y = aw.y + aw.orig_w = aw.w + aw.orig_h = aw.h + aw.x = 0 + aw.y = 0 + tb_h_mx: t.CInt = TASKBAR_HEIGHT + aw.h = _screen_h - tb_h_mx + aw.resize_w = _screen_w + aw.resize_h = _screen_h - tb_h_mx - _get_title_height(aw.style) - 2 + rs_evt3: InputEvent + rs_evt3.type = EVENT_TYPE_RESIZE + rs_evt3.win_id = hit_app + rs_evt3.x = aw.resize_w + rs_evt3.y = aw.resize_h + rs_evt3.button = 0 + rs_evt3._pad0 = 0 + rs_evt3._pad1 = 0 + rs_evt3._pad2 = 0 + _push_event(rs_evt3) + _mark_all_dirty() + else: + min_bx2: t.CInt = max_bx2 - btn_w + if aw.style == STYLE_WIN7 or aw.style == STYLE_WINXP: + min_bx2 = max_bx2 - btn_w - 1 + if _cursor_x >= min_bx2 and _cursor_x < min_bx2 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h: + aw.minimized = 1 + if _focused_win == hit_app: + _focused_win = -1 + j2: t.CInt = _win_count - 1 + while j2 >= 0: + if _wins[j2].active and not _wins[j2].minimized: + _focused_win = j2 + break + j2 -= 1 + _mark_all_dirty() + else: + re: t.CInt = _hit_test_resize(_cursor_x, _cursor_y, hit_app) + if re != RESIZE_EDGE_NONE: + _rsz_drag_idx = hit_app + _rsz_edge = re + _rsz_start_x = _cursor_x + _rsz_start_y = _cursor_y + _rsz_start_w = _eff_w(aw) + _rsz_start_h = aw.h + _rsz_last_w = _eff_w(aw) + _rsz_last_h = aw.h + _rsz_last_tick = timer.timer_get_ticks() + _bring_to_front(hit_app) + _focused_win = hit_app + _mark_all_dirty() + elif _cursor_y >= aw.y and _cursor_y < aw.y + th: + _drag_idx = hit_app + _drag_off_x = _cursor_x - aw.x + _drag_off_y = _cursor_y - aw.y + _bring_to_front(hit_app) + _focused_win = hit_app + _mark_all_dirty() + else: + _bring_to_front(hit_app) + _focused_win = hit_app + _mark_all_dirty() + evt: InputEvent + evt.type = EVENT_TYPE_CLICK + evt.win_id = hit_app + evt.x = _cursor_x - aw.x + evt.y = _cursor_y - aw.y - _get_title_height(aw.style) + evt.button = 1 + evt._pad0 = 0 + evt._pad1 = 0 + evt._pad2 = 0 + _push_event(evt) + else: + tb_h_cl: t.CInt = TASKBAR_HEIGHT + bar_y2: t.CInt = _screen_h - tb_h_cl + if _cursor_y >= bar_y2: + logo_x2: t.CInt = 4 + logo_sz2: t.CInt = TASKBAR_LOGO_SZ + logo_r2: t.CInt = logo_x2 + logo_sz2 + logo_b2: t.CInt = bar_y2 + tb_h_cl + if _cursor_x >= logo_x2 and _cursor_x < logo_r2 and _cursor_y >= bar_y2 and _cursor_y < logo_b2: + if _focused_win >= 0 and _focused_win < _win_count: + if _wins[_focused_win].active: + _wins[_focused_win].active = 0 + _focused_win = -1 + _mark_all_dirty() + else: + icon_sz2: t.CInt = TASKBAR_ICON_SZ + icon_pad2: t.CInt = TASKBAR_ICON_PAD + icon_x2: t.CInt = logo_x2 + logo_sz2 + 80 + icon_cnt2: t.CInt = 0 + for ti2 in range(_win_count): + if _wins[ti2].active: + ix2: t.CInt = icon_x2 + icon_cnt2 * (icon_sz2 + icon_pad2) + iy2: t.CInt = bar_y2 + (tb_h_cl - icon_sz2) // 2 + if _cursor_x >= ix2 and _cursor_x < ix2 + icon_sz2 and _cursor_y >= iy2 and _cursor_y < iy2 + icon_sz2: + if _wins[ti2].minimized: + _wins[ti2].minimized = 0 + _bring_to_front(ti2) + _focused_win = ti2 + elif _focused_win == ti2: + _wins[ti2].minimized = 1 + _focused_win = -1 + for j3 in range(_win_count - 1, -1, -1): + if _wins[j3].active and not _wins[j3].minimized: + _focused_win = j3 + break + else: + _bring_to_front(ti2) + _focused_win = ti2 + _mark_all_dirty() + break + icon_cnt2 += 1 + if not btn_down: + if prev_down: + if _rsz_drag_idx >= 0: + rs_aw: AppWindow | t.CPtr = c.Addr(_wins[_rsz_drag_idx]) + final_w: t.CInt = _rsz_last_w + final_h: t.CInt = _rsz_last_h + old_h_mu: t.CInt = _eff_h(rs_aw) + _get_title_height(rs_aw.style) + 2 + _mark_rows_dirty(rs_aw.y, old_h_mu) + final_ch: t.CInt = final_h - _get_title_height(rs_aw.style) - 2 + rs_aw.resize_w = final_w + rs_aw.resize_h = final_ch + rs_aw.h = final_h + _mark_rows_dirty(rs_aw.y, final_h) + rs_evt: InputEvent + rs_evt.type = EVENT_TYPE_RESIZE + rs_evt.win_id = _rsz_drag_idx + rs_evt.x = final_w + rs_evt.y = final_ch + rs_evt.button = 0 + rs_evt._pad0 = 0 + rs_evt._pad1 = 0 + rs_evt._pad2 = 0 + _push_event(rs_evt) + _rsz_drag_idx = -1 + _rsz_edge = RESIZE_EDGE_NONE + else: + rel_hit: t.CInt = _hit_test_window(_cursor_x, _cursor_y) + if rel_hit >= 0: + aw_rel: AppWindow | t.CPtr = c.Addr(_wins[rel_hit]) + rel_evt: InputEvent + rel_evt.type = EVENT_TYPE_RELEASE + rel_evt.win_id = rel_hit + rel_evt.x = _cursor_x - aw_rel.x + rel_evt.y = _cursor_y - aw_rel.y - _get_title_height(aw_rel.style) + rel_evt.button = 0 + rel_evt._pad0 = 0 + rel_evt._pad1 = 0 + rel_evt._pad2 = 0 + _push_event(rel_evt) + if _drag_idx >= 0: + moved = 1 + _mark_rows_dirty(_wins[_drag_idx].y, _wins[_drag_idx].h) + _drag_idx = -1 + if moved and _drag_idx < 0: + hit_hov: t.CInt = _hit_test_window(_cursor_x, _cursor_y) + if hit_hov >= 0: + if _last_hover_win >= 0 and _last_hover_win != hit_hov and _last_hover_win < _win_count: + if _wins[_last_hover_win].active: + leave_evt3: InputEvent + leave_evt3.type = EVENT_TYPE_HOVER_LEAVE + leave_evt3.win_id = _last_hover_win + leave_evt3.x = -1 + leave_evt3.y = -1 + leave_evt3.button = 0 + leave_evt3._pad0 = 0 + leave_evt3._pad1 = 0 + leave_evt3._pad2 = 0 + _push_event(leave_evt3) + aw_hov: AppWindow | t.CPtr = c.Addr(_wins[hit_hov]) + th_hov: t.CInt = _get_title_height(aw_hov.style) + hov_evt: InputEvent + hov_evt.type = EVENT_TYPE_HOVER + hov_evt.win_id = hit_hov + hov_evt.x = _cursor_x - aw_hov.x + hov_evt.y = _cursor_y - aw_hov.y - th_hov + hov_evt.button = 0 + hov_evt._pad0 = 0 + hov_evt._pad1 = 0 + hov_evt._pad2 = 0 + _push_event(hov_evt) + _last_hover_win = hit_hov + re_hov: t.CInt = _hit_test_resize(_cursor_x, _cursor_y, hit_hov) + if re_hov == RESIZE_EDGE_RIGHT: + aw_hov.cursor_type = CURSOR_RESIZE_EW + elif re_hov == RESIZE_EDGE_BOTTOM: + aw_hov.cursor_type = CURSOR_RESIZE_NS + elif re_hov == RESIZE_EDGE_CORNER: + aw_hov.cursor_type = CURSOR_RESIZE_NWSE + else: + aw_hov.cursor_type = aw_hov.app_cursor_type + else: + if _last_hover_win >= 0 and _last_hover_win < _win_count: + if _wins[_last_hover_win].active: + leave_evt: InputEvent + leave_evt.type = EVENT_TYPE_HOVER_LEAVE + leave_evt.win_id = _last_hover_win + leave_evt.x = -1 + leave_evt.y = -1 + leave_evt.button = 0 + leave_evt._pad0 = 0 + leave_evt._pad1 = 0 + leave_evt._pad2 = 0 + _push_event(leave_evt) + _last_hover_win = -1 + if _drag_idx >= 0: + aw2: AppWindow | t.CPtr = c.Addr(_wins[_drag_idx]) + _drag_old_y: t.CInt = aw2.y + _mark_rows_dirty(_drag_old_y, aw2.h) + aw2.x = _cursor_x - _drag_off_x + aw2.y = _cursor_y - _drag_off_y + _mark_rows_dirty(aw2.y, aw2.h) + if _rsz_drag_idx >= 0: + rs_aw2: AppWindow | t.CPtr = c.Addr(_wins[_rsz_drag_idx]) + dx: t.CInt = _cursor_x - _rsz_start_x + dy: t.CInt = _cursor_y - _rsz_start_y + new_w: t.CInt = _rsz_start_w + new_h: t.CInt = _rsz_start_h + if _rsz_edge == RESIZE_EDGE_RIGHT or _rsz_edge == RESIZE_EDGE_CORNER: + new_w = _rsz_start_w + dx + if new_w < RESIZE_MIN_W: new_w = RESIZE_MIN_W + if _rsz_edge == RESIZE_EDGE_BOTTOM or _rsz_edge == RESIZE_EDGE_CORNER: + new_h = _rsz_start_h + dy + if new_h < RESIZE_MIN_H: new_h = RESIZE_MIN_H + if new_w != rs_aw2.w or new_h != rs_aw2.h: + dw: t.CInt = new_w - _rsz_last_w + if dw < 0: dw = -dw + dh: t.CInt = new_h - _rsz_last_h + if dh < 0: dh = -dh + now_tick: t.CUInt64T = timer.timer_get_ticks() + elapsed: t.CUInt64T = now_tick - _rsz_last_tick + need_update: t.CInt = 0 + if dw >= 32 or dh >= 32: + need_update = 1 + elif elapsed >= 100 and (dw >= 4 or dh >= 4): + need_update = 1 + if need_update: + _rsz_last_w = new_w + _rsz_last_h = new_h + _rsz_last_tick = now_tick + old_h_rs: t.CInt = _eff_h(rs_aw2) + _get_title_height(rs_aw2.style) + 2 + _mark_rows_dirty(rs_aw2.y, old_h_rs) + new_ch: t.CInt = new_h - _get_title_height(rs_aw2.style) - 2 + rs_aw2.resize_w = new_w + rs_aw2.resize_h = new_ch + rs_aw2.h = new_h + _mark_rows_dirty(rs_aw2.y, new_h) + rs_evt2: InputEvent + rs_evt2.type = EVENT_TYPE_RESIZE + rs_evt2.win_id = _rsz_drag_idx + rs_evt2.x = new_w + rs_evt2.y = new_ch + rs_evt2.button = 0 + rs_evt2._pad0 = 0 + rs_evt2._pad1 = 0 + rs_evt2._pad2 = 0 + _push_event(rs_evt2) + if not moved and _last_hover_win < 0: + hit_hov2: t.CInt = _hit_test_window(_cursor_x, _cursor_y) + if hit_hov2 >= 0 and _wins[hit_hov2].active: + aw_hov2: AppWindow | t.CPtr = c.Addr(_wins[hit_hov2]) + th_hov2: t.CInt = _get_title_height(aw_hov2.style) + hov_evt2: InputEvent + hov_evt2.type = EVENT_TYPE_HOVER + hov_evt2.win_id = hit_hov2 + hov_evt2.x = _cursor_x - aw_hov2.x + hov_evt2.y = _cursor_y - aw_hov2.y - th_hov2 + hov_evt2.button = 0 + hov_evt2._pad0 = 0 + hov_evt2._pad1 = 0 + hov_evt2._pad2 = 0 + _push_event(hov_evt2) + _last_hover_win = hit_hov2 + if not moved and _last_hover_win >= 0: + if _last_hover_win >= _win_count or not _wins[_last_hover_win].active: + leave_evt2: InputEvent + leave_evt2.type = EVENT_TYPE_HOVER_LEAVE + leave_evt2.win_id = _last_hover_win + leave_evt2.x = -1 + leave_evt2.y = -1 + leave_evt2.button = 0 + leave_evt2._pad0 = 0 + leave_evt2._pad1 = 0 + leave_evt2._pad2 = 0 + _push_event(leave_evt2) + _last_hover_win = -1 + return moved + +_render_count: t.CInt = 0 +_cached_top_win: t.CInt = -1 +_perf_t_mouse: t.CUInt64T = 0 +_perf_t_kbd: t.CUInt64T = 0 +_perf_t_render: t.CUInt64T = 0 +_perf_t_flush: t.CUInt64T = 0 +_perf_t_total: t.CUInt64T = 0 +_perf_frames: t.CUInt32T = 0 +_perf_start_ticks: t.CUInt64T = 0 +_perf_t_bg: t.CUInt64T = 0 +_perf_t_win: t.CUInt64T = 0 +_perf_t_cur: t.CUInt64T = 0 +_perf_t_fb: t.CUInt64T = 0 + +def _render(): + global _render_count, _cached_top_win + global _perf_t_bg, _perf_t_win, _perf_t_cur, _perf_t_fb + w: t.CInt = _screen_w + h: t.CInt = _screen_h + pitch: t.CInt = _fb_pitch + back: UINT32PTR = _back_fb + + _cached_top_win = _get_top_window() + + # No need to erase cursor - dirty row mechanism redraws background + windows from scratch + + t0: t.CUInt64T = timer.timer_get_ticks() + row_bytes: t.CInt = w * 4 + if _bg_row_buf and _dirty_count > 0: + py_bg: t.CInt = _dirty_y_min + while py_bg <= _dirty_y_max: + if _dirty_rows[py_bg]: + string.memcpy(t.CVoid(t.CUInt64T(back) + py_bg * row_bytes, t.CPtr), _bg_row_buf, row_bytes) + py_bg += 1 + t1: t.CUInt64T = timer.timer_get_ticks() + _perf_t_bg += (t1 - t0) + + _draw_taskbar(back) + + t2: t.CUInt64T = timer.timer_get_ticks() + sorted: t.CArray[t.CInt, MAX_APP_WINDOWS] + cnt: t.CInt = 0 + for si in range(_win_count): + if _wins[si].active and not _wins[si].minimized: + sorted[cnt] = si + cnt += 1 + for si in range(cnt - 1): + for sj in range(si + 1, cnt): + zi: t.CInt = _wins[sorted[si]].z_order + zj: t.CInt = _wins[sorted[sj]].z_order + if zi > zj: + tmp: t.CInt = sorted[si] + sorted[si] = sorted[sj] + sorted[sj] = tmp + for si in range(cnt): + _draw_app_window(back, c.Addr(_wins[sorted[si]])) + t3: t.CUInt64T = timer.timer_get_ticks() + _perf_t_win += (t3 - t2) + + # Mark cursor rows as dirty so cursor gets flushed to framebuffer + _mark_rows_dirty(_cursor_y, CURSOR_H) + + _draw_cursor(back) + t4: t.CUInt64T = timer.timer_get_ticks() + _perf_t_cur += (t4 - t3) + + _wait_vblank() + if _dirty_count > 0: + py_fb: t.CInt = _dirty_y_min + while py_fb <= _dirty_y_max: + if _dirty_rows[py_fb]: + run_start: t.CInt = py_fb + while py_fb <= _dirty_y_max and _dirty_rows[py_fb]: + py_fb += 1 + run_bytes: t.CInt = (py_fb - run_start) * row_bytes + string.memcpy(t.CVoid(t.CUInt64T(_fb) + run_start * pitch, t.CPtr), t.CVoid(t.CUInt64T(back) + run_start * row_bytes, t.CPtr), run_bytes) + else: + py_fb += 1 + t5: t.CUInt64T = timer.timer_get_ticks() + _perf_t_fb += (t5 - t4) + + _render_count += 1 + +def _draw_taskbar(back: UINT32PTR): + w: t.CInt = _screen_w + h: t.CInt = _screen_h + tb_h: t.CInt = TASKBAR_HEIGHT + bar_y: t.CInt = h - tb_h + if bar_y + tb_h <= _dirty_y_min or bar_y > _dirty_y_max: + return + bar_col: t.CUInt32T = gfx.COLOR_RGB(32, 32, 32) + line_col: t.CUInt32T = gfx.COLOR_RGB(60, 60, 60) + + _fill_rect(back, w, h, 0, bar_y, w, tb_h, bar_col) + _fill_rect(back, w, h, 0, bar_y, w, 1, line_col) + + logo_x: t.CInt = 4 + logo_sz: t.CInt = TASKBAR_LOGO_SZ + logo_y: t.CInt = bar_y + (tb_h - logo_sz) // 2 + lc1: t.CUInt32T = gfx.COLOR_RGB(80, 120, 220) + lc2: t.CUInt32T = gfx.COLOR_RGB(50, 80, 180) + lc3: t.CUInt32T = gfx.COLOR_RGB(100, 160, 255) + for lx in range(logo_sz): + for ly in range(logo_sz): + px: t.CInt = logo_x + lx + py: t.CInt = logo_y + ly + if px >= 0 and px < w and py >= 0 and py < h: + col: t.CUInt32T = lc2 + cx: t.CInt = logo_sz // 2 + cy: t.CInt = logo_sz // 2 + dx: t.CInt = lx - cx + dy: t.CInt = ly - cy + dist: t.CInt = dx * dx + dy * dy + r2: t.CInt = cx * cx + if dist <= r2: + col = lc1 + if dist <= r2 // 2: + col = lc3 + back[py * w + px] = col + + _draw_text(back, w, h, logo_x + logo_sz + 8, bar_y + (tb_h - 16) // 2, "ViperOS", 0xFFFFFFFF) + + icon_sz: t.CInt = TASKBAR_ICON_SZ + icon_pad: t.CInt = TASKBAR_ICON_PAD + icon_x: t.CInt = logo_x + logo_sz + 80 + icon_cnt: t.CInt = 0 + for ti in range(_win_count): + if _wins[ti].active: + ix: t.CInt = icon_x + icon_cnt * (icon_sz + icon_pad) + iy: t.CInt = bar_y + (tb_h - icon_sz) // 2 + is_focused: t.CInt = 1 if ti == _focused_win else 0 + is_minimized: t.CInt = _wins[ti].minimized + ic_bg: t.CUInt32T = gfx.COLOR_RGB(40, 40, 70) + if is_focused: + ic_bg = gfx.COLOR_RGB(60, 60, 120) + elif is_minimized: + ic_bg = gfx.COLOR_RGB(30, 30, 50) + _fill_rect(back, w, h, ix, iy, icon_sz, icon_sz, ic_bg) + ic_border: t.CUInt32T = gfx.COLOR_RGB(80, 80, 140) + if is_focused: + ic_border = gfx.COLOR_RGB(120, 120, 220) + _fill_rect(back, w, h, ix, iy, icon_sz, 1, ic_border) + _fill_rect(back, w, h, ix, iy + icon_sz - 1, icon_sz, 1, ic_border) + _fill_rect(back, w, h, ix, iy, 1, icon_sz, ic_border) + _fill_rect(back, w, h, ix + icon_sz - 1, iy, 1, icon_sz, ic_border) + if _wins[ti].title[0] != 0: + first_ch: t.CChar = _wins[ti].title[0] + ch_col: t.CUInt32T = 0xFFFFFFFF + if not is_focused: + ch_col = gfx.COLOR_RGB(180, 180, 200) + ch_x: t.CInt = ix + (icon_sz - 8) // 2 + ch_y: t.CInt = iy + (icon_sz - 16) // 2 + _draw_char(back, w, h, ch_x, ch_y, first_ch, ch_col) + icon_cnt += 1 + + if _render_count % 30 == 0: + rtc.rtc_read_time() + + hr: t.CUInt8T = rtc.rtc_get_hours() + mn: t.CUInt8T = rtc.rtc_get_minutes() + sc: t.CUInt8T = rtc.rtc_get_seconds() + day: t.CUInt8T = rtc.rtc_get_day() + mon: t.CUInt8T = rtc.rtc_get_month() + yr: t.CUInt16T = rtc.rtc_get_year() + time_buf: t.CArray[t.CChar, 64] + string.memset(c.Addr(time_buf), 0, 64) + viperlib.snprintf(c.Addr(time_buf), 64, "%04u-%02u-%02u %02u:%02u:%02u", t.CUnsignedInt(yr), t.CUnsignedInt(mon), t.CUnsignedInt(day), t.CUnsignedInt(hr), t.CUnsignedInt(mn), t.CUnsignedInt(sc)) + time_str_x: t.CInt = w - 180 + _draw_text(back, w, h, time_str_x, bar_y + (tb_h - 16) // 2, time_buf, 0xFFFFFFFF) + +def _draw_window_shadow(back: UINT32PTR, bw: t.CInt, bh: t.CInt, wx: t.CInt, wy: t.CInt, ww: t.CInt, wh: t.CInt, is_rounded: t.CInt, rad: t.CInt): + if not SHADOW_ENABLED: return + sl: t.CInt = SHADOW_LEFT + sr: t.CInt = SHADOW_RIGHT + st: t.CInt = SHADOW_TOP + sb: t.CInt = SHADOW_BOTTOM + sa: t.CInt = SHADOW_ALPHA + shadow_col: t.CUInt32T = gfx.COLOR_RGB(0, 0, 0) + for py in range(wy - st, wy + wh + sb): + if py >= 0 and py < bh: + if not _dirty_rows[py]: + continue + for px in range(wx - sl, wx + ww + sr): + if px >= 0 and px < bw: + in_win: t.CInt = 0 + if px >= wx and px < wx + ww and py >= wy and py < wy + wh: + in_win = 1 + if is_rounded: + rx: t.CInt = px - wx + ry: t.CInt = py - wy + if rx < rad and ry < rad: + if (rx - rad) * (rx - rad) + (ry - rad) * (ry - rad) > rad * rad: + in_win = 0 + elif rx >= ww - rad and ry < rad: + if (rx - (ww - rad - 1)) * (rx - (ww - rad - 1)) + (ry - rad) * (ry - rad) > rad * rad: + in_win = 0 + elif rx < rad and ry >= wh - rad: + if (rx - rad) * (rx - rad) + (ry - (wh - rad - 1)) * (ry - (wh - rad - 1)) > rad * rad: + in_win = 0 + elif rx >= ww - rad and ry >= wh - rad: + if (rx - (ww - rad - 1)) * (rx - (ww - rad - 1)) + (ry - (wh - rad - 1)) * (ry - (wh - rad - 1)) > rad * rad: + in_win = 0 + if in_win: + continue + cpx: t.CInt = px + if cpx < wx: cpx = wx + elif cpx > wx + ww - 1: cpx = wx + ww - 1 + cpy: t.CInt = py + if cpy < wy: cpy = wy + elif cpy > wy + wh - 1: cpy = wy + wh - 1 + if is_rounded and rad > 0: + if cpx < wx + rad and cpy < wy + rad: + acx: t.CInt = wx + rad + acy: t.CInt = wy + rad + adx: t.CInt = cpx - acx + ady: t.CInt = cpy - acy + ad_sq: t.CInt = adx * adx + ady * ady + if ad_sq > rad * rad and ad_sq > 0: + aax: t.CInt = adx + if aax < 0: aax = 0 - aax + aay: t.CInt = ady + if aay < 0: aay = 0 - aay + am: t.CInt = aax + if aay > am: am = aay + an: t.CInt = aax + if aay < an: an = aay + ad: t.CInt = am + (an * 3) / 8 + if ad > 0: + cpx = acx + (adx * rad) / ad + cpy = acy + (ady * rad) / ad + elif cpx >= wx + ww - rad and cpy < wy + rad: + acx = wx + ww - rad - 1 + acy = wy + rad + adx = cpx - acx + ady = cpy - acy + ad_sq = adx * adx + ady * ady + if ad_sq > rad * rad and ad_sq > 0: + aax = adx + if aax < 0: aax = 0 - aax + aay = ady + if aay < 0: aay = 0 - aay + am = aax + if aay > am: am = aay + an = aax + if aay < an: an = aay + ad = am + (an * 3) / 8 + if ad > 0: + cpx = acx + (adx * rad) / ad + cpy = acy + (ady * rad) / ad + elif cpx < wx + rad and cpy >= wy + wh - rad: + acx = wx + rad + acy = wy + wh - rad - 1 + adx = cpx - acx + ady = cpy - acy + ad_sq = adx * adx + ady * ady + if ad_sq > rad * rad and ad_sq > 0: + aax = adx + if aax < 0: aax = 0 - aax + aay = ady + if aay < 0: aay = 0 - aay + am = aax + if aay > am: am = aay + an = aax + if aay < an: an = aay + ad = am + (an * 3) / 8 + if ad > 0: + cpx = acx + (adx * rad) / ad + cpy = acy + (ady * rad) / ad + elif cpx >= wx + ww - rad and cpy >= wy + wh - rad: + acx = wx + ww - rad - 1 + acy = wy + wh - rad - 1 + adx = cpx - acx + ady = cpy - acy + ad_sq = adx * adx + ady * ady + if ad_sq > rad * rad and ad_sq > 0: + aax = adx + if aax < 0: aax = 0 - aax + aay = ady + if aay < 0: aay = 0 - aay + am = aax + if aay > am: am = aay + an = aax + if aay < an: an = aay + ad = am + (an * 3) / 8 + if ad > 0: + cpx = acx + (adx * rad) / ad + cpy = acy + (ady * rad) / ad + dx: t.CInt = px - cpx + dy: t.CInt = py - cpy + adx_d: t.CInt = dx + if adx_d < 0: adx_d = 0 - adx_d + ady_d: t.CInt = dy + if ady_d < 0: ady_d = 0 - ady_d + dist: t.CInt = adx_d + if ady_d > dist: dist = ady_d + dist = dist + ((adx_d + ady_d - dist) * 3) / 8 + me_x: t.CInt = sl + if dx > 0: me_x = sr + me_y: t.CInt = st + if dy > 0: me_y = sb + me: t.CInt = me_x + if adx_d + ady_d > 0: + me = (me_x * adx_d + me_y * ady_d) / (adx_d + ady_d) + if me > 0 and dist > 0 and dist < me: + t_val: t.CInt = (256 * dist) / me + t_inv: t.CInt = 256 - t_val + a: t.CInt = (sa * t_inv * t_inv) / 65536 + if a > 0: + back[py * bw + px] = _alpha_blend(back[py * bw + px], shadow_col, a) + +def _cursor_save_bg(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt): + global _cursor_saved + cw: t.CInt = CURSOR_W + ch: t.CInt = CURSOR_H + for dy in range(ch): + for dx in range(cw): + sx: t.CInt = cx + dx + sy: t.CInt = cy + dy + if sx >= 0 and sx < w and sy >= 0 and sy < h: + _cursor_save_buf[dy * cw + dx] = back[sy * w + sx] + else: + _cursor_save_buf[dy * cw + dx] = 0 + _cursor_saved = 1 + +def _cursor_restore_bg(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt): + global _cursor_saved + if _cursor_saved == 0: return + cw: t.CInt = CURSOR_W + ch: t.CInt = CURSOR_H + for dy in range(ch): + for dx in range(cw): + sx: t.CInt = cx + dx + sy: t.CInt = cy + dy + if sx >= 0 and sx < w and sy >= 0 and sy < h: + back[sy * w + sx] = _cursor_save_buf[dy * cw + dx] + _cursor_saved = 0 + +def _cursor_draw_bitmap(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt, bmp: t.CArray[t.CUInt8T, 256]): + cw: t.CInt = CURSOR_W + ch: t.CInt = CURSOR_H + white: t.CUInt32T = gfx.COLOR_RGB(255, 255, 255) + black: t.CUInt32T = gfx.COLOR_RGB(0, 0, 0) + for dy in range(ch): + for dx in range(cw): + pixel: t.CUInt8T = bmp[dy * cw + dx] + if pixel != 0: + px: t.CInt = cx + dx + py: t.CInt = cy + dy + if px >= 0 and px < w and py >= 0 and py < h: + if pixel == 1: + back[py * w + px] = black + elif pixel == 2: + back[py * w + px] = white + +def _cursor_draw_xor(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt, bmp: t.CArray[t.CUInt8T, 256]): + cw: t.CInt = CURSOR_W + ch: t.CInt = CURSOR_H + xor_val: t.CUInt32T = t.CUInt32T(0x00FFFFFF) + for dy in range(ch): + for dx in range(cw): + pixel: t.CUInt8T = bmp[dy * cw + dx] + if pixel != 0: + px: t.CInt = cx + dx + py: t.CInt = cy + dy + if px >= 0 and px < w and py >= 0 and py < h: + back[py * w + px] = back[py * w + px] ^ xor_val + +def _draw_cursor(back: UINT32PTR): + cx: t.CInt = _cursor_x + cy: t.CInt = _cursor_y + w: t.CInt = _screen_w + h: t.CInt = _screen_h + cur_type: t.CInt = CURSOR_DEFAULT + if _last_hover_win >= 0 and _last_hover_win < _win_count: + cur_type = _wins[_last_hover_win].cursor_type + if cur_type == CURSOR_IBEAM: + _cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_IBEAM) + elif cur_type == CURSOR_RESIZE_EW: + _cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_RESIZE_EW) + elif cur_type == CURSOR_RESIZE_NS: + _cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_RESIZE_NS) + elif cur_type == CURSOR_RESIZE_NWSE: + _cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_RESIZE_NWSE) + else: + _cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_ARROW) + +def _erase_cursor(back: UINT32PTR): + global _cursor_saved + if _cursor_saved == 0: return + _cursor_restore_bg(back, _prev_cursor_x, _prev_cursor_y, _screen_w, _screen_h) + +def desktop_thread(arg: t.CVoid | t.CPtr) -> t.CInt: + global _last_clock_sec, _mouse_moved + global _prev_cursor_x, _prev_cursor_y + global _perf_t_mouse, _perf_t_kbd, _perf_t_render, _perf_t_flush, _perf_t_total, _perf_frames, _perf_start_ticks + global _perf_t_bg, _perf_t_win, _perf_t_cur, _perf_t_fb + serial.puts("[desktop] thread started\n") + _mark_all_dirty() + _dt_hb_sec: t.CUInt32T = 0 + tb_h_dt: t.CInt = TASKBAR_HEIGHT + _perf_buf: t.CArray[t.CChar, 256] + _perf_start_ticks = timer.timer_get_ticks() + _kbd_poll_ticks: t.CUInt64T = 0 + _mouse_poll_ticks: t.CUInt64T = 0 + while True: + _loop_t0: t.CUInt64T = timer.timer_get_ticks() + # Poll mouse at most every 4ms to avoid excessive I/O port overhead + _now_ticks: t.CUInt64T = timer.timer_get_ticks() + if _now_ticks - _mouse_poll_ticks >= 4: + _mouse_poll_ticks = _now_ticks + _mt0: t.CUInt64T = timer.timer_get_ticks() + mouse_active: t.CInt = _update_mouse() + _mt1: t.CUInt64T = timer.timer_get_ticks() + _perf_t_mouse += (_mt1 - _mt0) + # Poll keyboard at most every 4ms to avoid excessive I/O port overhead + if _now_ticks - _kbd_poll_ticks >= 4: + _kbd_poll_ticks = _now_ticks + _kt0: t.CUInt64T = timer.timer_get_ticks() + _update_keyboard() + _kt1: t.CUInt64T = timer.timer_get_ticks() + _perf_t_kbd += (_kt1 - _kt0) + _heartbeat_check() + sched.Scheduler.try_reschedule() + _dt_cur_sec: t.CUInt32T = timer.timer_get_seconds() + if _dt_cur_sec != _dt_hb_sec: + _dt_hb_sec = _dt_cur_sec + cur_sec: t.CUInt32T = timer.timer_get_seconds() + if cur_sec != _last_clock_sec: + _last_clock_sec = cur_sec + _mark_rows_dirty(_screen_h - tb_h_dt, tb_h_dt) + if _mouse_moved: + _mouse_moved = 0 + sz: t.CInt = CURSOR_H + _mark_rows_dirty(_prev_cursor_y, sz) + _mark_rows_dirty(_cursor_y, sz) + _prev_cursor_x = _cursor_x + _prev_cursor_y = _cursor_y + top_win: t.CInt = _hit_test_window(_cursor_x, _cursor_y) + for ai in range(_win_count): + if _wins[ai].active and not _wins[ai].minimized: + th: t.CInt = _get_title_height(_wins[ai].style) + btn_w: t.CInt = CLOSE_BTN_SIZE + btn_h: t.CInt = CLOSE_BTN_SIZE + ew_hov: t.CInt = _eff_w(c.Addr(_wins[ai])) + btn_x: t.CInt = _wins[ai].x + ew_hov - btn_w - 4 + btn_y: t.CInt = _wins[ai].y + (th - btn_h) // 2 + if _wins[ai].style == STYLE_WIN7: + btn_w = CLOSE_BTN_W7_W + btn_h = CLOSE_BTN_W7_H + btn_x = _wins[ai].x + ew_hov - btn_w - 5 + btn_y = _wins[ai].y + (th - btn_h) // 2 + elif _wins[ai].style == STYLE_WINXP: + btn_w = CLOSE_BTN_XP_SZ + btn_h = CLOSE_BTN_XP_SZ + btn_x = _wins[ai].x + ew_hov - btn_w - 5 + btn_y = _wins[ai].y + (th - btn_h) // 2 + elif _wins[ai].style == STYLE_WIN10 or _wins[ai].style == STYLE_WIN11: + btn_w = CLOSE_BTN_W_WIN + btn_h = th - 2 + btn_x = _wins[ai].x + ew_hov - btn_w + btn_y = _wins[ai].y + 1 + max_bx3: t.CInt = btn_x - btn_w + if _wins[ai].style == STYLE_WIN7 or _wins[ai].style == STYLE_WINXP: + max_bx3 = btn_x - btn_w - 1 + min_bx3: t.CInt = max_bx3 - btn_w + if _wins[ai].style == STYLE_WIN7 or _wins[ai].style == STYLE_WINXP: + min_bx3 = max_bx3 - btn_w - 1 + is_over_close: t.CInt = 0 + is_over_max: t.CInt = 0 + is_over_min: t.CInt = 0 + if ai == top_win: + if _cursor_x >= btn_x and _cursor_x < btn_x + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h: + is_over_close = 1 + elif _cursor_x >= max_bx3 and _cursor_x < max_bx3 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h: + is_over_max = 1 + elif _cursor_x >= min_bx3 and _cursor_x < min_bx3 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h: + is_over_min = 1 + if is_over_close and _wins[ai].close_hover < 255: + _old_hov: t.CInt = _wins[ai].close_hover + _wins[ai].close_hover += 60 + if _wins[ai].close_hover > 255: + _wins[ai].close_hover = 255 + if _wins[ai].close_hover != _old_hov: + _mark_rows_dirty(btn_y, btn_h) + elif not is_over_close and _wins[ai].close_hover > 0: + _old_hov2: t.CInt = _wins[ai].close_hover + _wins[ai].close_hover -= 45 + if _wins[ai].close_hover < 0: + _wins[ai].close_hover = 0 + if _wins[ai].close_hover != _old_hov2: + _mark_rows_dirty(btn_y, btn_h) + if is_over_max and _wins[ai].max_hover < 255: + _old_mh: t.CInt = _wins[ai].max_hover + _wins[ai].max_hover += 60 + if _wins[ai].max_hover > 255: + _wins[ai].max_hover = 255 + if _wins[ai].max_hover != _old_mh: + _mark_rows_dirty(btn_y, btn_h) + elif not is_over_max and _wins[ai].max_hover > 0: + _old_mh2: t.CInt = _wins[ai].max_hover + _wins[ai].max_hover -= 45 + if _wins[ai].max_hover < 0: + _wins[ai].max_hover = 0 + if _wins[ai].max_hover != _old_mh2: + _mark_rows_dirty(btn_y, btn_h) + if is_over_min and _wins[ai].min_hover < 255: + _old_nh: t.CInt = _wins[ai].min_hover + _wins[ai].min_hover += 60 + if _wins[ai].min_hover > 255: + _wins[ai].min_hover = 255 + if _wins[ai].min_hover != _old_nh: + _mark_rows_dirty(btn_y, btn_h) + elif not is_over_min and _wins[ai].min_hover > 0: + _old_nh2: t.CInt = _wins[ai].min_hover + _wins[ai].min_hover -= 45 + if _wins[ai].min_hover < 0: + _wins[ai].min_hover = 0 + if _wins[ai].min_hover != _old_nh2: + _mark_rows_dirty(btn_y, btn_h) + if _has_dirty_rows(): + _rt0: t.CUInt64T = timer.timer_get_ticks() + _render() + _rt1: t.CUInt64T = timer.timer_get_ticks() + _perf_t_render += (_rt1 - _rt0) + _ft0: t.CUInt64T = timer.timer_get_ticks() + _clear_dirty_flags() + _ft1: t.CUInt64T = timer.timer_get_ticks() + _perf_t_flush += (_ft1 - _ft0) + _perf_frames += 1 + _loop_t1: t.CUInt64T = timer.timer_get_ticks() + _perf_t_total += (_loop_t1 - _loop_t0) + _now_ticks: t.CUInt64T = timer.timer_get_ticks() + _elapsed_ms: t.CUInt64T = _now_ticks - _perf_start_ticks + if _elapsed_ms >= 1000: + _fps_val: t.CUInt32T = 0 + if _elapsed_ms > 0: + _fps_val = t.CUInt32T(t.CUInt64T(_perf_frames) * 1000 / _elapsed_ms) + string.memset(c.Addr(_perf_buf), 0, 256) + viperlib.snprintf(c.Addr(_perf_buf), 256, "[perf] fps=%u wall=%lu work=%lu mouse=%lu kbd=%lu render=%lu(bg=%lu win=%lu cur=%lu fb=%lu) dirty=%d vsync=%d\n", _fps_val, t.CUInt64T(_elapsed_ms), t.CUInt64T(_perf_t_total), t.CUInt64T(_perf_t_mouse), t.CUInt64T(_perf_t_kbd), t.CUInt64T(_perf_t_render), t.CUInt64T(_perf_t_bg), t.CUInt64T(_perf_t_win), t.CUInt64T(_perf_t_cur), t.CUInt64T(_perf_t_fb), _dirty_count, _vblank_ok) + serial.puts(c.Addr(_perf_buf)) + _perf_t_mouse = 0 + _perf_t_kbd = 0 + _perf_t_render = 0 + _perf_t_flush = 0 + _perf_t_total = 0 + _perf_t_bg = 0 + _perf_t_win = 0 + _perf_t_cur = 0 + _perf_t_fb = 0 + _perf_frames = 0 + _perf_start_ticks = _now_ticks + sched.Scheduler.try_reschedule() + return 0 + +def start(fb_ptr: t.CVoid | t.CPtr, w: t.CInt, h: t.CInt, pitch: t.CInt = 0) -> sched.Thread | t.CPtr: + global _fb, _screen_w, _screen_h, _fb_pitch, _cursor_x, _cursor_y + global _back_fb, _row_buf + global _dirty_rows, _prev_cursor_x, _prev_cursor_y + _fb = fb_ptr + _screen_w = w + _screen_h = h + if pitch > 0: + _fb_pitch = pitch + else: + _fb_pitch = w * 4 + _cursor_x = w // 2 + _cursor_y = h // 2 + _prev_cursor_x = _cursor_x + _prev_cursor_y = _cursor_y + + _dirty_rows = mm.malloc(h) + if _dirty_rows: + string.memset(_dirty_rows, 0, h) + else: + serial.puts("[desktop] dirty_rows malloc FAILED\n") + return None + + _back_fb = mm.malloc(w * h * 4) + if _back_fb: + string.memset(_back_fb, 0, w * h * 4) + else: + serial.puts("[desktop] back_fb malloc FAILED\n") + return None + + _row_buf = mm.malloc(w * 4) + if _row_buf: + string.memset(_row_buf, 0, w * 4) + else: + serial.puts("[desktop] row_buf malloc FAILED!\n") + + _bg_row_buf = mm.malloc(w * 4) + if _bg_row_buf: + desktop_init_col: t.CUInt32T = gfx.COLOR_RGB(25, 25, 35) + for ri in range(w): + _bg_row_buf[ri] = desktop_init_col + else: + serial.puts("[desktop] bg_row_buf malloc FAILED!\n") + + _mark_all_dirty() + p: process.Process | t.CPtr = process.ProcessManager.create_process("desktop", None) + desktop_pid: t.CInt = 0 + if p: + desktop_pid = p.pid + th: sched.Thread | t.CPtr = sched.Scheduler.create_thread(desktop_thread, None, desktop_pid) + if p and th: + p.addThread(th.tid) + return th + +def _get_top_window() -> t.CInt: + best: t.CInt = -1 + best_z: t.CInt = -1 + for ai in range(_win_count): + if _wins[ai].active and not _wins[ai].minimized: + z: t.CInt = _wins[ai].z_order + if z > best_z: + best_z = z + best = ai + return best + +def _draw_min_btn(back: UINT32PTR, bw: t.CInt, bh: t.CInt, bx: t.CInt, by: t.CInt, btn_w: t.CInt, btn_h: t.CInt, hover_alpha: t.CInt, style: t.CInt): + if hover_alpha > 0: + hover_col: t.CUInt32T = gfx.COLOR_RGB(80, 80, 80) + if style == STYLE_WIN7: + hover_col = gfx.COLOR_RGB(200, 220, 240) + elif style == STYLE_WINXP: + hover_col = gfx.COLOR_RGB(180, 210, 240) + for py in range(by, by + btn_h): + if py >= 0 and py < bh: + for px in range(bx, bx + btn_w): + if px >= 0 and px < bw: + bg_pixel: t.CUInt32T = back[py * bw + px] + back[py * bw + px] = _alpha_blend(bg_pixel, hover_col, hover_alpha) + line_col: t.CUInt32T = gfx.COLOR_RGB(180, 180, 180) + if hover_alpha > 0: + line_col = gfx.COLOR_RGB(255, 255, 255) + cx: t.CInt = bx + btn_w // 2 + cy: t.CInt = by + btn_h // 2 + lw: t.CInt = 5 + if btn_w >= 30: + lw = 8 + ly: t.CInt = cy + 3 + if ly >= 0 and ly < bh: + for lx in range(cx - lw, cx + lw + 1): + if lx >= 0 and lx < bw: + back[ly * bw + lx] = line_col + +def _draw_max_btn(back: UINT32PTR, bw: t.CInt, bh: t.CInt, bx: t.CInt, by: t.CInt, btn_w: t.CInt, btn_h: t.CInt, hover_alpha: t.CInt, style: t.CInt, is_maximized: t.CInt): + if hover_alpha > 0: + hover_col2: t.CUInt32T = gfx.COLOR_RGB(80, 80, 80) + if style == STYLE_WIN7: + hover_col2 = gfx.COLOR_RGB(200, 220, 240) + elif style == STYLE_WINXP: + hover_col2 = gfx.COLOR_RGB(180, 210, 240) + for py2 in range(by, by + btn_h): + if py2 >= 0 and py2 < bh: + for px2 in range(bx, bx + btn_w): + if px2 >= 0 and px2 < bw: + bg_pixel2: t.CUInt32T = back[py2 * bw + px2] + back[py2 * bw + px2] = _alpha_blend(bg_pixel2, hover_col2, hover_alpha) + rect_col: t.CUInt32T = gfx.COLOR_RGB(180, 180, 180) + if hover_alpha > 0: + rect_col = gfx.COLOR_RGB(255, 255, 255) + cx2: t.CInt = bx + btn_w // 2 + cy2: t.CInt = by + btn_h // 2 + sz: t.CInt = 4 + if btn_w >= 30: + sz = 6 + if is_maximized: + off: t.CInt = 2 + for ry1 in range(cy2 - sz + off, cy2 + sz + off + 1): + if ry1 >= 0 and ry1 < bh: + for rx1 in range(cx2 - sz + off, cx2 + sz + off + 1): + if rx1 >= 0 and rx1 < bw: + is_top: t.CInt = 0 + if ry1 == cy2 - sz + off or ry1 == cy2 - sz + off + 1: + is_top = 1 + is_border: t.CInt = 0 + if ry1 == cy2 - sz + off or ry1 == cy2 + sz + off or rx1 == cx2 - sz + off or rx1 == cx2 + sz + off: + is_border = 1 + if is_top or is_border: + back[ry1 * bw + rx1] = rect_col + for ry2 in range(cy2 - sz, cy2 + sz + 1): + if ry2 >= 0 and ry2 < bh: + for rx2 in range(cx2 - sz, cx2 + sz + 1): + if rx2 >= 0 and rx2 < bw: + if ry2 == cy2 - sz or ry2 == cy2 - sz + 1 or rx2 == cx2 - sz or rx2 == cx2 + sz: + back[ry2 * bw + rx2] = rect_col + else: + for ry3 in range(cy2 - sz, cy2 + sz + 1): + if ry3 >= 0 and ry3 < bh: + for rx3 in range(cx2 - sz, cx2 + sz + 1): + if rx3 >= 0 and rx3 < bw: + if ry3 == cy2 - sz or ry3 == cy2 + sz or rx3 == cx2 - sz or rx3 == cx2 + sz: + back[ry3 * bw + rx3] = rect_col + top_y: t.CInt = cy2 - sz + if top_y >= 0 and top_y < bh: + for tx in range(cx2 - sz, cx2 + sz + 1): + if tx >= 0 and tx < bw: + back[top_y * bw + tx] = rect_col + if top_y + 1 < bh: + for tx2 in range(cx2 - sz, cx2 + sz + 1): + if tx2 >= 0 and tx2 < bw: + back[(top_y + 1) * bw + tx2] = rect_col + +def _draw_close_btn(back: UINT32PTR, bw: t.CInt, bh: t.CInt, bx: t.CInt, by: t.CInt, btn_w: t.CInt, btn_h: t.CInt, hover_alpha: t.CInt, style: t.CInt, clip_wx: t.CInt, clip_wy: t.CInt, clip_ww: t.CInt, clip_r: t.CInt): + x_col: t.CUInt32T = gfx.COLOR_RGB(200, 200, 200) + if style == STYLE_CLASSIC: + bg: t.CUInt32T = gfx.COLOR_RGB(40, 40, 70) + if hover_alpha > 0: + red: t.CUInt32T = gfx.COLOR_RGB(200, 50, 50) + bg = _alpha_blend(bg, red, hover_alpha) + _fill_rect(back, bw, bh, bx, by, btn_w, btn_h, bg) + if hover_alpha > 128: + x_col = gfx.COLOR_RGB(255, 255, 255) + elif style == STYLE_WIN7: + br: t.CInt = CLOSE_BTN_W7_R + bg_r: t.CInt = 200 + bg_g: t.CInt = 50 + bg_b: t.CInt = 50 + if hover_alpha > 0: + bg_r = 200 + ((232 - 200) * hover_alpha) / 255 + bg_g = 50 + ((17 - 50) * hover_alpha) / 255 + bg_b = 50 + ((35 - 50) * hover_alpha) / 255 + bg_col7: t.CUInt32T = gfx.COLOR_RGB(bg_r, bg_g, bg_b) + for py2 in range(by, by + btn_h): + if py2 >= 0 and py2 < bh: + for px2 in range(bx, bx + btn_w): + if px2 >= 0 and px2 < bw: + lrx: t.CInt = px2 - bx + lry: t.CInt = py2 - by + if lry < br: + if lrx < br: + if (lrx - br) * (lrx - br) + (lry - br) * (lry - br) > br * br: + continue + elif lrx >= btn_w - br: + if (lrx - (btn_w - br - 1)) * (lrx - (btn_w - br - 1)) + (lry - br) * (lry - br) > br * br: + continue + elif lry >= btn_h - br: + if lrx < br: + if (lrx - br) * (lrx - br) + (lry - (btn_h - br - 1)) * (lry - (btn_h - br - 1)) > br * br: + continue + elif lrx >= btn_w - br: + if (lrx - (btn_w - br - 1)) * (lrx - (btn_w - br - 1)) + (lry - (btn_h - br - 1)) * (lry - (btn_h - br - 1)) > br * br: + continue + if clip_r > 0: + crx: t.CInt = px2 - clip_wx + cry: t.CInt = py2 - clip_wy + if cry < clip_r and crx >= clip_ww - clip_r: + cdx: t.CInt = crx - (clip_ww - clip_r - 1) + cdy: t.CInt = cry - clip_r + if cdx * cdx + cdy * cdy > clip_r * clip_r: + continue + back[py2 * bw + px2] = bg_col7 + x_col = gfx.COLOR_RGB(255, 255, 255) + if hover_alpha > 0: + x_col = _alpha_blend(gfx.COLOR_RGB(255, 255, 255), gfx.COLOR_RGB(255, 240, 240), hover_alpha) + elif style == STYLE_WINXP: + br_xp: t.CInt = 3 + bg_r_xp: t.CInt = 200 + bg_g_xp: t.CInt = 50 + bg_b_xp: t.CInt = 50 + if hover_alpha > 0: + bg_r_xp = 200 + ((232 - 200) * hover_alpha) / 255 + bg_g_xp = 50 + ((17 - 50) * hover_alpha) / 255 + bg_b_xp = 50 + ((35 - 50) * hover_alpha) / 255 + bg_col_xp: t.CUInt32T = gfx.COLOR_RGB(bg_r_xp, bg_g_xp, bg_b_xp) + for py_xp in range(by, by + btn_h): + if py_xp >= 0 and py_xp < bh: + for px_xp in range(bx, bx + btn_w): + if px_xp >= 0 and px_xp < bw: + lrx_xp: t.CInt = px_xp - bx + lry_xp: t.CInt = py_xp - by + if lry_xp < br_xp: + if lrx_xp < br_xp: + if (lrx_xp - br_xp) * (lrx_xp - br_xp) + (lry_xp - br_xp) * (lry_xp - br_xp) > br_xp * br_xp: + continue + elif lrx_xp >= btn_w - br_xp: + if (lrx_xp - (btn_w - br_xp - 1)) * (lrx_xp - (btn_w - br_xp - 1)) + (lry_xp - br_xp) * (lry_xp - br_xp) > br_xp * br_xp: + continue + elif lry_xp >= btn_h - br_xp: + if lrx_xp < br_xp: + if (lrx_xp - br_xp) * (lrx_xp - br_xp) + (lry_xp - (btn_h - br_xp - 1)) * (lry_xp - (btn_h - br_xp - 1)) > br_xp * br_xp: + continue + elif lrx_xp >= btn_w - br_xp: + if (lrx_xp - (btn_w - br_xp - 1)) * (lrx_xp - (btn_w - br_xp - 1)) + (lry_xp - (btn_h - br_xp - 1)) * (lry_xp - (btn_h - br_xp - 1)) > br_xp * br_xp: + continue + back[py_xp * bw + px_xp] = bg_col_xp + x_col = gfx.COLOR_RGB(255, 255, 255) + if hover_alpha > 0: + x_col = _alpha_blend(gfx.COLOR_RGB(255, 255, 255), gfx.COLOR_RGB(255, 240, 240), hover_alpha) + else: + if hover_alpha > 0: + red = gfx.COLOR_RGB(232, 17, 35) + for py3 in range(by, by + btn_h): + if py3 >= 0 and py3 < bh: + for px3 in range(bx, bx + btn_w): + if px3 >= 0 and px3 < bw: + if clip_r > 0: + crx3: t.CInt = px3 - clip_wx + cry3: t.CInt = py3 - clip_wy + if cry3 < clip_r and crx3 >= clip_ww - clip_r: + cdx3: t.CInt = crx3 - (clip_ww - clip_r - 1) + cdy3: t.CInt = cry3 - clip_r + if cdx3 * cdx3 + cdy3 * cdy3 > clip_r * clip_r: + continue + bg_pixel: t.CUInt32T = back[py3 * bw + px3] + back[py3 * bw + px3] = _alpha_blend(bg_pixel, red, hover_alpha) + x_col = gfx.COLOR_RGB(150, 150, 150) + if hover_alpha > 0: + x_col = _alpha_blend(gfx.COLOR_RGB(150, 150, 150), gfx.COLOR_RGB(255, 255, 255), hover_alpha) + cx: t.CInt = bx + btn_w // 2 + cy: t.CInt = by + btn_h // 2 + d: t.CInt = 3 + if btn_w >= 30: + d = 4 + i: t.CInt = -d + while i <= d: + px1: t.CInt = cx + i + py1: t.CInt = cy + i + px2b: t.CInt = cx + i + py2b: t.CInt = cy - i + if px1 >= 0 and px1 < bw and py1 >= 0 and py1 < bh: + if clip_r > 0: + crx1: t.CInt = px1 - clip_wx + cry1: t.CInt = py1 - clip_wy + if cry1 < clip_r and crx1 >= clip_ww - clip_r: + cdx1: t.CInt = crx1 - (clip_ww - clip_r - 1) + cdy1: t.CInt = cry1 - clip_r + if cdx1 * cdx1 + cdy1 * cdy1 > clip_r * clip_r: + i += 1 + continue + back[py1 * bw + px1] = x_col + if px2b >= 0 and px2b < bw and py2b >= 0 and py2b < bh: + if clip_r > 0: + crx2: t.CInt = px2b - clip_wx + cry2: t.CInt = py2b - clip_wy + if cry2 < clip_r and crx2 >= clip_ww - clip_r: + cdx2: t.CInt = crx2 - (clip_ww - clip_r - 1) + cdy2: t.CInt = cry2 - clip_r + if cdx2 * cdx2 + cdy2 * cdy2 > clip_r * clip_r: + i += 1 + continue + back[py2b * bw + px2b] = x_col + i += 1 + +def _draw_app_window(back: UINT32PTR, win: AppWindow | t.CPtr): + w: t.CInt = _screen_w + h: t.CInt = _screen_h + wx: t.CInt = win.x + wy: t.CInt = win.y + ww: t.CInt = _eff_w(win) + wh: t.CInt = win.h + ch: t.CInt = _eff_h(win) + style: t.CInt = win.style + th: t.CInt = _get_title_height(style) + + if wy + wh <= _dirty_y_min or wy > _dirty_y_max: + return + + top_idx: t.CInt = _cached_top_win + is_active: t.CInt = 0 + if top_idx >= 0: + top_z: t.CInt = _wins[top_idx].z_order + if win.z_order == top_z: + is_active = 1 + + title_col: t.CUInt32T = gfx.COLOR_RGB(50, 50, 90) + border_col: t.CUInt32T = gfx.COLOR_RGB(100, 100, 180) + content_col: t.CUInt32T = gfx.COLOR_RGB(30, 30, 50) + outline_col: t.CUInt32T = gfx.COLOR_RGB(100, 100, 180) + title_alpha: t.CInt = 255 + has_separator: t.CInt = 1 + if style == STYLE_WIN10: + has_separator = 0 + if is_active: + title_col = gfx.COLOR_RGB(0, 0, 0) + border_col = gfx.COLOR_RGB(0, 0, 0) + content_col = gfx.COLOR_RGB(32, 32, 32) + outline_col = gfx.COLOR_RGB(80, 80, 80) + else: + title_col = gfx.COLOR_RGB(32, 32, 32) + border_col = gfx.COLOR_RGB(32, 32, 32) + content_col = gfx.COLOR_RGB(24, 24, 24) + outline_col = gfx.COLOR_RGB(60, 60, 60) + elif style == STYLE_WIN11: + has_separator = 0 + if is_active: + title_col = gfx.COLOR_RGB(32, 32, 32) + border_col = gfx.COLOR_RGB(32, 32, 32) + content_col = gfx.COLOR_RGB(32, 32, 32) + outline_col = gfx.COLOR_RGB(70, 70, 70) + else: + title_col = gfx.COLOR_RGB(40, 40, 40) + border_col = gfx.COLOR_RGB(40, 40, 40) + content_col = gfx.COLOR_RGB(40, 40, 40) + outline_col = gfx.COLOR_RGB(60, 60, 60) + elif style == STYLE_WIN7: + has_separator = 0 + if is_active: + title_col = gfx.COLOR_RGB(156, 181, 209) + border_col = gfx.COLOR_RGB(156, 181, 209) + content_col = gfx.COLOR_RGB(240, 240, 240) + outline_col = gfx.COLOR_RGB(100, 130, 170) + else: + title_col = gfx.COLOR_RGB(180, 195, 210) + border_col = gfx.COLOR_RGB(180, 195, 210) + content_col = gfx.COLOR_RGB(240, 240, 240) + outline_col = gfx.COLOR_RGB(140, 155, 175) + title_alpha = 102 + elif style == STYLE_WINXP: + has_separator = 0 + if is_active: + title_col = gfx.COLOR_RGB(107, 158, 212) + border_col = gfx.COLOR_RGB(107, 158, 212) + content_col = gfx.COLOR_RGB(235, 235, 235) + outline_col = gfx.COLOR_RGB(43, 87, 151) + else: + title_col = gfx.COLOR_RGB(140, 160, 195) + border_col = gfx.COLOR_RGB(140, 160, 195) + content_col = gfx.COLOR_RGB(235, 235, 235) + outline_col = gfx.COLOR_RGB(100, 115, 145) + else: + if is_active: + title_col = gfx.COLOR_RGB(70, 70, 130) + border_col = gfx.COLOR_RGB(130, 130, 220) + outline_col = gfx.COLOR_RGB(130, 130, 220) + + rounded: t.CInt = 0 + if style == STYLE_WIN11 or style == STYLE_WIN7 or style == STYLE_WINXP: + rounded = 1 + r: t.CInt = WIN_RADIUS + + _draw_window_shadow(back, w, h, wx, wy, ww, wh, rounded, r) + + if rounded: + for py in range(wy, wy + wh): + if py < 0 or py >= h: continue + if not _dirty_rows[py]: continue + ry: t.CInt = py - wy + row_col: t.CUInt32T = content_col + row_is_title: t.CInt = 0 + row_is_sep: t.CInt = 0 + if ry < th: + row_is_title = 1 + if style == STYLE_WIN7: + ratio: t.CInt = (ry * 100) / th + if ratio > 100: ratio = 100 + if is_active: + gr: t.CInt = 156 + ((186 - 156) * ratio) / 100 + gg: t.CInt = 181 + ((209 - 181) * ratio) / 100 + gb: t.CInt = 209 + ((233 - 209) * ratio) / 100 + else: + gr = 180 + ((200 - 180) * ratio) / 100 + gg = 195 + ((210 - 195) * ratio) / 100 + gb = 210 + ((225 - 210) * ratio) / 100 + row_col = gfx.COLOR_RGB(gr, gg, gb) + if ry == 0: + row_col = _alpha_blend(row_col, gfx.COLOR_RGB(220, 235, 255), 50) + elif ry == 1: + row_col = _alpha_blend(row_col, gfx.COLOR_RGB(210, 225, 250), 20) + if ry == th - 1: + row_col = _alpha_blend(row_col, gfx.COLOR_RGB(130, 160, 200), 25) + elif style == STYLE_WINXP: + ratio = (ry * 100) / th + if ratio > 100: ratio = 100 + if is_active: + gr = 107 + ((58 - 107) * ratio) / 100 + gg = 158 + ((124 - 158) * ratio) / 100 + gb = 212 + ((195 - 212) * ratio) / 100 + else: + gr = 150 + ((115 - 150) * ratio) / 100 + gg = 170 + ((135 - 170) * ratio) / 100 + gb = 205 + ((175 - 205) * ratio) / 100 + row_col = gfx.COLOR_RGB(gr, gg, gb) + if ry == 0: + row_col = _alpha_blend(row_col, gfx.COLOR_RGB(200, 225, 255), 50) + elif ry == 1: + row_col = _alpha_blend(row_col, gfx.COLOR_RGB(200, 225, 255), 25) + if ry == th - 1: + row_col = _alpha_blend(row_col, gfx.COLOR_RGB(100, 160, 230), 40) + else: + row_col = title_col + elif ry == th and has_separator: + row_is_sep = 1 + row_col = outline_col + in_corner_zone: t.CInt = 0 + if ry < r or ry >= wh - r: + in_corner_zone = 1 + if not in_corner_zone: + left_border: t.CInt = wx + right_border: t.CInt = wx + ww - 1 + if left_border >= 0 and left_border < w: + back[py * w + left_border] = outline_col + if right_border >= 0 and right_border < w: + back[py * w + right_border] = outline_col + fill_left: t.CInt = wx + 1 + fill_right: t.CInt = wx + ww - 1 + if fill_left < 0: fill_left = 0 + if fill_right > w: fill_right = w + fill_w: t.CInt = fill_right - fill_left + if fill_w > 0: + if row_is_title and (style == STYLE_WIN7 or style == STYLE_WINXP) and title_alpha < 255: + for fx in range(fill_left, fill_right): + bg_p: t.CUInt32T = back[py * w + fx] + back[py * w + fx] = _alpha_blend(bg_p, row_col, title_alpha) + else: + _fill_rect(back, w, h, fill_left, py, fill_w, 1, row_col) + else: + for px in range(wx, wx + ww): + if px < 0 or px >= w: continue + rx: t.CInt = px - wx + in_corner: t.CInt = 1 + on_border: t.CInt = 0 + if ry < r: + if rx < r: + in_corner = _is_in_corner(rx, ry, r, r, r) + elif rx >= ww - r: + in_corner = _is_in_corner(rx, ry, ww - r - 1, r, r) + elif ry >= wh - r: + if rx < r: + in_corner = _is_in_corner(rx, ry, r, wh - r - 1, r) + elif rx >= ww - r: + in_corner = _is_in_corner(rx, ry, ww - r - 1, wh - r - 1, r) + if not in_corner: + on_border = _is_on_border(rx, ry, ww, wh, r) + if not on_border: + continue + col: t.CUInt32T = row_col + is_border_pixel: t.CInt = 0 + if on_border or _is_on_border(rx, ry, ww, wh, r): + col = outline_col + is_border_pixel = 1 + ca: t.CInt = 255 + if ry < r: + if rx < r: + ca = _corner_alpha(rx, ry, r, r, r) + elif rx >= ww - r: + ca = _corner_alpha(rx, ry, ww - r - 1, r, r) + elif ry >= wh - r: + if rx < r: + ca = _corner_alpha(rx, ry, r, wh - r - 1, r) + elif rx >= ww - r: + ca = _corner_alpha(rx, ry, ww - r - 1, wh - r - 1, r) + if is_border_pixel and ca > 0 and ca < 255: + ca = 255 + if row_is_title and (style == STYLE_WIN7 or style == STYLE_WINXP) and ry >= 1 and rx >= 1 and rx < ww - 1 and ca == 255 and not is_border_pixel: + ca = title_alpha + if ca < 255: + bg_pixel: t.CUInt32T = back[py * w + px] + col = _alpha_blend(bg_pixel, col, ca) + back[py * w + px] = col + else: + _fill_rect(back, w, h, wx, wy, ww, th, title_col) + _fill_rect(back, w, h, wx, wy + th + 1, ww, ch, content_col) + if not has_separator: + _fill_rect(back, w, h, wx, wy + th, ww, 1, content_col) + if wx >= 0 and wx < w: + for py in range(wy, wy + wh): + if py >= 0 and py < h: + back[py * w + wx] = border_col + if wx + ww - 1 >= 0 and wx + ww - 1 < w: + for py in range(wy, wy + wh): + if py >= 0 and py < h: + back[py * w + wx + ww - 1] = border_col + _fill_rect(back, w, h, wx, wy, ww, 1, border_col) + _fill_rect(back, w, h, wx, wy + wh - 1, ww, 1, border_col) + if has_separator: + _fill_rect(back, w, h, wx, wy + th, ww, 1, border_col) + + win_fb: UINT32PTR = win.fb + if win_fb: + fb_stride: t.CInt = win.fb_w + fbh: t.CInt = win.fb_h + if fb_stride <= 0 or fbh <= 0: + win_fb = t.CVoid(0, t.CPtr) + if win_fb: + draw_rows: t.CInt = ch + if draw_rows > fbh: draw_rows = fbh + if draw_rows < 0: draw_rows = 0 + content_y: t.CInt = wy + th + 1 + fb_total: t.CInt = fb_stride * fbh + for row in range(draw_rows): + dst_y: t.CInt = content_y + row + if dst_y < 0 or dst_y >= h: continue + if not _dirty_rows[dst_y]: continue + ry: t.CInt = th + 1 + row + if rounded: + if ry >= r and ry < wh - r: + src_x: t.CInt = 1 + dst_x: t.CInt = wx + 1 + if dst_x < 0: + src_x = src_x - dst_x + dst_x = 0 + end_x: t.CInt = wx + ww - 1 + if end_x > w: end_x = w + actual_w: t.CInt = end_x - dst_x + if actual_w <= 0: continue + if src_x + actual_w > fb_stride: actual_w = fb_stride - src_x + if actual_w <= 0: continue + src_off: t.CInt = row * fb_stride + src_x + if src_off + actual_w > fb_total: continue + string.memcpy(t.CVoid(t.CUInt64T(back) + (dst_y * w + dst_x) * 4, t.CPtr), t.CVoid(t.CUInt64T(win_fb) + src_off * 4, t.CPtr), actual_w * 4) + else: + copy_w: t.CInt = fb_stride - 1 + if copy_w > ww - 1: copy_w = ww - 1 + for col_px in range(1, copy_w): + if ry < r: + if col_px < r: + if not _is_in_corner(col_px, ry, r, r, r): continue + elif col_px >= ww - r: + if not _is_in_corner(col_px, ry, ww - r - 1, r, r): continue + elif ry >= wh - r: + if col_px < r: + if not _is_in_corner(col_px, ry, r, wh - r - 1, r): continue + elif col_px >= ww - r: + if not _is_in_corner(col_px, ry, ww - r - 1, wh - r - 1, r): continue + if _is_on_border(col_px, ry, ww, wh, r): continue + dst_x2: t.CInt = wx + col_px + if dst_x2 < 0 or dst_x2 >= w: continue + if col_px >= fb_stride: continue + px_off: t.CInt = row * fb_stride + col_px + if px_off < 0 or px_off >= fb_total: continue + back[dst_y * w + dst_x2] = win_fb[px_off] + else: + src_x2: t.CInt = 1 + dst_x3: t.CInt = wx + 1 + if dst_x3 < 0: + src_x2 = src_x2 - dst_x3 + dst_x3 = 0 + end_x2: t.CInt = wx + ww - 1 + if end_x2 > w: end_x2 = w + actual_w2: t.CInt = end_x2 - dst_x3 + if actual_w2 <= 0: continue + if src_x2 + actual_w2 > fb_stride: actual_w2 = fb_stride - src_x2 + if actual_w2 <= 0: continue + src_off2: t.CInt = row * fb_stride + src_x2 + if src_off2 + actual_w2 > fb_total: continue + string.memcpy(t.CVoid(t.CUInt64T(back) + (dst_y * w + dst_x3) * 4, t.CPtr), t.CVoid(t.CUInt64T(win_fb) + src_off2 * 4, t.CPtr), actual_w2 * 4) + + btn_w: t.CInt = CLOSE_BTN_SIZE + btn_h: t.CInt = CLOSE_BTN_SIZE + btn_x: t.CInt = wx + ww - btn_w - 4 + btn_y: t.CInt = wy + (th - btn_h) // 2 + clip_r: t.CInt = 0 + if style == STYLE_WIN7: + btn_w = CLOSE_BTN_W7_W + btn_h = CLOSE_BTN_W7_H + btn_x = wx + ww - btn_w - 5 + btn_y = wy + (th - btn_h) // 2 + if rounded: + clip_r = r + elif style == STYLE_WINXP: + btn_w = CLOSE_BTN_XP_SZ + btn_h = CLOSE_BTN_XP_SZ + btn_x = wx + ww - btn_w - 5 + btn_y = wy + (th - btn_h) // 2 + elif style == STYLE_WIN10 or style == STYLE_WIN11: + btn_w = CLOSE_BTN_W_WIN + btn_h = th - 2 + btn_x = wx + ww - btn_w + btn_y = wy + 1 + if rounded: + clip_r = r + _draw_close_btn(back, w, h, btn_x, btn_y, btn_w, btn_h, win.close_hover, style, wx, wy, ww, clip_r) + max_bx: t.CInt = btn_x - btn_w + if style == STYLE_WIN7 or style == STYLE_WINXP: + max_bx = btn_x - btn_w - 1 + _draw_max_btn(back, w, h, max_bx, btn_y, btn_w, btn_h, win.max_hover, style, win.is_maximized) + min_bx: t.CInt = max_bx - btn_w + if style == STYLE_WIN7 or style == STYLE_WINXP: + min_bx = max_bx - btn_w - 1 + _draw_min_btn(back, w, h, min_bx, btn_y, btn_w, btn_h, win.min_hover, style) + + text_col: t.CUInt32T = 0xFFFFFFFF + if not is_active: + text_col = gfx.COLOR_RGB(170, 170, 170) + text_y: t.CInt = wy + (th - 16) // 2 + _draw_text(back, w, h, wx + 8, text_y, c.Addr(win.title[0]), text_col) + +def create(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | t.CChar | t.CPtr, style: t.CInt) -> t.CInt: + global _win_count, _next_z, _focused_win + if _win_count >= MAX_APP_WINDOWS: return -1 + idx: t.CInt = _win_count + _win_count += 1 + aw: AppWindow | t.CPtr = c.Addr(_wins[idx]) + aw.active = 1 + aw.x = x + aw.y = y + aw.w = w + aw.h = h + aw.content_h = h - _get_title_height(style) - 2 + _next_z += 1 + aw.z_order = _next_z + aw.style = style + aw.close_hover = 0 + aw.min_hover = 0 + aw.max_hover = 0 + aw.is_maximized = 0 + aw.orig_x = x + aw.orig_y = y + aw.orig_w = w + aw.orig_h = h + aw.pid = _current_app_pid + aw.cursor_type = 0 + aw.app_cursor_type = 0 + aw.fb_w = 0 + aw.fb_h = 0 + aw.old_fb = t.CVoid(0, t.CPtr) + aw.ping_pending = 0 + aw.no_resp = 0 + aw.no_resp_sec = 0 + aw.minimized = 0 + aw.resizable_w = 1 + aw.resizable_h = 1 + aw.resize_w = 0 + aw.resize_h = 0 + string.memset(c.Addr(aw.title), 0, 32) + string.memset(c.Addr(aw.orig_title), 0, 32) + for i in range(31): + ch: t.CChar = title[i] + if ch == 0: break + aw.title[i] = ch + aw.orig_title[i] = ch + aw.fb = mm.malloc(w * aw.content_h * 4) + aw.fb_w = w + aw.fb_h = aw.content_h + if aw.fb: + bg_col: t.CUInt32T = gfx.COLOR_RGB(25, 25, 35) + fb_size: t.CInt = w * aw.content_h + for fi in range(fb_size): + aw.fb[fi] = bg_col + _mark_rows_dirty(y, h) + _focused_win = idx + return idx + +def draw_text(win_id: t.CInt, x: t.CInt, y: t.CInt, text: t.CConst | t.CChar | t.CPtr, color: t.CUInt32T) -> t.CInt: + if win_id < 0 or win_id >= _win_count: return -1 + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return -1 + win_fb: UINT32PTR = aw.fb + if not win_fb: return -1 + fbw: t.CInt = aw.fb_w + fbh: t.CInt = aw.fb_h + _draw_text(win_fb, fbw, fbh, x, y, text, color) + _mark_rows_dirty(aw.y + _get_title_height(aw.style) + 1 + y, 16) + return 0 + +def set_app_pid(pid: t.CInt): + global _current_app_pid + _current_app_pid = pid + +def is_active(win_id: t.CInt) -> t.CInt: + if win_id < 0 or win_id >= _win_count: return 0 + return _wins[win_id].active + +def get_fb(win_id: t.CInt) -> UINT32PTR: + if win_id < 0 or win_id >= _win_count: return t.CVoid(0, t.CPtr) + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return t.CVoid(0, t.CPtr) + if aw.old_fb: + mm.free(aw.old_fb) + aw.old_fb = t.CVoid(0, t.CPtr) + if aw.resize_w != 0 and (aw.fb_w != aw.resize_w or aw.fb_h != aw.resize_h): + target_w: t.CInt = aw.resize_w + target_h: t.CInt = aw.resize_h + if target_w <= 0 or target_h <= 0: + aw.resize_w = 0 + aw.resize_h = 0 + return aw.fb + new_fb_sz_gf: t.CInt = target_w * target_h + new_fb_gf: UINT32PTR = mm.malloc(new_fb_sz_gf * 4) + if not new_fb_gf: + aw.resize_w = 0 + aw.resize_h = 0 + return aw.fb + old_fb_gf: UINT32PTR = aw.fb + old_fbw_gf: t.CInt = aw.fb_w + old_fbh_gf: t.CInt = aw.fb_h + aw.fb = new_fb_gf + aw.fb_w = target_w + aw.fb_h = target_h + aw.w = target_w + aw.content_h = target_h + aw.resize_w = 0 + aw.resize_h = 0 + bg_gf: t.CUInt32T = gfx.COLOR_RGB(30, 30, 50) + for fi_gf in range(new_fb_sz_gf): + aw.fb[fi_gf] = bg_gf + if old_fb_gf: + cph_gf: t.CInt = old_fbh_gf + if cph_gf > target_h: cph_gf = target_h + for ri_gf in range(cph_gf): + rb_gf: t.CInt = old_fbw_gf + if rb_gf > target_w: rb_gf = target_w + string.memcpy(t.CVoid(t.CUInt64T(aw.fb) + ri_gf * target_w * 4, t.CPtr), t.CVoid(t.CUInt64T(old_fb_gf) + ri_gf * old_fbw_gf * 4, t.CPtr), rb_gf * 4) + if aw.old_fb: + mm.free(aw.old_fb) + aw.old_fb = old_fb_gf + return aw.fb + +def get_size(win_id: t.CInt) -> t.CInt: + if win_id < 0 or win_id >= _win_count: return 0 + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return 0 + return (aw.fb_w << 16) | (aw.fb_h & 0xFFFF) + +_NO_RESP_SUFFIX: t.CArray[t.CChar, 14] = " - (No Resp)" + +def _update_no_resp_title(win_id: t.CInt, mark: t.CInt): + if win_id < 0 or win_id >= _win_count: return + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return + string.memset(c.Addr(aw.title), 0, 32) + if mark: + j: t.CInt = 0 + for ch in aw.orig_title: + if j >= 31: break + if ch == 0: break + aw.title[j] = ch + j += 1 + for sc in _NO_RESP_SUFFIX: + if j >= 13: break + if sc == 0: break + if j < 31: + aw.title[j] = sc + j += 1 + else: + j2: t.CInt = 0 + for ch2 in aw.orig_title: + if j2 >= 31: break + if ch2 == 0: break + aw.title[j2] = ch2 + j2 += 1 + _mark_rows_dirty(aw.y, _get_title_height(aw.style) + 2) + +def _heartbeat_check(): + global _hb_last_sec + cur_sec: t.CUInt32T = timer.timer_get_seconds() + if cur_sec - _hb_last_sec < HB_INTERVAL_SEC: return + _hb_last_sec = cur_sec + for i in range(_win_count): + aw: AppWindow | t.CPtr = c.Addr(_wins[i]) + if not aw.active: + continue + if aw.pid == 0: + continue + if aw.ping_pending: + if not aw.no_resp: + aw.no_resp = 1 + aw.no_resp_sec = cur_sec + _update_no_resp_title(i, 1) + else: + elapsed: t.CUInt32T = cur_sec - aw.no_resp_sec + if elapsed >= HB_NO_RESP_SEC: + aw.active = 0 + _mark_rows_dirty(aw.y, aw.h) + p_ref: process.Process | t.CPtr = process.ProcessManager.get_process(aw.pid) + if p_ref is not None: + p_ref.exit() + else: + aw.ping_pending = 1 + evt: InputEvent + string.memset(c.Addr(evt), 0, INPUT_EVENT_SIZE) + evt.type = EVENT_TYPE_PING + evt.win_id = i + _push_event(evt) + +def flush(win_id: t.CInt): + if win_id < 0 or win_id >= _win_count: return + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return + if aw.old_fb: + mm.free(aw.old_fb) + aw.old_fb = t.CVoid(0, t.CPtr) + if aw.ping_pending: + aw.ping_pending = 0 + if aw.no_resp: + aw.no_resp = 0 + aw.no_resp_sec = 0 + _update_no_resp_title(win_id, 0) + _mark_rows_dirty(aw.y + _get_title_height(aw.style) + 1, _eff_h(aw)) + +def set_title(win_id: t.CInt, title: t.CConst | t.CChar | t.CPtr) -> t.CInt: + if win_id < 0 or win_id >= _win_count: return -1 + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return -1 + string.memset(c.Addr(aw.title), 0, 32) + string.memset(c.Addr(aw.orig_title), 0, 32) + for i in range(31): + ch: t.CChar = title[i] + if ch == 0: break + aw.title[i] = ch + aw.orig_title[i] = ch + _mark_rows_dirty(aw.y, _get_title_height(aw.style) + 2) + return 0 + +def set_geometry(win_id: t.CInt, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt) -> t.CInt: + if win_id < 0 or win_id >= _win_count: return -1 + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return -1 + old_y: t.CInt = aw.y + old_h: t.CInt = aw.h + aw.x = x + aw.y = y + aw.resize_w = w + aw.resize_h = h - _get_title_height(aw.style) - 2 + aw.h = h + _mark_rows_dirty(old_y, old_h) + _mark_rows_dirty(y, h) + return 0 + +def set_resizable(win_id: t.CInt, rw: t.CInt, rh: t.CInt) -> t.CInt: + if win_id < 0 or win_id >= _win_count: return -1 + aw: AppWindow | t.CPtr = c.Addr(_wins[win_id]) + if not aw.active: return -1 + aw.resizable_w = rw + aw.resizable_h = rh + return 0 + +def destroy_by_pid(pid: t.CInt): + global _focused_win, _last_hover_win + for i in range(_win_count): + if _wins[i].pid == pid and _wins[i].active: + _wins[i].active = 0 + _mark_rows_dirty(_wins[i].y, _wins[i].h) + if _focused_win == i: + _focused_win = -1 + if _last_hover_win == i: + _last_hover_win = -1 + if _focused_win < 0: + for j in range(_win_count - 1, -1, -1): + if _wins[j].active and not _wins[j].minimized: + _focused_win = j + break diff --git a/VKernel/Kernel/services/keyboard.py b/VKernel/Kernel/services/keyboard.py new file mode 100644 index 0000000..e7aae90 --- /dev/null +++ b/VKernel/Kernel/services/keyboard.py @@ -0,0 +1,50 @@ +import t, c +import drivers.input.keyboard.keyboard as keyboard +import drivers.usb.hid.usb_kbd as usb_kbd +import drivers.serial.uart.serial as serial +import sched.sched as sched +import viperlib + +SERVICE_KEYBOARD: t.CDefine = 1 + +@t.Object +class KeyboardService: + th: sched.Thread | t.CPtr + running: t.CInt + usb_kbd_th: sched.Thread | t.CPtr + + def __init__(self): + self.th = None + self.running = 0 + self.usb_kbd_th = None + +kbd_svc: KeyboardService + + +def kbd_thread(arg: t.CVoid | t.CPtr) -> t.CInt: + serial.puts("[kbd_svc] started\n") + while True: + if keyboard.has_data(): + sc: t.CUInt8T = keyboard.read_scancode() + else: + sched.Scheduler._yield() + return 0 + +def usb_kbd_thread(arg: t.CVoid | t.CPtr) -> t.CInt: + serial.puts("[usb_kbd_svc] started\n") + evt: usb_kbd.usb_kbd_event + while True: + if usb_kbd.has_data(): + usb_kbd.read_event(c.Addr(evt)) + else: + sched.Scheduler._yield() + return 0 + +def start() -> t.CInt: + global kbd_svc + kbd_svc = KeyboardService() + keyboard.init() + serial.puts("[kbd_svc] PS/2 keyboard initialized, IRQ1 installed\n") + usb_kbd.init() + kbd_svc.running = 1 + return 0 diff --git a/VKernel/Kernel/services/mouse.py b/VKernel/Kernel/services/mouse.py new file mode 100644 index 0000000..53e07f8 --- /dev/null +++ b/VKernel/Kernel/services/mouse.py @@ -0,0 +1,13 @@ +import t, c +import drivers.input.mouse.mouse as mouse +import drivers.usb.hid.usb_mouse as usb_mouse +import drivers.serial.uart.serial as serial +import sched.sched as sched + +SERVICE_MOUSE: t.CDefine = 2 + +def start() -> t.CInt: + serial.puts("[mse_svc] mouse initialized\n") + if usb_mouse.init() == 0: + serial.puts("[mse_svc] usb mouse initialized\n") + return 0 diff --git a/VKernel/linker.ld b/VKernel/linker.ld new file mode 100644 index 0000000..9ad6879 --- /dev/null +++ b/VKernel/linker.ld @@ -0,0 +1,39 @@ +ENTRY(_start) + +SECTIONS { + . = 0x100000; + + .text : { + *(.text.startup) + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + __bss_start = .; + *(.bss) + *(.bss.*) + *(COMMON) + __bss_end = .; + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.rela) + *(.debug*) + } +} diff --git a/VKernel/project.json b/VKernel/project.json new file mode 100644 index 0000000..000a00f --- /dev/null +++ b/VKernel/project.json @@ -0,0 +1,31 @@ +{ + "name": "Kernel", + "version": "1.0.0", + "source_dir": "./Kernel", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "binary", + "--strip-all" + ], + "output": "kernel.bin" + }, + "includes": ["../../includes"], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} diff --git a/boot/Makefile b/boot/Makefile new file mode 100644 index 0000000..92da09c --- /dev/null +++ b/boot/Makefile @@ -0,0 +1,60 @@ +# Bootloader Makefile - Using WSL to Compile + +BUILD_DIR = ../build/boot +DISK_DIR = ../disk/EFI/BOOT + +SOURCE = ./boot.c +EFI_TARGET = $(BUILD_DIR)/bootx64.efi +BOOT_OBJ = $(BUILD_DIR)/boot.o +BOOT_SO = $(BUILD_DIR)/boot.so +DISK_TARGET = $(DISK_DIR)/bootx64.efi + +all: $(EFI_TARGET) + +build: $(EFI_TARGET) + +$(BUILD_DIR): + @mkdir -p $(BUILD_DIR) + +$(DISK_DIR): + @mkdir -p $(DISK_DIR) + +$(BOOT_OBJ): $(SOURCE) | $(BUILD_DIR) + @echo "=== Compiling Bootloader ===" + wsl -d Ubuntu -- bash -c "gcc -DCONFIG_x86_64 -Wno-error=pragmas -std=c11 -DGNU_EFI_USE_MS_ABI -maccumulate-outgoing-args -mno-red-zone -mno-avx -fshort-wchar -fPIC -g -O2 -Wall -Wextra -Wstrict-prototypes -Werror -fno-strict-aliasing -ffreestanding -fno-stack-protector -fno-merge-all-constants -I. -I../_UNCADK/tools/gnu-efi/inc -I../_UNCADK/tools/gnu-efi/inc/x86_64 -I../_UNCADK/tools/gnu-efi/inc/protocol -c boot.c -o ../build/boot/boot.o" + +$(BOOT_SO): $(BOOT_OBJ) + @echo "=== Linking Bootloader ===" + wsl -d Ubuntu -- bash -c "ld -nostdlib --warn-common --no-undefined --fatal-warnings --build-id=sha1 -z norelro -z nocombreloc --no-dynamic-linker -T ../_UNCADK/tools/gnu-efi/gnuefi/elf_x86_64_efi.lds -pie -Bsymbolic -L../_UNCADK/tools/gnu-efi/x86_64/lib -L../_UNCADK/tools/gnu-efi/x86_64/gnuefi ../_UNCADK/tools/gnu-efi/x86_64/gnuefi/crt0-efi-x86_64.o ../build/boot/boot.o -lefi -lgnuefi -o ../build/boot/boot.so" + +$(EFI_TARGET): $(BOOT_SO) + @echo "=== Creating EFI Application ===" + wsl -d Ubuntu -- bash -c "objcopy -j .text -j .sdata -j .data -j .dynamic -j .rodata -j .rel -j .rela -j .reloc --target=efi-app-x86_64 ../build/boot/boot.so ../disk/EFI/BOOT/bootx64.efi" + @echo "Bootloader built: $(EFI_TARGET)" + +$(DISK_TARGET): $(EFI_TARGET) | $(DISK_DIR) + @echo "Installing bootloader to disk..." + cp $(EFI_TARGET) $(DISK_TARGET) + +install: $(DISK_TARGET) + +quick-build: $(EFI_TARGET) + +clean: + @echo "Cleaning bootloader build..." + rm -rf $(BUILD_DIR) + rm -f $(DISK_TARGET) + @echo "Bootloader build cleaned" + +clean-build: clean +clean-all: clean + +info: + @echo "=== Bootloader Build Information ===" + @echo "Source: $(SOURCE)" + @echo "Build directory: $(BUILD_DIR)" + @echo "Target: $(EFI_TARGET)" + @echo "Disk target: $(DISK_TARGET)" + @echo "====================================" + +.PHONY: all build install clean clean-build clean-all quick-build info diff --git a/boot/boot.c b/boot/boot.c new file mode 100644 index 0000000..4d591ea --- /dev/null +++ b/boot/boot.c @@ -0,0 +1,586 @@ +// UEFI Bootloader for 64-bit kernel +// GNU-EFI based bootloader - FINAL PRODUCTION VERSION (FULLY COMPATIBLE) +// Complies with UEFI Spec 2.8 + x86_64 System V ABI +// Compatible with old GNU-EFI (<3.0) + C99 compilers + legacy firmware + +#include +#include +#include +#include + +// ========================== +// Compatibility Macros (Critical for Legacy Environments) +// ========================== +// Fix EFI_MEMORY_WB missing in old GNU-EFI versions +#ifndef EFI_MEMORY_WB + #define EFI_MEMORY_WB 0x0000000000000040ULL // WriteBack memory attribute +#endif + +// Fix _Static_assert missing in C99 (use typedef fallback) +#if __STDC_VERSION__ >= 201112L + #define STATIC_ASSERT(expr, msg) _Static_assert(expr, #msg) +#else + #define STATIC_ASSERT(expr, msg) typedef char static_assert_##msg[(expr) ? 1 : -1] +#endif + +// Kernel boot info structure +typedef struct { + unsigned long memmap_addr; + unsigned long memmap_size; + unsigned long memmap_desc_size; + unsigned long kernel_phys_addr; + unsigned long framebuffer_addr; + unsigned long framebuffer_size; + unsigned long framebuffer_width; + unsigned long framebuffer_height; + unsigned long framebuffer_pitch; + unsigned long framebuffer_format; + unsigned long system_table; +} boot_info_t; + +// Kernel entry point type +typedef void (*KernelEntry)(boot_info_t*) __attribute__((noreturn)); + +// ========================== +// Core Configuration (Type-Safe + Compile-Time Validated) +// ========================== +// Kernel load address (matches kernel link address, 4KB page aligned) +#define KERNEL_LOAD_ADDRESS 0x100000ULL // unsigned long long (x86_64 safe) +// Fallback address for dynamic allocation (if fixed address is reserved) +#define KERNEL_FALLBACK_ADDRESS 0x200000ULL // 2MB fallback (avoid 1MB reserved) +// x86_64 code/data alignment requirement (16-byte for ABI compliance) +#define KERNEL_ALIGNMENT 16U + +// ========================== +// Stack Configuration (ABI Compliant + Type-Safe) +// ========================== +// x86_64 System V ABI: RSP must be 16-byte aligned BEFORE call instruction +// call pushes 8-byte return address → RSP = STACK_TOP - 8 (still 16-byte aligned) +#define BOOTLOADER_STACK_SIZE 0x20000ULL // 128KB stack (type-safe) +#define BOOTLOADER_STACK_TOP 0x80000ULL // 16-byte aligned (0x80000 % 16 = 0) +#define BOOTLOADER_STACK_BOTTOM (BOOTLOADER_STACK_TOP - BOOTLOADER_STACK_SIZE) + +// ========================== +// Compile-Time Assertions (Critical Validations) +// ========================== +// Ensure stack top is 16-byte aligned (x86_64 ABI requirement) +STATIC_ASSERT((BOOTLOADER_STACK_TOP % 16U) == 0U, StackTopNotAligned); +// Ensure kernel address is 4KB page aligned (UEFI AllocatePages requirement) +STATIC_ASSERT((KERNEL_LOAD_ADDRESS % 0x1000ULL) == 0ULL, KernelAddrNotPageAligned); +// Ensure stack size is positive and non-zero +STATIC_ASSERT(BOOTLOADER_STACK_SIZE > 0ULL, StackSizeZero); +// Ensure stack region does not overlap with kernel load address +STATIC_ASSERT(BOOTLOADER_STACK_TOP < KERNEL_LOAD_ADDRESS, StackOverlapsKernel); +// Ensure fallback address is page aligned +STATIC_ASSERT((KERNEL_FALLBACK_ADDRESS % 0x1000ULL) == 0ULL, FallbackAddrNotPageAligned); + +// ========================== +// Validate Stack Memory Region (Robust Descriptor Traversal) +// ========================== +EFI_STATUS ValidateStackMemory(EFI_SYSTEM_TABLE *SystemTable) { + EFI_STATUS Status; + UINTN MemoryMapSize = 0U; + UINTN MapKey; + UINTN DescriptorSize; + UINT32 DescriptorVersion; + EFI_MEMORY_DESCRIPTOR *MemoryMap = NULL; + EFI_MEMORY_DESCRIPTOR *Descriptor; + UINTN NumDescriptors; + BOOLEAN StackRegionValid = FALSE; + + // Get memory map size (first call - get required size) + Status = SystemTable->BootServices->GetMemoryMap( + &MemoryMapSize, + MemoryMap, + &MapKey, + &DescriptorSize, + &DescriptorVersion + ); + if (Status != EFI_BUFFER_TOO_SMALL) { + Print(L"Error: GetMemoryMap failed (size check) - %r\r\n", Status); + return Status; + } + + // Allocate buffer with extra space (safety margin for firmware changes) + MemoryMapSize += 2U * DescriptorSize; + Status = SystemTable->BootServices->AllocatePool( + EfiLoaderData, + MemoryMapSize, + (VOID**)&MemoryMap + ); + if (EFI_ERROR(Status)) { + Print(L"Error: Allocate MemoryMap failed - %r\r\n", Status); + return Status; + } + + // Get actual memory map + Status = SystemTable->BootServices->GetMemoryMap( + &MemoryMapSize, + MemoryMap, + &MapKey, + &DescriptorSize, + &DescriptorVersion + ); + if (EFI_ERROR(Status)) { + Print(L"Error: GetMemoryMap failed (actual) - %r\r\n", Status); + SystemTable->BootServices->FreePool(MemoryMap); + return Status; + } + + // Check if stack region is usable conventional memory + // FIX: Traverse ALL descriptors until valid one is found (no premature break) + Descriptor = MemoryMap; + NumDescriptors = MemoryMapSize / DescriptorSize; + + for (UINTN i = 0U; i < NumDescriptors; i++) { + const UINT64 DescStart = Descriptor->PhysicalStart; + const UINT64 DescEnd = DescStart + (Descriptor->NumberOfPages * 0x1000ULL); + + // Check if descriptor fully covers stack region + if (DescStart <= BOOTLOADER_STACK_BOTTOM && DescEnd >= BOOTLOADER_STACK_TOP) { + // Must be writable conventional memory (EfiConventionalMemory + WriteBack) + if (Descriptor->Type == EfiConventionalMemory && + (Descriptor->Attribute & EFI_MEMORY_WB)) { + StackRegionValid = TRUE; + break; // Only break when valid descriptor is found + } + // Continue traversal if descriptor covers stack but is invalid + } + + // Move to next descriptor (correct pointer arithmetic for x86_64) + Descriptor = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)Descriptor + DescriptorSize); + } + + // Cleanup memory map (safe to free - only used for validation) + SystemTable->BootServices->FreePool(MemoryMap); + + // Final validation check + if (!StackRegionValid) { + Print(L"Error: Stack region 0x%lx-0x%lx is not usable conventional memory\r\n", + BOOTLOADER_STACK_BOTTOM, BOOTLOADER_STACK_TOP); + return EFI_INVALID_PARAMETER; + } + + Print(L"✅ Stack region validated: 0x%lx-0x%lx\r\n", + BOOTLOADER_STACK_BOTTOM, BOOTLOADER_STACK_TOP); + return EFI_SUCCESS; +} + +// ========================== +// Read Kernel File from Disk (Resource-Safe + Fallback Allocation) +// ========================== +EFI_STATUS ReadKernelFile( + EFI_HANDLE ImageHandle, + EFI_SYSTEM_TABLE *SystemTable, + VOID **KernelBuffer, + UINTN *KernelSize +) { + EFI_STATUS Status; + EFI_LOADED_IMAGE *LoadedImage = NULL; + EFI_FILE_IO_INTERFACE *FileIo = NULL; + EFI_FILE_HANDLE RootDir = NULL; + EFI_FILE_HANDLE KernelFile = NULL; + EFI_FILE_INFO *FileInfo = NULL; + UINTN InfoSize = 0U; + const CHAR16 KernelPath[] = L"\\sys\\kernel\\x86_64\\kernel.bin"; + + // Initialize outputs to safe values (prevent wild pointers) + *KernelBuffer = NULL; + *KernelSize = 0U; + + // Get Loaded Image Protocol (to find boot device) + Status = SystemTable->BootServices->HandleProtocol( + ImageHandle, + &gEfiLoadedImageProtocolGuid, + (VOID**)&LoadedImage + ); + if (EFI_ERROR(Status)) { + Print(L"Error: Get LoadedImage Protocol - %r\r\n", Status); + return Status; + } + + // Get Simple File System Protocol + Status = SystemTable->BootServices->HandleProtocol( + LoadedImage->DeviceHandle, + &gEfiSimpleFileSystemProtocolGuid, + (VOID**)&FileIo + ); + if (EFI_ERROR(Status)) { + Print(L"Error: Get FileSystem Protocol - %r\r\n", Status); + return Status; + } + + // Open root directory + Status = FileIo->OpenVolume(FileIo, &RootDir); + if (EFI_ERROR(Status)) { + Print(L"Error: Open Root Directory - %r\r\n", Status); + return Status; + } + + // Open kernel file (read-only) + Status = RootDir->Open( + RootDir, + &KernelFile, + (CHAR16*)KernelPath, + EFI_FILE_MODE_READ, + 0U + ); + if (EFI_ERROR(Status)) { + Print(L"Error: Open Kernel File %s - %r\r\n", KernelPath, Status); + RootDir->Close(RootDir); + return Status; + } + + // Get file size (first call - get required buffer size) + Status = KernelFile->GetInfo( + KernelFile, + &gEfiFileInfoGuid, + &InfoSize, + NULL + ); + if (Status != EFI_BUFFER_TOO_SMALL) { + Print(L"Error: Get FileInfo Size - %r\r\n", Status); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + return Status; + } + + // Allocate buffer for file info + Status = SystemTable->BootServices->AllocatePool( + EfiLoaderData, + InfoSize, + (VOID**)&FileInfo + ); + if (EFI_ERROR(Status)) { + Print(L"Error: Allocate FileInfo - %r\r\n", Status); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + return Status; + } + + // Get actual file info + Status = KernelFile->GetInfo( + KernelFile, + &gEfiFileInfoGuid, + &InfoSize, + FileInfo + ); + if (EFI_ERROR(Status)) { + Print(L"Error: Get FileInfo - %r\r\n", Status); + SystemTable->BootServices->FreePool(FileInfo); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + return Status; + } + + // Validate file size (non-empty) + *KernelSize = FileInfo->FileSize; + if (*KernelSize == 0U) { + Print(L"Error: Kernel File is Empty\r\n"); + SystemTable->BootServices->FreePool(FileInfo); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + return EFI_INVALID_PARAMETER; + } + + // Allocate physical pages (fixed address first, fallback to dynamic) + const UINTN PagesNeeded = (*KernelSize + 0xFFFU) / 0x1000U; // Round up to 4KB pages + EFI_PHYSICAL_ADDRESS KernelPhysAddr = KERNEL_LOAD_ADDRESS; + + // Step 1: Try fixed address allocation (preferred) + Status = SystemTable->BootServices->AllocatePages( + AllocateAddress, // Fixed address allocation (matches kernel link address) + EfiLoaderCode, // Memory type: executable code + PagesNeeded, + &KernelPhysAddr + ); + + // Step 2: Fallback to dynamic allocation if fixed address fails + if (EFI_ERROR(Status)) { + Print(L"Warning: Fixed address allocation (0x%lx) failed - %r\r\n", + KERNEL_LOAD_ADDRESS, Status); + Print(L"Falling back to dynamic allocation at 0x%lx...\r\n", KERNEL_FALLBACK_ADDRESS); + + KernelPhysAddr = KERNEL_FALLBACK_ADDRESS; + Status = SystemTable->BootServices->AllocatePages( + AllocateAnyPages, // Dynamic allocation (UEFI chooses address) + EfiLoaderCode, + PagesNeeded, + &KernelPhysAddr + ); + + if (EFI_ERROR(Status)) { + Print(L"Error: Dynamic allocation failed - %r\r\n", Status); + SystemTable->BootServices->FreePool(FileInfo); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + return Status; + } + } + + // Validate kernel address alignment (x86_64 requirement) + if ((UINTN)KernelPhysAddr % KERNEL_ALIGNMENT != 0U) { + Print(L"Error: Kernel Address Not Aligned (0x%lx, req: %d bytes)\r\n", + KernelPhysAddr, KERNEL_ALIGNMENT); + SystemTable->BootServices->FreePages(KernelPhysAddr, PagesNeeded); + SystemTable->BootServices->FreePool(FileInfo); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + return EFI_INVALID_PARAMETER; + } + + // Read kernel file into allocated memory + *KernelBuffer = (VOID*)KernelPhysAddr; + Status = KernelFile->Read(KernelFile, KernelSize, *KernelBuffer); + if (EFI_ERROR(Status)) { + Print(L"Error: Read Kernel File - %r\r\n", Status); + SystemTable->BootServices->FreePages(KernelPhysAddr, PagesNeeded); + SystemTable->BootServices->FreePool(FileInfo); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + return Status; + } + + // Success - print kernel info + Print(L"✅ Kernel Loaded Successfully\r\n"); + Print(L" Address: 0x%lx (aligned to %d bytes)\r\n", KernelPhysAddr, KERNEL_ALIGNMENT); + Print(L" Size: %lu bytes (%lu pages)\r\n", *KernelSize, PagesNeeded); + + // Cleanup resources (all allocated resources freed) + SystemTable->BootServices->FreePool(FileInfo); + KernelFile->Close(KernelFile); + RootDir->Close(RootDir); + + return EFI_SUCCESS; +} + +// ========================== +// 修复版:兼容老旧/定制UEFI固件的ExitBootServices + 内核跳转 +// 核心:显示所有GOP模式 + 默认使用第0个(第一个)模式 +// ========================== +EFI_STATUS ExitBootServicesAndJumpToKernel( + EFI_HANDLE ImageHandle, + EFI_SYSTEM_TABLE *SystemTable, + VOID *KernelBuffer +) { + EFI_STATUS Status = EFI_SUCCESS; + + // ========================== + // 前置校验 + // ========================== + UINT64 ActualKernelAddr = (UINT64)KernelBuffer; + Print(L"Kernel Address: 0x%lx\r\n", ActualKernelAddr); + + UINT8 *KernelCode = (UINT8*)KernelBuffer; + Print(L"Kernel First Byte: 0x%02x\r\n", KernelCode[0]); + + // ========================== + // 获取GOP + // ========================== + EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop = NULL; + EFI_STATUS GopStatus = SystemTable->BootServices->LocateProtocol( + &gEfiGraphicsOutputProtocolGuid, + NULL, + (VOID**)&Gop + ); + + unsigned long framebuffer_addr = 0; + unsigned long framebuffer_size = 0; + unsigned long framebuffer_width = 0; + unsigned long framebuffer_height = 0; + unsigned long framebuffer_pitch = 0; + unsigned long framebuffer_format = 0; + + Print(L"GopStatus: %r\r\n", GopStatus); + Print(L"Gop: 0x%lx\r\n", (unsigned long)Gop); + + // ========================== + // 显示所有 GOP 模式 + 分辨率,并使用第 0 个模式 + // ========================== + if (!EFI_ERROR(GopStatus) && Gop != NULL) { + Print(L"\n=== All Supported Graphics Modes ===\r\n"); + UINTN MaxMode = Gop->Mode->MaxMode; + + // 遍历并打印所有模式 + for (UINTN i = 0; i < MaxMode; i++) { + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; + UINTN SizeOfInfo; + EFI_STATUS s = Gop->QueryMode(Gop, i, &SizeOfInfo, &Info); + if (EFI_ERROR(s)) continue; + + UINT32 W = Info->HorizontalResolution; + UINT32 H = Info->VerticalResolution; + Print(L"Mode %2lu: %lux%lu\r\n", i, W, H); + } + Print(L"====================================\r\n"); + + // ========================== + // 关键:强制使用模式 + // ========================== + UINTN UseMode = 15; + Status = Gop->SetMode(Gop, UseMode); + if (!EFI_ERROR(Status)) { + Print(L"✅ Using Mode %lu: %lux%lu\r\n", + UseMode, + Gop->Mode->Info->HorizontalResolution, + Gop->Mode->Info->VerticalResolution); + } + + // 读取帧缓冲信息 + if (Gop->Mode != NULL && Gop->Mode->FrameBufferBase != 0) { + framebuffer_addr = (unsigned long)Gop->Mode->FrameBufferBase; + framebuffer_size = Gop->Mode->FrameBufferSize; + framebuffer_width = Gop->Mode->Info->HorizontalResolution; + framebuffer_height = Gop->Mode->Info->VerticalResolution; + framebuffer_pitch = Gop->Mode->Info->PixelsPerScanLine * 4; + + // 像素格式 + switch (Gop->Mode->Info->PixelFormat) { + case PixelRedGreenBlueReserved8BitPerColor: framebuffer_format = 0; break; + case PixelBlueGreenRedReserved8BitPerColor: framebuffer_format = 1; break; + case PixelBitMask: framebuffer_format = 2; break; + case PixelBltOnly: framebuffer_format = 3; break; + default: framebuffer_format = 1; break; + } + + UINT64 calc_size = framebuffer_width * framebuffer_height * 4; + if (framebuffer_size < calc_size) framebuffer_size = calc_size; + } + } + + // ========================== + // 兜底:GOP不可用时使用 1920×1080 + // ========================== + if (framebuffer_addr == 0) { + Print(L"Warning: Using fallback 1920x1080\r\n"); + framebuffer_addr = 0x80000000; + framebuffer_width = 1920; + framebuffer_height = 1080; + framebuffer_pitch = 1920 * 4; + framebuffer_size = 1920 * 1080 * 4; + framebuffer_format = 1; + } + + Print(L"Final FB: 0x%lx | %lux%lu | pitch %lu | format %lu\r\n", + framebuffer_addr, framebuffer_width, framebuffer_height, framebuffer_pitch, framebuffer_format); + + // ========================== + // ExitBootServices 流程 + // ========================== + Print(L"Exiting Boot Services...\r\n"); + + UINTN MemoryMapSize = 0; + UINTN MapKey; + UINTN DescriptorSize; + UINT32 DescriptorVersion; + EFI_MEMORY_DESCRIPTOR *MemoryMap = NULL; + + do { + Status = SystemTable->BootServices->GetMemoryMap( + &MemoryMapSize, NULL, &MapKey, &DescriptorSize, &DescriptorVersion + ); + if (Status != EFI_BUFFER_TOO_SMALL) break; + + if (MemoryMap) SystemTable->BootServices->FreePool(MemoryMap); + MemoryMapSize += 2 * DescriptorSize; + + Status = SystemTable->BootServices->AllocatePool( + EfiLoaderData, MemoryMapSize, (VOID**)&MemoryMap + ); + if (EFI_ERROR(Status)) return Status; + + Status = SystemTable->BootServices->GetMemoryMap( + &MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion + ); + } while (Status == EFI_BUFFER_TOO_SMALL); + + if (EFI_ERROR(Status)) return Status; + + // 将MemoryMap复制到静态缓冲区,避免ExitBootServices后数据被覆盖 + static UINT8 SafeMemmap[16384]; // 16KB should be enough for memory map + if (MemoryMapSize <= sizeof(SafeMemmap)) { + __builtin_memcpy(SafeMemmap, MemoryMap, MemoryMapSize); + } + + // 构造启动信息 + boot_info_t BootInfo; + BootInfo.memmap_addr = (unsigned long)(MemoryMapSize <= sizeof(SafeMemmap) ? (void*)SafeMemmap : (void*)MemoryMap); + BootInfo.memmap_size = MemoryMapSize; + BootInfo.memmap_desc_size = DescriptorSize; + BootInfo.kernel_phys_addr = (unsigned long)KernelBuffer; + BootInfo.framebuffer_addr = framebuffer_addr; + BootInfo.framebuffer_size = framebuffer_size; + BootInfo.framebuffer_width = framebuffer_width; + BootInfo.framebuffer_height = framebuffer_height; + BootInfo.framebuffer_pitch = framebuffer_pitch; + BootInfo.framebuffer_format = framebuffer_format; + BootInfo.system_table = 0; + + static boot_info_t static_boot_info; + static_boot_info = BootInfo; + void* stack_top = (void*)0x80000; + + // 退出UEFI引导服务 + Status = SystemTable->BootServices->ExitBootServices(ImageHandle, MapKey); + if (EFI_ERROR(Status)) return Status; + + // 跳转到内核 + __asm__ volatile ( + ".intel_syntax noprefix\n" + "cli\n" + "cld\n" + "mov rdi, %1\n" + "mov rsp, %2\n" + "jmp %0\n" + ".att_syntax\n" + : : "r"(KernelBuffer), "r"(&static_boot_info), "r"(stack_top) + ); + + while (1) __asm__ volatile ("hlt"); + return EFI_SUCCESS; +} + +// ========================== +// Main UEFI Entry Point (Clean + Readable) +// ========================== +EFI_STATUS efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { + EFI_STATUS Status; + VOID *KernelBuffer = NULL; + UINTN KernelSize = 0U; + UINT64 CurrentRsp; + + // Initialize GNU-EFI library (required for Print/Protocol access) + InitializeLib(ImageHandle, SystemTable); + + // Print bootloader header (user feedback) + Print(L"\n=== UD UEFI Bootloader (x86_64) ===\r\n"); + Print(L"=====================================\r\n"); + + // Print initial system info (debug/validation) + __asm__ volatile ("mov %%rsp, %0" : "=r" (CurrentRsp)); + Print(L"Initial RSP: 0x%lx\r\n", CurrentRsp); + Print(L"Kernel Load Address (fixed): 0x%lx\r\n", KERNEL_LOAD_ADDRESS); + Print(L"Kernel Fallback Address: 0x%lx\r\n", KERNEL_FALLBACK_ADDRESS); + Print(L"Stack Region: 0x%lx-0x%lx\r\n", + BOOTLOADER_STACK_BOTTOM, BOOTLOADER_STACK_TOP); + + // Step 1: Load kernel from disk + Print(L"\n[1/3] Loading Kernel...\r\n"); + Status = ReadKernelFile(ImageHandle, SystemTable, &KernelBuffer, &KernelSize); + if (EFI_ERROR(Status)) { + Print(L"Error: Kernel Load Failed - %r\r\n", Status); + return Status; + } + + // Step 2: Exit boot services and jump to kernel + Print(L"\n[2/3] Exiting Boot Services...(If you see 3/3, that's mean the kernel has finished, that is an error!)\r\n"); + Status = ExitBootServicesAndJumpToKernel(ImageHandle, SystemTable, KernelBuffer); + Print(L"\n[3/3] OK...\r\n"); + if (EFI_ERROR(Status)) { + Print(L"Error: Jump to Kernel Failed - %r\r\n", Status); + return Status; + } + + // Unreachable (kernel is marked noreturn) + return EFI_SUCCESS; +} \ No newline at end of file diff --git a/build.py b/build.py new file mode 100644 index 0000000..9e8956e --- /dev/null +++ b/build.py @@ -0,0 +1,116 @@ +import shutil +import os +import sys +import subprocess +import threading + +clean = '--noclean' not in sys.argv +kernel_only = '--kernel-only' in sys.argv + +cmd = r'D:\Python312\python.exe ../Projectrans.py --project VKernel/project.json' +if clean: + cmd += ' --clean' +cmd += ' --clear-cache' +os.system(cmd) + +shutil.copy("VKernel/output/kernel.bin", "./disk/sys/kernel/x86_64/kernel.bin") + +if kernel_only: + # 仅重编译VKernel,跳过其他应用和库 + print("[build] --kernel-only: 跳过其他应用和库的编译") + so_path = os.path.abspath("Libs/SerialLogger/output/slog.so") + vpui_path = os.path.abspath("Libs/VPUI64/output/vpui64.so") + # 确保disk.img中的apps和libs存在(使用之前的缓存) + if not os.path.exists("./disk/apps/hello.elf"): + print("[build] 警告: hello.elf 不存在,请先不带 --kernel-only 完整构建一次") +else: + so_cmd = r'D:\Python312\python.exe ../Projectrans.py --project Libs/SerialLogger/project.json' + if clean: + so_cmd += ' --clean' + os.system(so_cmd) + + vpui_cmd = r'D:\Python312\python.exe ../Projectrans.py --project Libs/VPUI64/project.json' + if clean: + vpui_cmd += ' --clean' + os.system(vpui_cmd) + + app_cmd = r'D:\Python312\python.exe ../Projectrans.py --project Apps/HelloWorld/project.json' + if clean: + app_cmd += ' --clean' + os.system(app_cmd) + + os.makedirs("./disk/apps", exist_ok=True) + shutil.copy("Apps/HelloWorld/output/helloworld.elf", "./disk/apps/hello.elf") + + term_cmd = r'D:\Python312\python.exe ../Projectrans.py --project Apps/Terminal/project.json' + if clean: + term_cmd += ' --clean' + os.system(term_cmd) + + shutil.copy("Apps/Terminal/output/terminal.elf", "./disk/apps/terminal.elf") + + garg_cmd = r'D:\Python312\python.exe ../Projectrans.py --project Apps/Gargantua/project.json' + if clean: + garg_cmd += ' --clean' + os.system(garg_cmd) + + shutil.copy("Apps/Gargantua/output/gargantua.elf", "./disk/apps/gargantua.elf") + + scene3d_cmd = r'D:\Python312\python.exe ../Projectrans.py --project Apps/Scene3D/project.json' + if clean: + scene3d_cmd += ' --clean' + os.system(scene3d_cmd) + + shutil.copy("Apps/Scene3D/output/scene3d.elf", "./disk/apps/scene3d.elf") + + os.makedirs("./disk/libs", exist_ok=True) + so_path = os.path.abspath("Libs/SerialLogger/output/slog.so") + vpui_path = os.path.abspath("Libs/VPUI64/output/vpui64.so") + if os.path.exists(so_path): + shutil.copy(so_path, "./disk/libs/slog.so") + if os.path.exists(vpui_path): + shutil.copy(vpui_path, "./disk/libs/vpui64.so") + +os.system('powershell -ExecutionPolicy Bypass -File ./Scripts/disk.ps1') + +#if "--noqemu" not in sys.argv: 不准使用 --noqemu!!! + +serial_log = os.path.abspath("serial.log") +if os.path.exists(serial_log): + os.remove(serial_log) + +print(f"[build] CWD: {os.getcwd()}") +print(f"[build] serial_log: {serial_log}") + +qemu_cmd = [ + 'qemu-system-x86_64', + '-pflash', r'D:\msys64\mingw64\share\qemu\edk2-x86_64-code.fd', + '-drive', 'file=disk.img,format=raw,if=ide', + '-m', '256m', + '-vga', 'std', + '-usb', + '-device', 'usb-kbd', + '-device', 'usb-mouse', + '-accel', 'tcg,tb-size=128', + '-serial', f'file:{serial_log}', + '-no-reboot' +] + +print(f"[build] QEMU cmd: {' '.join(qemu_cmd)}") + +proc = subprocess.Popen(qemu_cmd) +proc.wait() + +print(f"[build] serial_log path: {serial_log}") +print(f"[build] file exists: {os.path.exists(serial_log)}") +if os.path.exists(serial_log): + with open(serial_log, "r", encoding="utf-8", errors="replace") as f: + content = f.read() + print(f"[build] serial_log size: {len(content)} bytes") + print(content) + try: + pass # Keep serial.log for inspection + except: + pass +else: + print("[build] serial.log not found!") diff --git a/disk/EFI/BOOT/bootx64.efi b/disk/EFI/BOOT/bootx64.efi new file mode 100644 index 0000000000000000000000000000000000000000..1ada45093508f11ce3ca15f9c661771384b79eee GIT binary patch literal 77428 zcmeEv34B!5_5VvqAgnJaC@$4e2MwzUiYAJhAql*J2}IeVxPU+eWr<-1g0?a^iSiuB z#@1G}tL?!?4_b#(Mpz{0w`}=R7Og@?S?z#7# zd(OG%o_p?j_uVPKqQU537)BQU+u96cHLm;=i|>E`6-0KAQ`ht`9`F48X{$4Q&!0Aa z=B)Xy>Y91i)l8k^nl^Rr+vukE%f8$nw`OiEh6CsM9cfB?)?J)U87Ndf=6TcSGZ zPi}_M?WhYMeZj&08MS^CuL$CcypnHrO3KG&7*hsMpX#3qSW7v+n(#jc|F_{kw>|en`3E z!8P*)g?XnUuNdWAxJC~xmkw!9{hyM6721_^>19^rqAy=;YqKIlcYS6UmtS$|WzqSI z4da@tqdq9R6TCiHBWM+^Gwb)`&I$vC z6&_EFsPhDex)teF;YCB1J>Fg1R#D8F^-bndm<>a~QM^(%B#>KnOyOs#H3F*-^Z<25 z!8Tvm=dQHu&E9=`yx~bxeBsM1Us1Eq-eTVR9f}t0jUQ{C3f?zn_%fQUqK#&Vw$Tb- z+Q$kn%CVZ??`CBju$CNXYwKhz*lsO>@$6_Ud@)qiDX?{~$Ccw>4_pOrdD^z2I24mN? zwY8yS{1u=?yRyI8)n5Bm&v>u>+yu-1dYrXn7o|~U&da{z#0rYrvW zz(vxi_ZUuE?^9I0Zvrp1*NKg4{x4Oc(h67Pcdvav}yA*lDZC6C!*HFbVFz>rCfzjd-esGAT$Hx?Kv3Istn=;SzVl$oEM ziOl}UtQwrG&-{;N{)^1-;>cg?v-hG%*X{!UBPfz0p4i)<&^)zRwKG>1x}A6)K~7bJ z=n0=7qCky*fq_crV=sTvR*|3RhNNA9l{tKe#I7e^2mNCNYM&jCc2n4?^=u#&`;l=T zN1UqvQsnV$U+}U0`?U5V7iCB$f5*(>6jqX&ss=S*HF@pV6Z4h*rDeYc3${Y^2L>j@ z`4lMTyfQO-1@L&x#`6JuT}sFT#0r;8!JL6HSxfz>6@3}${>~jVIP`N+yj9@XXxSTJ zyN?x>{O($z;U6I@Y7kWask!uQmJj_HSj@UJWZe%%Q&!{=H|<{ZRX3YtW8sQErQr+8 z>)ZUWALw{lz!!PQO*7|<3_5_C@$L!zVI{AaY)hq~$;H5|J;@5!3CvI)pZ!_b$_ZCw zfyy~($BQYhyl7uwy`c0hQaVvLzveh0BVi3;U2NKUMOSejAokC*!qt5&Oy&7j{a*i6 z8XYTK>lW!25z<1T!q9;%%l5e~VKvQ?mn`#d`z-s%?qd5Ti{$z+ zD&0dPh}&GPcS+Z zjf0fkl9aQU+EnKbG7}%(vv>H3`=uU-?-U@bev7{ol}RYStYU@l5q%?UuP-9&(%E4- z%f81Q1RhQTwD0FoUjRoR`|R0n7v=!g7s_CN4u8j2)EaOpb!7pe448FPo>_Mz#)v=_ z1zIcoh(K?JAD8qk`Q9itjD@|eNcZBOBj5hSvR{mD0OvGaT8qLz#{6LqeFn3-We<9m z7Z5?QS$6hnk))jMiyjyEK8!Itd!>R`TAQ+4aL*6Gzg1v(g4w@O_r>;#8P262KHwt6nzsOEHcN!E5$Sin{YYv>))z9 z7yfB(!CPvoU8n7_n%_D|WCK|k<#Z`I51$ESz+^fy(H}y7!khe>-sE=^#zl*SZ!)w7 zhR=!&x&aqozpt(4?>qR9D|pKr&U%-2CbO-@3a!hqirxwAvLZh!L#cvKz4p7l0Yfhh z!G<*-?BG9%en7Znf1><`3@a4R@fPh4M1A&dpS`o-t>}fAT3|J^WNY8UH}l%-ef>U_ z4K{egrSH&rOl>fZ246uzHu#Qjz@X@@Y!LLF2!H)>?R^@EPWIWY%CBFS)SkqAw9PXS zp5ZmOVXBz`lPs1|_P5VM5|HMuN-M&33MLyXI=M>phJOSnjAFxou?28pS;42YWO-C# zihpMkf+geN$kE zsE#qZ0i4BuuKHVY)cbD0U{&siMw174(=@plHN_k@WXA>SPXhJid>9QR#&$QsB2aE+ zb&$I*Wi(281}}R01z<4iAH=uRDy&h^YN1$(geO*R%>EThWNgfSoCPiWqIz6mzZQr~ z_I2XYdpR!<%`~>3hc&}b>sjiXgfMG+keIx1@pwD?J>*4CfhmpNH;tvJUeRZI<2I7L zll6+Sn{e?D2F-=g1T}v2IyyRywUC9H1#QTW-HN)9f8?StZBboE(&|DffIMMWg*cu)#o-+zp>ccJEt;-xpXVM!bKGDQP9{wz!xD0p1K zma2wQvoWhj>C&tm3l*%Qmjb&}3TymAlG&c;4I|urXB{azAVehRq}33qVVPda3=AFG zKc4ZZP*(6EO{VOX?603Au3rIH3ZmNHzVLK+UU~h8{;o=(5mQP@jwY;+h5bO-q1;|S zk>k0SCGQZ>DERdO!vSV)ki*f^TH)Jk{T%H@2w< zv@;n$DKi_JVlNAru!pgo>G`oqASb1~GffqLMb1BPWkb8*ulJxBvgur0ZLP^>hcK8D zE`u7j_$PB5oe9eG3z|6C$&a;(W7n`&cjkqKv7EEQU5VmVFf;yNlpZ2EqF8)w!oN(e zSZ(+*Xm^Xh3(C7y`Ed4$xC?i0f{hd%0YR%?xKj)s%kBVj{I`*u)zD%wB~w`FE__7) zP7?8kr!^nsm-FrOd_OG|H_2Fn;EMkk%mbvs^RPn)O zvEW9Vbv6-%vujY&XCDx2zE+E#lCZjr7{LuJ_7z3_{VBLI%Wf&yMlZ0?+INm+cLe2? zK6`(P*`z$t|3Jtk1Wcz;0$|CH&4Psx{%bfpLp1me3@HQ~e?rcuG^>A2v1keYpP3Fa z8DL9#ohe-o?uDy5TXE0AAmM>6B+FQ!ke^Lb9z7HA*i=XXnej)(_(kva zu@FVciT0o?8d~4S+5g^WF(UX0dM0`?JtFw@J*-G*m&JO8(bq7EgGB#O%-x`1=Vnq6 zLwtqPiHj9ItKszn2oa#Oc6i|uhO-x=oE)AOIxrb^OR}TsgAQchjnZEGC5eek6PH+` zpl>Tdb0J1isnl1{#qv{i`HpD$-<9`3zcKqSL4*~ve`L@@oiZ?OdF-#?35>J9#rg~w zi^*wzbQi5YHHjQxjCU@p`_QaE4g&`*{%sR3p|zRe?w0}=ha097l$G6XF6DeklUfMV zW!518hDvbDt(T%!Xn%%T_ftR$;1z%mkGU#qQkBQf8ZV24)@F!eQA`Rg3wI#r;W%MK zP`vD3q9WAu3(MlExB5o|{JJQ-Y`XyFG=GrW`3Psmpik#FN{hA> zo4?#pTGVX*d|k2Yoj_S+e3v#4lp@Kxn4Nz zO0Z`AN(@=j9{31KaMikkPviSU!T6q(-jVZZvDw%bE9nMihb9#5}l6SXtZ zQP6x0Bd@L36IiieK8_!+)+-ptqAb@Al`|Jyf(QjdxC7yz@35Lb>R@Gj5{+JN7+|HSDg6LzwYE&mk#hi^UufVuVZSGOXtO-_^Ki+uKl&>C zN%JPdpwog0JNwIZa>V8(y-N%j`cUD@oPwsZ`n`cp@xcvP^gy&18iBo`CdheRuBT|@ z!rfN*LOQC)^LfGU=yog{Xbp=bI1oqmx=#b$L1Dh0rPc|5z8nSNN}e^I(o4z2Dh3W< z3LtGhj!o*sz6e6lVtzJPb*h51e_FhMRfD;zM-^Pm{^qKYndo!;Z^Zv687KfJu6|qK z-GV0i8<=lky}m~Aew(dj>Gpn-Wxqa=F-aMcGy9$EHv9e9om2QGZW=HMHXGQO{L zSKxcPdkVg1yQ}dn!!)>gO2*Fq4Et;50Y{#|ktcBE2^@I>N1niuCvfBm9C;c?p2UGD z^9nF~^);eDQ$1StFZByOxMLBe2?t^iqtxi7^?B>`{K5QYnXf|F%vJiV>i;$+zmKS` z@?+s9$tI9kD;RG=mlSkIo$=9T^RdR>B;e3&=kDH@b^~hl%|Z}uujl#IiIGMrah;; z&Bji3O@Tgf1esyqMq(Ij%CI66GcaZmcX-WgJg#u9|Kolw(5nWsabiY6leh=GpeY_r zF)vEK5}zwRK;Y|9*c=#;Kr5`-Cq&(y+S@9$7xSak-!IL^!{{&RG*=x@^{)yYaQVM1 zXsSZpGvilOHH1EKScA=2+9bJe7SR_Dzq<-a&G>cOheH`xnbjf`r+ zP3HxjBcq-rWZ_Ay35=MVw^}D`vqJBGgW3N4_!`g+X$67LIIO$`J)I;y`#}%9Jr39t z;;x3#kzPS&z*(qh!Js|Jf!aAzfu;|1Ua-SwH>F~f{gvW(=2($Hrn#zvxoXToD>C;W zEE0I$e~DQa2QdpT1vaye&tn1XZ`SVyunH@LEes1`+4@&Z59rDhtRG6Jdg4RL*%Fk$}?>F;G_ z9HbneOBowSw!uZ&$2fzue^6`WZqYA5Z zH7%Q~Is#+hQy2rgleQmhU)gsovW1DS3SDgYC$UQ|1KzF@FJ!Wy8--Kl2@NYX0-srt zlj)cGkG1-(^+tR@5=+XAZP9PBU=fo6<15h!7iyvPE;$oGz6Eaq=cXimlK-Gx}-4HUQG-74c&%2o=S7g5s-4aV4;K7J7F$ddnmJ9|@v+f@l~; zJQT4&vXw^in_O{0)qVCJwOB#hSWoX3q&KHcCsLoPLYU493Yw(8LqwrrBaOhOgU@~$f{DMP>1F?*Fgfhy z;16OcKJ5IX%AbrUf`M*vHP07qTyU(=A0eDQx<69~6Y;S<(b_3Wp7z>gtuGKaJ|;`F zyPBbLWpC{Lp`B(^G%J!7jAV^W#VGC3>gO@giudm1I-;sM+SP1q!NiIOY(GQqTm4?o z*q@wl*nWyRSN3cR#vz=(wo75|sT;}ium>p`A={&3GCudbF!bCj(*$D8e%S9szm~o% zg5LPXQ%HPcuLr)b6u#_3?jVXHDIfFV=5hCO33$gtrXJ;4OnPc*~#+-ZH%GJWW}irVKu_qzpbYDTB}4gv;csuaO}}sqb_Se?z(u zY__`%kKdglE#2R=Kd(|~&;2(D%nDzg6FrVTX{1*ND>59DNU1xw=DH-?@LRDM1dIOx z)*0Q1?S}=?Upg8=zP7;ItD}hNU`6Xc&ssBag8a~J?6=#(+G`(H9eb?i z{g=R|Tam2GtbW)rl0A1XdCAJB@vw^GnB6BR{zgOOuaU*Tv1E88d$7B| zV^!#27r(0t;neWBVk=Z)VGT1p)3Qs7tqV&E=Z`@H`IFZ@+ntoyh$kMVP)&2e7VM%J zSY?mOL;+w5FGcaTakW^$=OcJKkm%yS;yeqQf&Gv|qCL@)oY!gZV4$`G4MUcA=z&q- z!<~gC(D^h}6|_@RXr`*oe!q4HQPS-9kXUNdOf47b$At;t61)j%D^llf6<0iqrb_o= z0+@68;Lim}w|oWZ6W!V$c}v!l0_{KkAEl=Yl;Q~V_z|7K_|5XY4+56CqEu!{+ABA{k!0x1FQr+48La}`Sy-GkI-L=uWJQN9cfFMcJWX#!e zK1k6wxE$O#^xq{?KGet&P9C-vY1OkfZG=5-vo>uK7m|!f&AgR{LB53W!xhB|E?yuP zS~rvayVOswz3o4*2#2Ry(Yfhwl1?$4Qt3Q1mCmGbf*nYgx0hSr{-f%Vr1uRU+C znwT9bbBG0)c~(-&>GWb+!}{kxYLk)#qxZC0kU|&qRCm14$Uge ziB^Kh!|U!p9zBBY9+N_SQYD0Z;WTuLJ+_bWiA|mI!FST1mHFHJCY^k zPWZ!~iB(?nmW>m>u*`u`VSKQuvF;1tVI{Z|(T{=Kvfqn70OzyH{A>%#&)xXMV3a_e z{?7x1HC?w7xMX6%{e^jD^B3k7P3GAx3p$yTvNs`a^re~G{Y7!TyxAPQ_NGk#kMVr= z*v$B8Y8+nPyoRKU2|m;eXXVm^vfTJDL01cQt#NhqI@)4VjU%x0@e53<0kd)QGgCqNu&xWb59{U+SfN^+!EDR!=ElQ?8bsjA%vD=T?U$f3Sg1r@ z=smGo0sW%IC-4l#h%cjmC>oUMr77-<>|gQx9nPHC=?{Nq$*w&3kzywz93{)X)4dc| zpN%c0;)<<}xPrH06Fd5s3o2-xdo?SDd;jubUc}?m+1%VO_sWh`SZA6&`Kk!_ z(LaEetnhHlH*E^M*n8ZYQN(9UZ^#PQAZXCqcaNy{xbTGRwRUr8bC1y0PN6O4-gR*I z?hzi7WtP3PZygE-Da{q4Mx9(nnPn~X>W^X324-OAD4S_}O<30qKq5bSHHHhwT#5{S z$Zs_n3Nis9%vD0D6_rGZI(G}MSinLq7VeDr!m0)=3|`Y)Upm`Ec|pWy@?t=W*9s zMR8r5Szn96gBbU~;xsx0ST`zSKzt5foe;-8s`FAA2Z>E2f>G$F8OKqw<0h1r*k1^V zKp}+;t1~G;KBkqfz&-930e?imn8$Mb;Fga`T}FnnvChrMtGu={i^bUA{P3O%$q)Ie z`$;CBt2bhA6~?k<*`JH(IH8e>w-9e1x3H(VYMV8!*UzntH!p5`U##0AkGS^(0dVnh z3H~h`V+B`XPq<%~-N?P*o62%<2a!gixWrFj{o!U~uY7N01p2cqhaokrI5+x=KKHwV zKVi|b+`!wz%~idsQ_2M1_Spf1cfV;1bn@B%Br;$4=~i6K#`*)eV0X9~T8rL8F2QvW z@6XY;??O@74BduAxxFvmgZl$Bd>1}V9P?-D-h0M{q*(d?t&G%jpd4k zBAG&AxkcPU#|EAhs>>Y{R(!R>3uUeh?LXVReR5aBu-Ar~a;y$`n*hVI(h6U|ZTsxb z=%eRhAMbS1VSk2>!ItXii63%r%r>9B58;@5+|NM0qJMmY=1F#YtTP`ZX#a!5Q7D|} z>3pC;l}Chfn)UBPyYW02i+(YvLP!b{QPx%|!VHdQqWULb!QF1aH?-3)2@~VSDeHo)c$n6I7ol|Z@Xb7*;!s7#^ z?{}vNW)IK?pOOQT_{{oe@Su~}ZrdSD4dPc=V`KQ0x~GJXF&iIubBhEGdHu8Ek!Z0V zXOyLGC;kBDOCR>buOv`eJ?t%ltp7vO;kKlfL{Iwc$K`6e{#4N!(6Xd~w4kQ3zkZvL zUqa8;VaPefBAqMHd$sVHo;5d6x(!|`U9Ztj`~VTt?&?v`dX-JLI>*whEmwG z)f5$}(2`HJln{}$mCAg%U^LCTCe>{Bb}cb22S;u`lqPwz)#DLT~SY<7h?A){90e1C0SMWQ(~|M z4`f&;G+Ck6{qR0>DP~R;Bn2v6KM3>*m7WPE(nTB|66=RtrC-Z|GQpo&e;S%BhcN02 zg&N4IvtX~Vn?GO)sih6X22#Y(KHQiTTBnsQb+U?G2S-tjPwM>&V2Vu>9&KVA#Ky^V zLf(_H=OCRqE1X$|%{NNwP=#N+IeAopCfdG)?2Eohv1bW<;|_!G7Gfwxy0tbEpIP4s??-gOKMut@?U>l#k*((gZBI!aCk*sI`XP3YKrQu)MvYkl z<6BvW;oGtiFja=xtSh1djS~nGMnw&XzNv!9N0CG|=r@fM^sk8R_fqY%KbE6XY`z$Q zNqrAxA3{gEz7H9iBJ&Mm6$5>Te6G9*VFbgx{YHfWj|SvSb*1h*Z7IJnbK$Xm7VQLY zIaUv{su~jd@d1+1<25o~nR^ue->t7A3XlDv(2Sc9nfw8jj{OOxs~X_vpnT0yl>eP5 zubm=1!@8RG0ZXZ2h}|Lx41Y))a|(SaWMF7twiBMGwRQ2|gr*RA{UIl1{^@-3;>9cB zw_)*ZNXKeIZs6xwzzxGA4z?@j3x;WbfY10pgkR@*QV6otMm*|=+Ba|ILjm{^!SosE?L~ zuN1_+6*`j3U-JZd`)E2lj=h_bJ~74?!YOdtmbB@+u8c!R>pL_wuoD{k0m;>FkF@{QUQ`;Tp7Nq;sCU91pYsMa~D z&BT^t&NwU_oq8BJ=GkVL1K6i@a$_rwfTux}hJAFDuM@NNq3{+s6NhOxRx7OHq2;TO zz}FoppRVb#KXc1Ox>PJs?EtL<0qGUP$HYzq7YJuBzmI%WR0v0R%x7(FtH(e^pBRg; zH6!uGcRibM^*qH?Yz9iGrIdNAm!|$bOkiq$Wc-Tz)$z%82=j_AJE9K23pwa;|0eP-Em%3jKtVXNd^?^x3asqKLJkh}2h{gOF?lvupHv zHWBf=uNKE0fQUiJkFGyP5Ojtp8y$f%vT@Q9x0Ckb|F!3x)MYC21iWf`C$w15zhu{D z%sAGjk(TU-I(I%T-KLRPQy|KLckOeTwnygUznJRy49nXiaj*!3j`r>BK)4*&G~nH? zVdzQ5(?;i1m0^?i_4F+eOuHXNtkBJTDqNCyD%@2#9%fxVqiDkUILu>TGxS#xnkdOJ z8!z;QOL7^6_$6Y!!$3>2rNA$VSw+g64~NBb-*AZqJt41$=n2;YeZV9p189Scjge`7 z7~pi_9!2tSQE{+=%tbskAA*8{v!J!9q>@(={kjCRtcrfk#}ZryzX;U9uU;bfHU1aa zp7aT#Us7KwP!XV%kuM-tdK`X|GRCy#Ro&tZ|1=6n{6wMGjCA!GXmtVMwxaRxVAIaF)VS2Wvh2gG^D%^FrK2x}A9#@o+qT3M3 z6RnzjA6K;3%Jyan^@3j9E}f8cHQSG1?ZF)Fx*d*~e-t!HVve+N0K~ANntOF0K;_5D z{(+x;uJE%d|HG>JLA2ikD`Kem;kQ7Tp-YgBuFAo?cV_4cBxK)B$_*@sYjy7M_yY~} z&8Um&#G$xbuj4Q*(!zT4b7WNSUqF+nSjmXq(?XxZC4Gb|`XMBy z=Gk$1b_u-m3wRcU75(6J70JWvT6{MPk-Ul)70Kfg=N>oSb2g%HZ-C{*%Z0_U3*bs% zei30i`7Sp~Jt9VwS^uMipMm}q@w;Zs!76^27(Vp``ocF?iuV}f@Xf@CmtniZX%Dh1 zF!B+C3>BlC5KB*5_j|&3+W6oy&-g|IKYDu5uA7%iW2)w zc$w@`%qTn+0QrT#e?!N9x2EGZAd>wVE;(6+4X#gy z4aoV)f&)ej27m%a4A@n#?$BLTB4|-@!xXLHgrg@I3Ou}W+GO~mUsV;vf=W6KZ)!*v{bqNReYbQMQK3^hvr*^ZR8Szirz zT#Uf6P=ddk451A%IJ%>g1a7zCq<4%Vy`hX}}%9-_96$pBS+8q8{E0|YqP0dNM zd-Xb*r-vlGoh4!&T2h=E13C=RHCuJq=WsN{A7Oky+PZ zEnqkmA)+yulYPyA+V ztRu*S&%aXjAvS=kkrWBX{soL_fKSnMY87}CPe`LJ?SY72i1$UF*W2=OY?J+^*gAqh z#&U?zYk!QVuhC1N61zk?z@q0OO|Pw%OP{5bG7`BoCwXR1>=}&y^K;O#WIb!^rBup( zpO<#?Z2>RN7R8Y)q1{*uzAjE=>V`9yu&W1$;Jk{{R{6#&COo)A@Ds-iPLUEDc9Ql` z(2w;hUUhk<4+`>KJiZ&^wLgO68U4+Z#Do3YJnRX0AB77V%@Me=+(6~RUVOZ`?7%n4 zYwr^R5WBaz{T{->IYi=s4HeztMs2+5503ff@KCe^xnLdlYq{+LhJOXf0W6P`z)E>< zS<`rIW{cP=1d!MZgYMbdN$+Msg4MkB{KdcP7n0jFYvgAf#-R~^DrAm4&+e{dC}KagA_Q|i+$V-zX3C8 za0>$h(WhVL4u5X8_fPh|gfpA4P{4~(d%dAg5&r4^0^Urq?14B4lj@aFhLO-@_axB=){SBD4qKD3hlDycb{isxxG=ThB&7SS|<+M>Btk2Cgt!+-)#3lZ+J0w zsYQFAK-|#7Z#>8YyR~!8Ri*C1RiXV|{L}E=-G3FnbNuJy`_u(nu)iMf`n|cJ3A-## z!d4dS{`W@4cgMp*ZoBt)hZDKO(}p8L;txo>jRVR2KS#i9@Mij^JGe(*t`!->SA`-&Tak!O0vfHC zk={Q6DE@9T{dcBcNTwGv{Y)}_Gt+-frWY~&02&JN<`;+3hj*iKwj&z97X@N(zLd(L z&kUjaa={l2X54Q!s-Sb~Sef{tOC{_YE1NR*%u_ghXD#V{mdFS4ZC2NW544)TX} z@V7D_#rq?9RY4Pqtq{euKRNW-jheKdik1Xvbu(yWBSSYQGHZ|-n$68-xx6pS6f zRFW(r#;=HQa?o-EXYdwx2D&M8!E|4w`dgs?18PCeYf%sJcz<5OTX=QoegHZC&BpOR z%2J2rHHnvU?&nlh^kvOQX5$2R-L}9*I67me;0d?_sK&FaV}6ukeZa~De$O)<(X-Jj z0gwMCNgwiykPj3f;Sy%9Okq%tKdpZ!qhRbLlu0Qe^sjMDHs14>I^LCq_P&5ZB5ot} zJ3LVizV6I>lX;2fTe5yfsA4x{TH!G{5ISb$p~MqvT|5l$h-H7d0a@6G2@CLR)=pLz z`kmB&)a%SP7NoTwyd@iQ$Uc@0M7kTin5B3Fv$u)c;4OF`Gm_m$WDY$|X0oLr#3AsD zaf2}fi<<*kR4qBs;tAZ+{7LST16dup*uraG#=@aX4rF98g%G`gd#s5A9&iqt1lG`c zXch+4o2ITyh`>*Fd$ba4yJ*KhORHBCNOql@h)Tf{6c9^=kYCn1vOPoq3vnwq4BX9qD71YiRUTciSa>Z14`_mbj47=gs8^l z;3Z$4s6-sdJ{<@I)u$o1LDnD4`aCFEr6)3dBGWMCk)b=+X^fvfCy0wx>2ZkMEs5M0 zk(*{P)AeluvHk^RQaW7f(-`)H7@t&?DJ&O4IW-;=x_Bcqt|tPAzKzL3nQUQ`uVa}J zER)bz5BxEGn!ea_jef`B6$q)0$Os;}c2K(C?kI$VfPk9Vwl#CU%gNIXSv zn|F!xqUzt`?ogZ=jO^%7kwrB@zsh!>5q%Y1NO&X#zEQ%j0X$ZNu1Lxwt==Wfzw9vi zkI4F`GJi-~KJ^5|fae3vQcv`OQnAms;t4z+I7Wv42Gu3S?*JC#^D)gY`x7ULMSA}e zWl{#3%WUi@hQI$*WG+YMuq=PCVSGk+LKCH*eG6b1$1T3dG+G`Wp1K2VEde;X0L`G} zsrkY=iQd~&%sgjc8tVmuU2+_va1{!3A{v46%CC{x$m^frCw3OH)p(WmYD_=qU+|j@ zf1lttnf?JNJsX%{G``=$lBF1DWBLg7>IHagr+^08znEZT#;MGB0U0UE4gJCUj&$-4 z@JvF__~sosIED;QKR%yO+PN7D_(}7}Ipeq+u^PZv0N_S$d=)pppVPb(S~?p?{5M|d z&MWQwNonUzqQFdiU+K=r*;kASv+|VA?|Pa)V1+XK4PC6#(>caR$1G5REWkZ@kux&% z&ls=B$Y+MtdB4woovV<^1x>=MjddXpY2(W?9z!$I_cR=%o9NNV&=)xDL%XI#-`ETp zHI}e9dZQ=I#?i-_jq`hf%F%uBRdW1NzU0_gayqUSuBYJ&vP)dJg1VAZaRo6Yr{J20 z>yL1Sx|E!ZYad)s!WGXRN>0QT5-B-hX_hI6#2(J{D^qn@~uqg-6ck%=og>VYdc>W(Wp>V_*h>WV8l>Vhjd>P(KdaakR@Idd4! zlMJn&B4`^LfXw9!8nGI@QpfX4ht86egYRnqiVaL3zmcJyME)hr@0OOIkl7i?L(><6 zxk0km27||eHrT*+f0uH^B2T*>2kxRS@AxRS?naV3xE;7T5c z;7T3~$m4DVAQBY(b|VM%xB+Ng46XYxK%;j882c_6PZ#!N0`+guZ=ZxOG#-lhOB75| z#G8@iR35EBU9f}JMt4RlWTh)v=^|Q)79bm*mrS@>cGb9CsWKlYR0gA%%FvxS#T@!A z9xw%OK87ljBY`kP>zhQWz68Kzp(|cNF*KFkr6j9MNyjcFo%~qutF~5)ywT91al)r> z9Lc-Be9#DTyOTbSh{M0o4I3i-RjyqNp zJbk#H{EqGgo{r@@1%sYE^;SG>5ZNhonRV{e>VJ^cp(x{yS6y`?>z^PAz75&2FQEl; zeJRg>JZ%B_`3B;@cMzvs=w0G$x783Fy21+>uyi5(-O> zH-Ztp!x~l|&z`=#R`D-+ro}!wMmW=gG!;faSP7hwTA03d(X&98(yUunXF*0d0Lh&t zkHAA1D$5-y$?{-c0T(4%X5)I^cZl``RT$3zdp2cdk!CKxFT#esoi{3b0YOggM<4QY zX*mkSY{X}AUT9$S8pZ|fog|@7knlFxK`c}rw1-zt)%ctU{S=#=7^5tJD0aExot3_2 z>@inidrt;ltcIzYloi_1!Roga$2h~cz)5?07``rs;Y-nB50O>=xDTd%dqav|!s2AC z!!lzUI)Wz&LyvIwNOS-QE45ogtsOj}k30Cgz#wGd?Nx8T^}%u@hzFE2@HSNkU&crD zwigFh1#iZ1|2z0_@pM=fxJ>q!&=1K^nv%G9GDy$ZU!b2qF#ccQ6XN@YK~5V5YQ)(AJM`Ym=sODJt`MGZ4+j1H6FiHU4V4vA#LR~o?Kmkl z|BZS*<_lkxaTX|vWPfocp8~W6PKhlSomz))425tl_Kr5?is$XO15lV7h?|X8hNo~$ z27Ye6 zX3Fs_isMnp2gjX-hps{YQ!;~~3xC9Yyr=MQ`13-1$qG5gT17d5-LW?w#-P#q1B-cS zBizuvSPbXm35d{FJT2RT@$-ZMH)$MxxC}9W9^K8M#E~PX#c+1@Z0vWez&7co+G31| zsYr_67b9XvTZ4GOh0HUMSt>FyT-qvTlC46pq((`Q0%Q_h!ZCKJKOv?NRLeei14^O` z@E`^6c`au>jA!^4IHGe4pG0A6u#54TV8J#}Cg+%1{KiuDde+2nwLsh&um!@gs|$3f zUq)0vlvqsOgw6sfUWG!TZx}+A|m91FwUUhR@#TebHrE0F@wXi1`EF$&` zCxZt?Ji4Z>?U~>&mto4=7j5AT4*fm>G`@X3r$Iiff>lvJKeeb2o&p7)CP@ROp7vmKaPJZjkT_Jj;L~x~q3)ug zf)d3~Og3PJkAEsP$EpiOaaWe*3711ZdfPC#=*vPD>4BSk-gL(e4Qg38+_-qt6*ql& z(*-x!%h*`f883-J|z|kkIFd0i@o0e zH{wj#jbfWTRoddOEubx{IobiIds?QoR&4prmX$SM3wK;3e`d#Q&FD9waXpsyJQ=)jVqRc1 z?yq!TU0Qf`zrZ7&;J4T#Rt#=%muc27r1sDX;}KjR`ePOBr)#2rgdfLuJ1Xj$>}IrR z)^T$cc(7lLU5Gl#_KSHi#3mRz%FGINfU0bjq}(qVhPW|n#a+J zs6RUe^s^cdC@O}*|3v;)hIBZvXQf#97Q$AjF_;gwF$VFw1JcDzSdFB*i22JiVa+bl z=%60|MS586=;>L0?@`mUS<*AN&woumtakMD zZ5S1Kp#x_xSRr*l9CnKcAP)aQ`^0>#72%F5y!XW|Ak?clu&mktoC=cpOI0qYFYJGE ziK+Y=&4}htAWSkuM`{G@BIXbHcM&Vc-pH5EeBo|GLf@Rda5O#cjICr5jah6gj4e6X zHd7AE-9T|-1CCiY7sHZ63hXcVr(l6xlVWx3Mt=NRwvZpkPga1V*kBsNq_$T8eUh;@NJu-ec0h1-n1`lZ!+E@!Ac0hYat&RUi6R+3vF8VEn z&t60LDq!7DAU^PF-aa0>7^%+ng|oUaY!EsK*m=~?rao527pi0DT|*gk5kCX9t*v1x zD$7_;ZKY67)w4er@?h8x$J(SS=P|x8rmEE5ei)pdK&)CfYZFl&>2I=+m!qB34on$n z)XS^lYf|JZ^s_q0ZYCQTZ(YF3!l@AptaTg0K4#Ze3bwSqF1uQFEq*>ou_e{@niO^Y zqv$ho164glYo8D-$9Kw(m6up=R2T4A7S%X8AKCk{f_=Fa9IExKNJmrB_?_F0Nb2?O zOI$19H}3Ys)!m1n1$IcuTNxh@cNBB|V#oSHJKN{xxg*H&P{%?ac?}2${&kJ-!1);@(taTlM}$t8rJ0E z4Fa6$Z`tP-`ua8dG7#h4vUmr>34%}j)LYzU`{Cp8v-fJ(9o8+D2tI(&pzL4VwSWF! zk^tYdINaxX1=LrSj2Sh>$Q`d1bF8{shS($sqqnmaOxOPUz8Y6z)eWTXwdwJajC;xa$zxUp_AfqfY^6&c> zFW%Xs;>+p1_Jp3k@1wGRg#I=ydwcBTZN4`=jYF>}yS4I_kB&X};dgfSJbuHokB=&T zc6FDmyFPFG@YT~A|NiUG|KJIAeWUllSr4}k8T6a@(m^lxy#F}aerfOTzqse#fursl z{Lzyyx<0t4z>%NtNT2OUZ#Yo!;+VDDde(nwEZ=hRmJ2<*%QJVL^K#GM%rbH|E&1}n zy!ri`Pu;(*=bF(n|Fz}4AKevxan$npEkD|M%b=xQH~sbI)=3L|cin#W19>^2k{MI} z_}&SRgT6tMzHuX)Tc7!6ThIHu$@G$$`)?hP_e#&FeqsbS4O_aT;PG8n&9_Zue?I-I z%7a;5U!9nH=hNpsxU2MMEB<)S;w6i2+k0-elY=iG@GSS<(K7qQs)yhG^YDhZ&nO)A z%WrRbwfQ& zo^AIHxZ=;*e|+bXxqlil_R)UFd^PT*i*jCmedJq94uapIlE0tNoz(e~IjCPG>;KG= zere7l$F|IWrRT~UjE9Y<=j?ponB9Lr@69EiyZ2{3^t1D;{#5^1^M$?dxOLRS&zAq> z*l&l-DBP5P!`;7srRV(@O8i5-eK!2zt+z(}Va;=0-~4s2p>0>*bp0FA>alCy4E!a( z?+d*KF1#ss$EzqmLYDta!SX&OkDplnho)|w&phS98=t(T`s@=&A-_=O8;^bMTRrm+ zz6W+acJU<@nfEVQ^v;m82BCk#QXbEH>UT`oH-GHUBGZT6vFNdWr)|B;ee<_3_xx#w zpSE%IAsx|9f2nz{LYcJ}`b+-U(x`UwY%E=f2Q&4a$#| z_-foY|LNBE9~|?$vb9a`-~3ebX%2jA7y1h~%}U~LcBFq>aMI*!Q|KG>%%Tn*U;jzT z%JV;6efFxFhfceHOSZXb+o(IP8T#HiC10Q7y0|VpWXWY#!*df(95ZV1$#*;SW9oY! zhPVEFThE8P8?DPa47hVK_EhrG9RIUm^NPhf{BQ z`JM7pS%y#c`6<@v4l?c1X%dCc9y;OpaNDQ&sQgTYuOTAA9aOqPrxpHuomTkw+^Nbd z{4F}I@UPTq5m+;f2A%FI>$QYc{ciGF{0o)tF0V~I)&rjoSTXXmQkU;6)73iNL#8Y4 zkk<|>Je{x8T_jxdpQZR)&fSXmWGVh~RrxH!Bik#qVl~&{gm#VZPC%8hTmAHdjRl1XGI{2sAafp4>EK$KeoUqJte5HaD!o#rUr}l9yyd4= zr5jXwmr4g!`hZGTt8~Y!Bpp*!x|d2%6~}zW6|3~=DxI&=7pQbgv*f2jrJGdxYL#B8($}l> za+SVCrK?r?PL*y@>EEezP^BMMY1dZS-Wrw8Rq5wcde0V_|C&l0Du0hkx2pWyYcga( z^?jxzotD|2XqP~{1llFgE`fFlv`e5}0__rLmq5D&+9l8~fp!VBOQ2l>?Gk90K)VFm zCD1N`b_ujgpj`s(5@?q|y9C-L&@O>?3A9U~T>|YAXqP~{1llFgE`fFlv`e5}0__rL zmq5D&+9l8~fp!VBOQ2l>?Gk90K)VFmCD1N`b_ujgpj`s(5@?q|y9C-L&@O>?3A9U~ zT>}3bC6I--)EO}U8(VE}>i-rAu>Vs(Umt9n$Fw@v$I$1`7}fIp41NBLF-4};`9g+1 zT|=EOWTf>N-K~G9iQLp{$KO=#q>bxG^ z5BeN3{_-n78m`YHTj7A~bIDda;QD;7%?`NnQ;B!G1Fp|2i#p)?+_L=+xIVuuXQRZY z>D1?#@%MH4(Qtj9S)K!~&owJ_!1eiNmIJQOIjeNQ^?7GA9dLc_S*-)E&p%u0fa`P6 zmO0@1JhT-KxIPzcwF91zzXPt%N!#v#>+{m04!Fj%-wC&seB}Z0NjWQc9|v5YrvPpC2V9@0R_TE2bJb=#;QD;ES_fR8v$oU$*XOM*bHMevYbzXZeg4{N2V9@Sw%GyK z=do>f!1cLoQ3qU~&$izI*XOk5Y*zA?7ek-d*2e+Y=eFfJ;QIWwLI+%*<7PSF`aHKv z2V9@)Hq!yu=eyN9;QE}mr4G10?`@d_uFrj2;ehM&-&Q-|`W(2;4!AxKZo31n&xMOR z;QD;H{SLT3CoV^wBbB4%ug{C?459= zwx6A?8=gzHg!1eias~vEC4&7!4T%Skha@cKso~FKPdFpdCEk}8M zu4aV;uFu)5JOaE}odc%v>vQTX2V9?9SK)vg3U8$YuFtib;(!O$IcYN;aDDDwwFACK zojX_Sfa`Pdf)4mfReq@huFuVDaKQCBddnPeeXibe2fRVyU*Ul3bN5y{;QAcC)eg8m zm#@hIuU7asJK*}-zb>8Is@ z>vIb$9Pk!ZzS04otLT~HfH!IVcfj?zht&@FN>#qr0l!P(4?5uboW!LLxIQvJVt4tTyQ zpXY$vJ-fI^g=;%mxQspQE|V0oUhhE_c9P$_}n@!1cMCD;@A$RerSt zuFvIca=`UDotqtSeQsxq18yk%+a2)4xt3VKFFVn6g zz-8|LKi_3W4gTig{{q8hj5KB#exn@Ua|CP=?xz~n$f?40mN6SS(~-(E<|B0zQZ8ef zF%#Em#tlY4qz2)?FR*ntx(Feq79x;{YC)o&OQn{ z$Du51+yHKBK<9PfcAha8seGdr{LRMqpp<(T^5-LWE=u`v4InkuxEAH4gjjb0?&l&` zVj>+bNI5BKDY5LcT*i@GUm)n8g+7q-n=X3o@H9z$hm)7f@PW>$z{1}2f{Ll&kd#&k zUGR$@nkOhsmp*>W3MHKS;xa~nEAl$dSOm`5vvZCLn-kwy=o+QuGA;uRQkJE_%|2lN zQBM~k*Gbip@r*{z8ep6uxY2Y{N7-LLgh%REl0PpnRe=ijk=Flo9O?2oRNplPNlgE) z{*l{HujSY_*CIzj>X;mC@K4(w z8ld$4C_O5y|Bmi-eCcxbIHWX2X)v{57Nklol-4sjVh4hLYWIyo=6yx}wV;w?Xg(~J z<|Vz|%aF@HaT)0pXze;wvuRh-%cj}@>T>{co+etIkDdz%c_%f*g|^c9<9w0igL4A= zD3wpylSk%>{X@KZED@vpqlFFNJ}C*}*U)OTM0=GgpLE{-9i>vD>E$W68sMWg4Oh}n z@|SN6!AOwxMxsxvK{4kC&L{I#zv&rI<|pfNrqF#ze!CbKi9bq{8Z9MX4V<)X>~;3) zNZfPYohh`RmZ}{2wAZ|s7$jB=r$rkgq{gJ=UGgsD$A?Hm!m3fv4}G7R z$d_3@lpMW~qU1};@-pang+rRDa!4OP z+LkzN1o@}}UOA8SJ8FD-c1ZUB507Z}j-f`T^xSAAwdaT)YlytNGA&K_?t6G)^O{xOji=acaoAbdr{kd}l zM$R#QXgV#{S;B(Q@0^MrmvEN60g$Ah#Im$B>^thkeBt9yH&QWapI72xZRr=DmYB83 zlcaz$9gF!VU3TO=U4ElLJ$)0>MK2&-o#8b?>Aji%H*=@|}&r6H%J zhoiC-(zq71TnC6xNQ!rQq>crra-15l5S$&RL(YfZaW2!)3levIVIK@5Z*E}r?0&eS zRXNupflte+ZEZ%M^#2A0+k}eMt}5UU!zjZNQ0n@=fc8HMIZGCd@8=VV&w{m)|=X2hQ8s9OnWT#aT>Hz zuHo2g95)w;23WI$`01|vf+6ReJM_Hs3yVC%OG?W|AR{P>B)(jpBlUf0L0el^KgYj< zO~lq^BK1%j3WjF+L}Zix8S z6w+_dvjk`BxxkT1EBVwE(-%I{z2tyBKtEqfkg=y}NNX!wi@SlyosKKZdtrIUCR(c$ zFnW>KCSb|-B)wj6a}GSob44rpNSz0HXkIw$>JR`fz+(lV|C^tI=nmRU;Wh+4AtVB%437>J891m*H$Ie5` zWAM*;@LVC^WR8?+C8#X{Wi@GZ$he@4KFapA#GQRg+89riGSFj#a*$M~YGZ0HrIZ}~ z3k8N5sKfC#Q%MBBZm;-o!pqSM*Ck4k+UekS>LK-qqle}n0!_!Jby{+07#Cg$n|484 z+f(>|8~@+oe<1!XV<9M(E%U0|G>nP3pDQ$jvZAk|{SB?#7RI}lV>v9=Wr>l<)s!ri zT4p3@rtKg$$}%-i>UO?>_eEW{b#&j_Kj*BLm4`x!%t|ej=(3V^W2xsABX|Aa_ z-YYPM&I2C?Xe&XARfD4kn|sF3?1cWc-Tr1bqn7Iv4x~#LSai?ThN@5w88X8Vtam zR*4!ev@fHKoMTeq<3WdvLNa#Cl`>}}xenE_AzBjpv3fX9!7- zMSENU&jT%t#P2;$ZJlBIJ(qVtVcfz>`!mn!#2$UQ+c~K1<*n z!srhBQ}Z-Jv`rf+N2HEZxdew?p(M*|Y8g?Xys1aT!)QBWDzr*$fjUGhOX|rPsn+e$ zKC}NQAGXO>XCuv36C;ycVWpSw2uqx-;e|xXp#|wu9}nMS{2|aoqwY@alKRDYf~zB1 zD9%pu=>o?-V^=Z?D~QDxt@|`^C3~3^a|U31ZknKfqA1TtmaL-D7MuR}{8TKqfaz=OFF;*&9 zy}DE?MCTmo$}uQy#YoXITP+hF=0s=&t*E?`Z(1B`oV0R!PNkKWPqpM2uSH2}BgZ@M zrDdZE-Jmo}fHN#C<(IdqHl%N+#mz*KUjqIZ#UaIdwK4{nW+7bx zNl`8<0hs$%XNX7Ge;z@(71f(Jj z@-!gF;K!+xoNfZ7dU7hBHvw6h2Kg9}mNdv-KpL)0t(6I;SEfOF0Me2MISG(GX^?(^ z)hKX^{DV6sJLM1!PJZG;VY-rKJ|_eLxTI!&2Z$>H8CN!Uffs{6Hwmez zndb*+PiI|gT=~>$mSG4`;u+_eJ!|TG@ATG08RQzrA~UydYU}*~X~K`BC+RsKkZPBP zlm_Nhb0oM*HH3IPD6{-z4Y{WD@-d!rFV$X*Gs$~7%IvApWtcY^kSQ3MvMn_TjA|@* zlC94~nIMK{5>f+5b+1&&5luY%DE^A8gPCVzKOv??r3?U-{ zF;3GEDP=&?I$bf&v!qiAZXtE*>Drn;)As-B&RS!|zFh%#W1hzf%bB5MNspa}Yqc`ym$%LHFc zKz0xeKItGxR0#O}=hQi;{+Vo=UFiDizu(Sfx=eEoG*yQD3dpfRWGHI6Q++^l!P5u{ zboTGjh}%*%e$1l!GUV(rFaAKt9hwUUIyZnEF`9n>=`uO*0GYd7=KApa!DTwG6(v*J zRUF|W!1{g$H1t7w0xL9Q_0onUZndS)~>#V|uK#m_Q(@cO= zupzPbJOgAOhZc)`AIK~zBR>T)XCx!e6lQ17#F_lLMl@I+t(@NiNwB_KzgEp zdGQ<}5MXuv7)a$VjRa%iPCOA+KcW%R{vv4B<1%spWFJ>LN{-u8UC{lkMyOLTdYxlq zj|*2PPv0E8bTbG6!V#Y?Wkp&UM$_2qDa#%Y;Cbl_P-c8N?0pU%`g=mZ% zd~sd}a>%&;3gkMY`6rMUm>0KTFF$9ZL2~W@vd_3a2LwNWD%o@K6tVYV^UFZ2 z_IeLb_#~aOu0znQGs%A;E+*$cK#rI_XK*H{GMWd0SY4#&OQ(>>fP9kK{B z5*O3;6(ChcvkT;y+59|^Lx%hYh+~k|VuoKg+DYr{B$?U#7LawO{eu-`htb>vWRJ@4<} zoGOqRtCmNJhS~fzAPGaBCK{t_hNqr44sd9lc*)sw4)%+=@ZDh?=dOsxF)k&s2bc!^ z7*{uE6Kp0eLfeICLxXNJ*zS7y=0Y@TMGzDDfupf{mf@dy>;xm>(alrr$Du(NMHr9( z12meeD;@LHqgvAf2ehkg^e*=sm(E@|Z{Sum7LllX(a1o9RG9A_&##R}sd;Wav|_#w zvZ48Y?))Pc%)_D>rq+|5pILde+)GMNe(9kRV|yHjVPLf(#<*KEa=X6orPj`_ZzV0q zqhJtiMe&u$kgo;ND7L~bfw!58?ZDBODXs4I#j_$y=^j~!iLmm+1?O=RN;|F0>1_sK z*oyKX9eaLY($R`UF0k!;CTNRcH}vvxoK75u;;>&_aXGcj7oynfHsT2iBp~VTIj1is zs2WuxPC*VBLYIPk%}a$h4DoZB-liDmgNqlOK_q%u^JeIbvMi(%~L#_%<(sT1U0$IJaqn3{P0mNikvL;8*!+Xo5+o?02*trZXWC1f5q z17A$M5K%Eq2uoXQP=A^03yTkwjPXLRB6^u@3QC5o4J< z{a_O;Ep?aLEA?92U0Gc0E%e=ft=?X69hah^3r=a5=}^(5qfD)cJQ+*GI?QzOOM*>c zo6{r|l%rA@dl){`%#dhVQjR5)?@I-#3ul@nF{Xm9NV*oNB?;QbvO4ywnNgZPqLU9x z4`SL#11h6pR4vE381JegYn#FaSudaBe~POSI=z`(YCfkZQptH)9m|zyiPO0|EAB2gzNT@b)(?GQ; z$wBAD=`IaOJdhO0kMAo;;VL|l%2b@?Ar-Go>`t5SbfzJV6>54=!mA)fSNA;;u8b)# zE9nSB0I$kzSKn=lY^x_WA`~K~KIt%ns2>~8PsN=kHexE7lPdh_ov8lnOthz~H z<+&S`hmP>KRA|VU)!;pIfx$YUVT@T=w1H7&^RgC)(@D_wh5?2@QY3v0aYikt5t39E zv5t+!#x%vQAddQSWRoZx6=!2%EINsqetY`Y2Q`(D^EN&$i3Ulis8X;JQoF36=-OO;4%m?7@8BH{0~B@3;bMQ))`~d7^0w?v zf`^!F_@kd?jCJ=&#eKi@*7~>Vi7e=M+j>o~rQH*g1RJpvcDin}zq0BsEH_;&11@c| zTrW+%UEBjwW*5ak7#Z3cl}@|*q3ud(poHc_SyPG-Krzc3*g46HY?s z$v53gy*y4$bvv?58yFR;kyJ_3cz(B9#~?ZZtISYA%A31|9c`=3`da^7wb~7*nXz2a z+s@bIhQJy;$OSq_1lcJXDb{c-h4Ih}wRaMB-3C=gyVdKXG{~n*UbcBkjx~d(ju9yP ztTpjA_b zwiM)4IrxlzFuLhDA-}>|3F+EYHLwvGVof|nQ=ewN1Pkz3`0uf{P_i_uvTrgJaVchMa(gL;EXFi% z2V`dPirybdZZ?#)Tv=gKPIEWHQ1?MK!2yQ7=LvX^S9JV2l^x>N(c%+N#$5Vzo(C5z zD(#}`SZ>j4d~L0byae9LUeu<@?S@(lSA&ek7_Amkyj&oPv8c(V)08Uo3ngDo-p(*J z{Z{H`TX~Ef@s-V99D&w0yhi9{CI?bE?axk5dDaECW0z7d*%W?;)f;~mW147DyvY{8 vdPq?RFJpw|bS9OdbxQ84awg|7MPqru#>y&B@XA@7R}kR{g-@q$Ez|iw t.CUInt64T: + return syscall._syscall1(t.CUInt64T(syscall.LOAD_SO), t.CUInt64T(path)) + +def so_call1(sym_name: t.CConst | t.CChar | t.CPtr, arg1: t.CVoid | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall2(t.CUInt64T(syscall.SO_CALL1), t.CUInt64T(sym_name), t.CUInt64T(arg1))) diff --git a/vpsdk/fs.py b/vpsdk/fs.py new file mode 100644 index 0000000..3ad03bd --- /dev/null +++ b/vpsdk/fs.py @@ -0,0 +1,141 @@ +import t, c +from . import syscall as syscall + +O_RDONLY: t.CDefine = 0x01 +O_WRONLY: t.CDefine = 0x02 +O_RDWR: t.CDefine = 0x03 +O_CREAT: t.CDefine = 0x04 +O_TRUNC: t.CDefine = 0x08 +O_APPEND: t.CDefine = 0x30 + +SEEK_SET: t.CDefine = 0 +SEEK_CUR: t.CDefine = 1 +SEEK_END: t.CDefine = 2 + +DT_UNKNOWN: t.CDefine = 0 +DT_FILE: t.CDefine = 1 +DT_DIR: t.CDefine = 2 + +def open(path: t.CConst | t.CChar | t.CPtr, flags: t.CUInt32T) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.OPEN), t.CUInt64T(path), t.CUInt64T(flags), 0)) + +def close(fd: t.CInt) -> t.CInt: + return t.CInt(syscall._syscall1(t.CUInt64T(syscall.CLOSE), t.CUInt64T(fd))) + +def read(fd: t.CInt, buf: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.READ), t.CUInt64T(fd), t.CUInt64T(buf), t.CUInt64T(count))) + +def write(fd: t.CInt, buf: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.WRITE), t.CUInt64T(fd), t.CUInt64T(buf), t.CUInt64T(count))) + +def seek(fd: t.CInt, offset: t.CUInt32T) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.SEEK), t.CUInt64T(fd), t.CUInt64T(offset), 0)) + +def tell(fd: t.CInt) -> t.CUInt32T: + return t.CUInt32T(syscall._syscall1(t.CUInt64T(syscall.TELL), t.CUInt64T(fd))) + +def fsize(fd: t.CInt) -> t.CUInt32T: + return t.CUInt32T(syscall._syscall1(t.CUInt64T(syscall.SIZE), t.CUInt64T(fd))) + +def mkdir(path: t.CConst | t.CChar | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.MKDIR), t.CUInt64T(path), 0, 0)) + +def remove(path: t.CConst | t.CChar | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.REMOVE), t.CUInt64T(path), 0, 0)) + +def opendir(path: t.CConst | t.CChar | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.OPENDIR), t.CUInt64T(path), 0, 0)) + +def readdir(dd: t.CInt, info: t.CVoid | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.READDIR), t.CUInt64T(dd), t.CUInt64T(info), 0)) + +def closedir(dd: t.CInt) -> t.CInt: + return t.CInt(syscall._syscall1(t.CUInt64T(syscall.CLOSEDIR), t.CUInt64T(dd))) + +def stat(path: t.CConst | t.CChar | t.CPtr, info: t.CVoid | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.STAT), t.CUInt64T(path), t.CUInt64T(info), 0)) + +@t.Object +class FileIO: + _fd: t.CInt + _pos: t.CUInt32T + _size: t.CUInt32T + _flags: t.CUInt32T + _closed: t.CInt + + def __init__(self, fd: t.CInt, flags: t.CUInt32T): + self._fd = fd + self._flags = flags + self._pos = 0 + self._size = fsize(fd) + self._closed = 0 + + def read(self, buf: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt: + if self._closed: return -1 + n: t.CInt = read(self._fd, buf, count) + if n > 0: + self._pos = self._pos + t.CUInt32T(n) + return n + + def write(self, buf: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt: + if self._closed: return -1 + n: t.CInt = write(self._fd, buf, count) + if n > 0: + self._pos = self._pos + t.CUInt32T(n) + if self._pos > self._size: + self._size = self._pos + return n + + def seek(self, offset: t.CUInt32T) -> t.CInt: + if self._closed: return -1 + res: t.CInt = seek(self._fd, offset) + if res == 0: + self._pos = offset + return res + + def tell(self) -> t.CUInt32T: + return self._pos + + def size(self) -> t.CUInt32T: + return self._size + + def close(self) -> t.CInt: + if self._closed: return 0 + self._closed = 1 + return close(self._fd) + + def is_closed(self) -> t.CInt: + return self._closed + +@t.Object +class DirIO: + _dd: t.CInt + _closed: t.CInt + + def __init__(self, dd: t.CInt): + self._dd = dd + self._closed = 0 + + def read(self, info: t.CVoid | t.CPtr) -> t.CInt: + if self._closed: return -1 + return readdir(self._dd, info) + + def close(self) -> t.CInt: + if self._closed: return 0 + self._closed = 1 + return closedir(self._dd) + + def is_closed(self) -> t.CInt: + return self._closed + +def open_file(path: t.CConst | t.CChar | t.CPtr, flags: t.CUInt32T) -> FileIO | t.CPtr: + fd: t.CInt = open(path, flags) + if fd < 0: + return None + return FileIO(fd, flags) + +def open_dir(path: t.CConst | t.CChar | t.CPtr) -> DirIO | t.CPtr: + dd: t.CInt = opendir(path) + if dd < 0: + return None + return DirIO(dd) diff --git a/vpsdk/process.py b/vpsdk/process.py new file mode 100644 index 0000000..3ac7755 --- /dev/null +++ b/vpsdk/process.py @@ -0,0 +1,65 @@ +import t, c +from . import syscall as syscall + +def exit(code: t.CInt) -> t.CVoid: + syscall._syscall1(t.CUInt64T(syscall.EXIT), t.CUInt64T(code)) + +def yield_cpu() -> t.CVoid: + syscall._syscall2(t.CUInt64T(syscall.WAIT_WINDOW), t.CUInt64T(0), t.CUInt64T(2)) + +def spawn(path: t.CConst | t.CChar | t.CPtr, blocking: t.CInt = 0) -> t.CInt: + return t.CInt(syscall._syscall2(t.CUInt64T(syscall.SPAWN), t.CUInt64T(path), t.CUInt64T(blocking))) + +def thread_create(func: t.CVoid | t.CPtr, arg: t.CVoid | t.CPtr = None) -> t.CInt: + return t.CInt(syscall._syscall2(t.CUInt64T(syscall.THREAD_CREATE), t.CUInt64T(func), t.CUInt64T(arg))) + +@t.Object +class Thread: + _tid: t.CInt + _func: t.CVoid | t.CPtr + _arg: t.CVoid | t.CPtr + _running: t.CInt + + def __init__(self, func: t.CVoid | t.CPtr, arg: t.CVoid | t.CPtr = None): + self._func = func + self._arg = arg + self._tid = -1 + self._running = 0 + + def start(self) -> t.CInt: + if self._running: return -1 + self._tid = thread_create(self._func, self._arg) + if self._tid < 0: return -1 + self._running = 1 + return 0 + + def is_alive(self) -> t.CInt: + return self._running + + def get_tid(self) -> t.CInt: + return self._tid + +@t.Object +class Process: + _pid: t.CInt + _path: t.CArray[t.CChar, 128] + + def __init__(self, path: t.CConst | t.CChar | t.CPtr): + self._pid = -1 + i: t.CInt = 0 + for ch in path: + if i >= 127: break + self._path[i] = ch + i += 1 + self._path[i] = 0 + + def start(self) -> t.CInt: + self._pid = spawn(c.Addr(self._path[0]), 0) + return self._pid + + def start_blocking(self) -> t.CInt: + self._pid = spawn(c.Addr(self._path[0]), 1) + return self._pid + + def get_pid(self) -> t.CInt: + return self._pid diff --git a/vpsdk/stdlib.py b/vpsdk/stdlib.py new file mode 100644 index 0000000..397df55 --- /dev/null +++ b/vpsdk/stdlib.py @@ -0,0 +1,59 @@ +import vpsdk.syscall as _sc +import string +import t, c + +HEAP_BLOCK_MAGIC: t.CDefine = 0x48454150 + +@t.Object +class _heap_hdr: + magic: t.CUInt32T + size: t.CUInt32T + +_heap_inited: t.CInt = 0 + +def _heap_init(): + global _heap_inited + _heap_inited = 1 + +def malloc(size: t.CSizeT) -> t.CVoid | t.CPtr | t.CExport: + global _heap_inited + if size == 0: return None + if _heap_inited == 0: + _heap_init() + hdr_size: t.CSizeT = _heap_hdr.__sizeof__() + total: t.CSizeT = hdr_size + size + ptr: t.CUInt64T = _sc._syscall1(t.CUInt64T(_sc.SYSCALL_MMAP), t.CUInt64T(total)) + if ptr == 0: return None + hdr: _heap_hdr | t.CPtr = t.CVoid(ptr, t.CPtr) + hdr.magic = HEAP_BLOCK_MAGIC + hdr.size = t.CUInt32T(size) + return t.CVoid(ptr + hdr_size, t.CPtr) + +def free(ptr: t.CVoid | t.CPtr) -> t.CExport: + if ptr is None: return + hdr_size: t.CSizeT = _heap_hdr.__sizeof__() + hdr: _heap_hdr | t.CPtr = t.CVoid(t.CUInt64T(ptr) - hdr_size, t.CPtr) + if hdr.magic != HEAP_BLOCK_MAGIC: return + +def calloc(nmemb: t.CSizeT, size: t.CSizeT) -> t.CVoid | t.CPtr | t.CExport: + total: t.CSizeT = nmemb * size + p: t.CVoid | t.CPtr = malloc(total) + if p: + string.memset(p, 0, total) + return p + +def realloc(ptr: t.CVoid | t.CPtr, size: t.CSizeT) -> t.CVoid | t.CPtr | t.CExport: + if ptr is None: return malloc(size) + if size == 0: + free(ptr) + return None + hdr_size: t.CSizeT = _heap_hdr.__sizeof__() + hdr: _heap_hdr | t.CPtr = t.CVoid(t.CUInt64T(ptr) - hdr_size, t.CPtr) + if hdr.magic != HEAP_BLOCK_MAGIC: return None + old_size: t.CSizeT = hdr.size + new_ptr: t.CVoid | t.CPtr = malloc(size) + if new_ptr is None: return None + copy_size: t.CSizeT = old_size if old_size < size else size + string.memcpy(new_ptr, ptr, copy_size) + free(ptr) + return new_ptr diff --git a/vpsdk/syscall.py b/vpsdk/syscall.py new file mode 100644 index 0000000..a2e48ac --- /dev/null +++ b/vpsdk/syscall.py @@ -0,0 +1,114 @@ +import t, c + +CREATE_WINDOW: t.CDefine = 1 +DRAW_TEXT: t.CDefine = 2 +EXIT: t.CDefine = 3 +OPEN: t.CDefine = 4 +CLOSE: t.CDefine = 5 +READ: t.CDefine = 6 +WRITE: t.CDefine = 7 +SEEK: t.CDefine = 8 +TELL: t.CDefine = 9 +SIZE: t.CDefine = 10 +MKDIR: t.CDefine = 11 +REMOVE: t.CDefine = 12 +OPENDIR: t.CDefine = 13 +READDIR: t.CDefine = 14 +CLOSEDIR: t.CDefine = 15 +STAT: t.CDefine = 16 +WAIT_WINDOW: t.CDefine = 17 +SERIAL_PUTS: t.CDefine = 18 +LOAD_SO: t.CDefine = 19 +SO_CALL1: t.CDefine = 20 +GET_WINBUF: t.CDefine = 21 +WIN_FLUSH: t.CDefine = 22 +POLL_EVENT: t.CDefine = 23 +SET_CURSOR: t.CDefine = 24 + +YIELD: t.CDefine = 25 +SET_TITLE: t.CDefine = 26 +SET_GEOMETRY: t.CDefine = 27 +SPAWN: t.CDefine = 28 +MMAP: t.CDefine = 29 +MUNMAP: t.CDefine = 30 +SET_RESIZABLE: t.CDefine = 31 +GET_WIN_SIZE: t.CDefine = 32 +THREAD_CREATE: t.CDefine = 33 + +def _syscall0(num: t.CUInt64T) -> t.CUInt64T: + ret: t.CUInt64T + c.Asm(f"""mov rax, {c.AsmInp(num, t.ASM_DESCR.REG_ANY)} + syscall""", + out=[c.AsmOut(ret, t.ASM_DESCR.OUTPUT_REG)], + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_R11]) + return ret + +def _syscall1(num: t.CUInt64T, a1: t.CUInt64T) -> t.CUInt64T: + ret: t.CUInt64T + c.Asm(f"""mov rdi, {c.AsmInp(a1, t.ASM_DESCR.REG_ANY)} + mov rax, {c.AsmInp(num, t.ASM_DESCR.REG_ANY)} + syscall""", + out=[c.AsmOut(ret, t.ASM_DESCR.OUTPUT_REG)], + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RDI]) + return ret + +def _syscall2(num: t.CUInt64T, a1: t.CUInt64T, a2: t.CUInt64T) -> t.CUInt64T: + ret: t.CUInt64T + c.Asm(f"""mov rdi, {c.AsmInp(a1, t.ASM_DESCR.REG_ANY)} + mov rsi, {c.AsmInp(a2, t.ASM_DESCR.REG_ANY)} + mov rax, {c.AsmInp(num, t.ASM_DESCR.REG_ANY)} + syscall""", + out=[c.AsmOut(ret, t.ASM_DESCR.OUTPUT_REG)], + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_RSI]) + return ret + +def _syscall3(num: t.CUInt64T, a1: t.CUInt64T, a2: t.CUInt64T, a3: t.CUInt64T) -> t.CUInt64T: + ret: t.CUInt64T + c.Asm(f"""mov rdi, {c.AsmInp(a1, t.ASM_DESCR.REG_ANY)} + mov rsi, {c.AsmInp(a2, t.ASM_DESCR.REG_ANY)} + mov rdx, {c.AsmInp(a3, t.ASM_DESCR.REG_ANY)} + mov rax, {c.AsmInp(num, t.ASM_DESCR.REG_ANY)} + syscall""", + out=[c.AsmOut(ret, t.ASM_DESCR.OUTPUT_REG)], + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_RSI, t.ASM_DESCR.CLOBBER_RDX]) + return ret + +def _syscall5(num: t.CUInt64T, a1: t.CUInt64T, a2: t.CUInt64T, a3: t.CUInt64T, a4: t.CUInt64T, a5: t.CUInt64T) -> t.CUInt64T: + ret: t.CUInt64T + c.Asm(f"""mov rdi, {c.AsmInp(a1, t.ASM_DESCR.REG_ANY)} + mov rsi, {c.AsmInp(a2, t.ASM_DESCR.REG_ANY)} + mov rdx, {c.AsmInp(a3, t.ASM_DESCR.REG_ANY)} + mov r10, {c.AsmInp(a4, t.ASM_DESCR.REG_ANY)} + mov r8, {c.AsmInp(a5, t.ASM_DESCR.REG_ANY)} + mov rax, {c.AsmInp(num, t.ASM_DESCR.REG_ANY)} + syscall""", + out=[c.AsmOut(ret, t.ASM_DESCR.OUTPUT_REG)], + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_RSI, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R8]) + return ret + +def _syscall6(num: t.CUInt64T, a1: t.CUInt64T, a2: t.CUInt64T, a3: t.CUInt64T, a4: t.CUInt64T, a5: t.CUInt64T, a6: t.CUInt64T) -> t.CUInt64T: + ret: t.CUInt64T + c.Asm(f"""mov rdi, {c.AsmInp(a1, t.ASM_DESCR.REG_ANY)} + mov rsi, {c.AsmInp(a2, t.ASM_DESCR.REG_ANY)} + mov rdx, {c.AsmInp(a3, t.ASM_DESCR.REG_ANY)} + mov r10, {c.AsmInp(a4, t.ASM_DESCR.REG_ANY)} + mov r8, {c.AsmInp(a5, t.ASM_DESCR.REG_ANY)} + mov r9, {c.AsmInp(a6, t.ASM_DESCR.REG_ANY)} + mov rax, {c.AsmInp(num, t.ASM_DESCR.REG_ANY)} + syscall""", + out=[c.AsmOut(ret, t.ASM_DESCR.OUTPUT_REG)], + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_RSI, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R8, + t.ASM_DESCR.CLOBBER_R9]) + return ret diff --git a/vpsdk/vpui.py b/vpsdk/vpui.py new file mode 100644 index 0000000..f8367a0 --- /dev/null +++ b/vpsdk/vpui.py @@ -0,0 +1,1556 @@ +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.syscall as _syscall +from stdint import * +import t, c +import string + +TITLE_H: t.CDefine = 32 +MAX_WIDGETS: t.CDefine = 24 +CHAR_W: t.CDefine = 8 +CHAR_H: t.CDefine = 16 + +WIDGET_LABEL: t.CDefine = 1 +WIDGET_BUTTON: t.CDefine = 2 +WIDGET_ENTRY: t.CDefine = 3 +WIDGET_TEXT: t.CDefine = 4 +WIDGET_SCROLLBAR: t.CDefine = 5 +WIDGET_FBCANVAS: t.CDefine = 6 + +VERTICAL: t.CDefine = 1 +HORIZONTAL: t.CDefine = 2 +TEXT_MAX_LINES: t.CDefine = 200 + +PACK_TOP: t.CDefine = 1 +PACK_BOTTOM: t.CDefine = 2 +PACK_LEFT: t.CDefine = 3 +PACK_RIGHT: t.CDefine = 4 + +FILL_NONE: t.CDefine = 0 +FILL_X: t.CDefine = 1 +FILL_Y: t.CDefine = 2 +FILL_BOTH: t.CDefine = 3 + +STICKY_NONE: t.CDefine = 0 +STICKY_W: t.CDefine = 1 +STICKY_E: t.CDefine = 2 +STICKY_N: t.CDefine = 4 +STICKY_S: t.CDefine = 8 +STICKY_NSEW: t.CDefine = 15 + +_g_wl_type: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_side: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_fill: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_expand: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_padx: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_pady: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_row: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_col: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_rowspan: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_colspan: t.CArray[t.CInt, MAX_WIDGETS] +_g_wl_sticky: t.CArray[t.CInt, MAX_WIDGETS] +_g_layout_init: t.CInt + +_hex_tbl: t.CArray[t.CChar, 17] = "0123456789ABCDEF" +_hex_buf: t.CArray[t.CChar, 20] = "0000000000000000000" + +def _serial_puts(s: t.CConst | t.CChar | t.CPtr) -> t.CVoid: + _syscall._syscall1(t.CUInt64T(_syscall.SERIAL_PUTS), t.CUInt64T(s)) + +def _ptr_to_hex(val: t.CUInt64T, buf: t.CVoid | t.CPtr) -> t.CVoid: + buf[0] = 48 + buf[1] = 120 + i: t.CInt = 15 + while i >= 0: + nibble: t.CInt = t.CInt(val & t.CUInt64T(0xF)) + buf[2 + i] = _hex_tbl[nibble] + val = val >> 4 + i -= 1 + buf[18] = 0 + +def _fill_rect(fb: UINT32PTR, fw: t.CInt, fh: t.CInt, x1: t.CInt, y1: t.CInt, w: t.CInt, h: t.CInt, color: t.CUInt32T): + if not fb: return + if x1 < 0: w += x1; x1 = 0 + if y1 < 0: h += y1; y1 = 0 + if w <= 0 or h <= 0: return + for py in range(y1, y1 + h): + if py >= 0 and py < fh: + for px in range(x1, x1 + w): + if px >= 0 and px < fw: + fb[py * fw + px] = color + +def _draw_hline(fb: UINT32PTR, fw: t.CInt, fh: t.CInt, x1: t.CInt, y: t.CInt, w: t.CInt, color: t.CUInt32T): + if not fb: return + if y < 0 or y >= fh: return + for px in range(x1, x1 + w): + if px >= 0 and px < fw: + fb[y * fw + px] = color + +def _draw_vline(fb: UINT32PTR, fw: t.CInt, fh: t.CInt, x: t.CInt, y1: t.CInt, h: t.CInt, color: t.CUInt32T): + if not fb: return + if x < 0 or x >= fw: return + for py in range(y1, y1 + h): + if py >= 0 and py < fh: + fb[py * fw + x] = color + +def _lighten(color: t.CUInt32T, amount: t.CInt) -> t.CUInt32T: + r: t.CInt = t.CInt(color & 0xFF) + g: t.CInt = t.CInt((color >> 8) & 0xFF) + b: t.CInt = t.CInt((color >> 16) & 0xFF) + r = r + ((255 - r) * amount) / 256 + g = g + ((255 - g) * amount) / 256 + b = b + ((255 - b) * amount) / 256 + return t.CUInt32T(r | (g << 8) | (b << 16)) + +def _darken(color: t.CUInt32T, amount: t.CInt) -> t.CUInt32T: + r: t.CInt = t.CInt(color & 0xFF) + g: t.CInt = t.CInt((color >> 8) & 0xFF) + b: t.CInt = t.CInt((color >> 16) & 0xFF) + r = r - (r * amount) / 256 + g = g - (g * amount) / 256 + b = b - (b * amount) / 256 + return t.CUInt32T(r | (g << 8) | (b << 16)) + + +font: t.CArray[t.CArray[t.CUInt8T, 16], 256] = [ + {0x00,0x00,0x00,0x00,0x00,0x40,0x55,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x02,0x56,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x42,0x56,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x55,0x55,0x57,0x71,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x57,0x54,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x57,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x57,0x51,0x52,0x72,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x57,0x55,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x57,0x55,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x56,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x54,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x50,0x50,0x57,0x74,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x51,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x57,0x55,0x57,0x74,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x53,0x54,0x56,0x74,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x15,0x15,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x32,0x16,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x20,0x6E,0x22,0x2E,0x28,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x11,0x13,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x35,0x15,0x17,0x11,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x14,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x11,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x15,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x15,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x36,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x34,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x30,0x10,0x17,0x14,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x31,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x37,0x15,0x17,0x14,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x33,0x14,0x16,0x14,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00}, + {0x6C,0x6C,0x6C,0x6C,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x00,0x00,0x00}, + {0x0C,0x0C,0x3E,0x7F,0x68,0x68,0x3E,0x0B,0x0B,0x7F,0x3E,0x18,0x18,0x00,0x00,0x00}, + {0x60,0x60,0x66,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x66,0x06,0x06,0x00,0x00,0x00}, + {0x38,0x6C,0x6C,0x6C,0x6C,0x6C,0x38,0x6C,0x6D,0x66,0x66,0x6F,0x3B,0x00,0x00,0x00}, + {0x18,0x18,0x18,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x1C,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00}, + {0x38,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x38,0x00,0x00}, + {0x00,0x00,0x18,0x18,0x7E,0x3C,0x3C,0x3C,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x18,0x18,0x18,0x7E,0x7E,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00}, + {0x00,0x00,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x00,0x00,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x66,0x6E,0x6E,0x7E,0x76,0x76,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x18,0x38,0x78,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x06,0x06,0x0E,0x1C,0x38,0x70,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x06,0x06,0x1C,0x1C,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x0C,0x0C,0x1C,0x1C,0x3C,0x2C,0x6C,0x7E,0x7E,0x0C,0x0C,0x0C,0x0C,0x00,0x00,0x00}, + {0x7E,0x7E,0x60,0x60,0x7C,0x7E,0x06,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x18,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3C,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00}, + {0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0x60,0x30,0x18,0x0C,0x06,0x02,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x40,0x60,0x30,0x18,0x0C,0x06,0x06,0x0C,0x18,0x30,0x60,0x40,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x06,0x0C,0x0C,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x66,0x6E,0x6A,0x6A,0x6A,0x6E,0x60,0x60,0x7C,0x3C,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x7C,0x7E,0x66,0x66,0x66,0x7C,0x7C,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x78,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0x7C,0x78,0x00,0x00,0x00}, + {0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00}, + {0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x60,0x60,0x60,0x6E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x66,0x66,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00}, + {0x3E,0x3E,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x6C,0x7C,0x38,0x00,0x00,0x00}, + {0x66,0x66,0x66,0x6C,0x6C,0x78,0x78,0x78,0x6C,0x6C,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00}, + {0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00}, + {0x66,0x66,0x66,0x66,0x76,0x76,0x7E,0x6E,0x6E,0x66,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x6A,0x6A,0x6C,0x7E,0x36,0x00,0x00,0x00}, + {0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x3C,0x7E,0x66,0x60,0x60,0x78,0x3C,0x0E,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00}, + {0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00}, + {0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x77,0x77,0x22,0x00,0x00,0x00}, + {0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x66,0x66,0x66,0x66,0x7E,0x3C,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00}, + {0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00}, + {0x78,0x78,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x78,0x78,0x00,0x00}, + {0xC0,0xC0,0x60,0x60,0x30,0x30,0x18,0x0C,0x0C,0x06,0x06,0x03,0x03,0x00,0x00,0x00}, + {0x1E,0x1E,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x1E,0x1E,0x00,0x00}, + {0x3C,0x7E,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00}, + {0x30,0x38,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3C,0x3E,0x06,0x3E,0x7E,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00}, + {0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x06,0x06,0x06,0x06,0x3E,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x7E,0x7E,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x1C,0x3E,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x3E,0x3C,0x00}, + {0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x18,0x18,0x00,0x00,0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00}, + {0x0C,0x0C,0x00,0x00,0x3C,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x7C,0x78,0x00}, + {0x60,0x60,0x60,0x60,0x66,0x66,0x6C,0x78,0x78,0x6C,0x6C,0x66,0x66,0x00,0x00,0x00}, + {0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x36,0x7F,0x6B,0x6B,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x00}, + {0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x07,0x07,0x00}, + {0x00,0x00,0x00,0x00,0x6C,0x7E,0x76,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3C,0x7E,0x62,0x70,0x3C,0x0E,0x46,0x7E,0x3C,0x00,0x00,0x00}, + {0x30,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x7F,0x36,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x7E,0x7C,0x00}, + {0x00,0x00,0x00,0x00,0x7E,0x7E,0x0C,0x0C,0x18,0x30,0x30,0x7E,0x7E,0x00,0x00,0x00}, + {0x0E,0x1E,0x18,0x18,0x18,0x18,0x30,0x30,0x18,0x18,0x18,0x18,0x1E,0x0E,0x00,0x00}, + {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00}, + {0x70,0x78,0x18,0x18,0x18,0x18,0x0C,0x0C,0x18,0x18,0x18,0x18,0x78,0x70,0x00,0x00}, + {0x31,0x79,0x6B,0x4F,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x70,0x13,0x24,0x26,0x24,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, +] + +def _draw_char(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, x: t.CInt, y: t.CInt, ch: t.CChar, color: t.CUInt32T): + if not buf: return + if bw <= 0 or bh <= 0: return + ci: t.CUInt8T = t.CUInt8T(ch) + if ci >= 128: ci = 0 + fy: t.CInt = 0 + while fy < CHAR_H: + if y + fy < 0 or y + fy >= bh: + fy += 1 + continue + row: t.CUInt8T = font[ci][fy] + if row == 0: + fy += 1 + continue + fx: t.CInt = 0 + while fx < CHAR_W: + if row & (0x80 >> fx): + px: t.CInt = x + fx + if px >= 0 and px < bw: + buf[(y + fy) * bw + px] = color + fx += 1 + fy += 1 + +def _draw_text(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, x: t.CInt, y: t.CInt, s: t.CConst | str, color: t.CUInt32T): + if not buf: return + if bw <= 0 or bh <= 0: return + cx: t.CInt = x + cy: t.CInt = y + i: t.CInt = 0 + while True: + ch: t.CChar = s[i] + if ch == 0: break + if ch == 10: + cy += 16 + cx = x + else: + _draw_char(buf, bw, bh, cx, cy, ch, color) + cx += 8 + i += 1 + +def _register_widget(master: t.CVoid | t.CPtr, widget: Widget | t.CPtr) -> t.CInt: + tk: Tk | t.CPtr = t.CType(master, Tk, t.CPtr) + idx: t.CInt = tk.widget_count + if idx < MAX_WIDGETS: + tk.widgets[idx] = widget + tk.widget_count = idx + 1 + return idx + +@t.Object +@t.CVTable +class Widget: + x: t.CInt + y: t.CInt + w: t.CInt + h: t.CInt + visible: t.CInt + master: t.CVoid | t.CPtr + widget_type: t.CInt + focused: t.CInt + hovered: t.CInt + + def draw(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt): + pass + + def handle_event(self, evt: window.InputEvent | t.CPtr) -> t.CInt: + return 0 + + def contains(self, px: t.CInt, py: t.CInt) -> t.CInt: + if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h: + return 1 + return 0 + + def place(self, x: t.CInt = 0, y: t.CInt = 0, w: t.CInt = 0, h: t.CInt = 0): + self.x = x + self.y = y + if w > 0: + self.w = w + if h > 0: + self.h = h + +@t.Object +@t.CVTable +class Label(Widget): + text: t.CArray[t.CChar, 64] + color: t.CUInt32T + + def __init__(self, master: t.CVoid | t.CPtr, text: str, color: t.CUInt32T): + self.x = 0 + self.y = 0 + self.w = 0 + self.h = 0 + self.visible = 1 + self.master = master + self.widget_type = WIDGET_LABEL + self.focused = 0 + self.hovered = 0 + self.color = color + i: t.CInt = 0 + for ch in text: + if i >= 63: break + self.text[i] = ch + i += 1 + self.text[i] = 0 + _register_widget(master, t.CType(self, Widget, t.CPtr)) + + def draw(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt): + _draw_text(fb, fw, fh, self.x, self.y, c.Addr(self.text[0]), self.color) + + def handle_event(self, evt: window.InputEvent | t.CPtr) -> t.CInt: + return 0 + + def config(self, text: t.CConst | t.CChar | t.CPtr = None, color: t.CUInt32T = None): + if text is not None: + i: t.CInt = 0 + while i < 63: + ch: t.CChar = c.Deref(text + i) + self.text[i] = ch + if ch == 0: break + i += 1 + self.text[i] = 0 + if color is not None: + self.color = color + +@t.Object +@t.CVTable +class Button(Widget): + text: t.CArray[t.CChar, 32] + fill_color: t.CUInt32T + text_color: t.CUInt32T + pressed: t.CInt + callback: t.CVoid | t.CPtr + + def __init__(self, master: t.CVoid | t.CPtr, w: t.CInt, h: t.CInt, text: str, callback: t.CVoid | t.CPtr): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self.master = master + self.widget_type = WIDGET_BUTTON + self.focused = 0 + self.hovered = 0 + self.fill_color = t.CUInt32T(0x3C3C5A) + self.text_color = t.CUInt32T(0xFFFFFFFF) + self.pressed = 0 + self.callback = callback + i: t.CInt = 0 + for ch in text: + if i >= 31: break + self.text[i] = ch + i += 1 + self.text[i] = 0 + _register_widget(master, t.CType(self, Widget, t.CPtr)) + + def draw(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt): + fill: t.CUInt32T = self.fill_color + border: t.CUInt32T = t.CUInt32T(0x8080B0) + if self.pressed: + fill = _lighten(self.fill_color, 60) + border = t.CUInt32T(0xA0A0D0) + elif self.hovered: + fill = _lighten(self.fill_color, 30) + border = t.CUInt32T(0xA0A0D0) + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, fill) + top_left: t.CUInt32T = _lighten(fill, 80) + bottom_right: t.CUInt32T = _darken(fill, 80) + if self.pressed: + top_left = _darken(fill, 60) + bottom_right = _lighten(fill, 40) + _draw_hline(fb, fw, fh, self.x, self.y, self.w, top_left) + _draw_hline(fb, fw, fh, self.x, self.y + 1, self.w, top_left) + _draw_vline(fb, fw, fh, self.x, self.y, self.h, top_left) + _draw_vline(fb, fw, fh, self.x + 1, self.y, self.h, top_left) + _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bottom_right) + _draw_hline(fb, fw, fh, self.x, self.y + self.h - 2, self.w, bottom_right) + _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bottom_right) + _draw_vline(fb, fw, fh, self.x + self.w - 2, self.y, self.h, bottom_right) + _draw_hline(fb, fw, fh, self.x - 1, self.y - 1, self.w + 2, border) + _draw_hline(fb, fw, fh, self.x - 1, self.y + self.h, self.w + 2, border) + _draw_vline(fb, fw, fh, self.x - 1, self.y - 1, self.h + 2, border) + _draw_vline(fb, fw, fh, self.x + self.w, self.y - 1, self.h + 2, border) + tl: t.CInt = string.strlen(c.Addr(self.text[0])) + tx: t.CInt = self.x + (self.w - 8 * tl) / 2 + ty: t.CInt = self.y + (self.h - CHAR_H) / 2 + _draw_text(fb, fw, fh, tx, ty, c.Addr(self.text[0]), self.text_color) + + def handle_event(self, evt: window.InputEvent | t.CPtr) -> t.CInt: + etype: t.CUInt32T = evt.type + ex: t.CInt = evt.x + ey: t.CInt = evt.y + if etype == window.EVENT_TYPE_HOVER: + if self.contains(ex, ey): + if not self.hovered: + self.hovered = 1 + return 1 + else: + if self.hovered and not self.pressed: + self.hovered = 0 + return 1 + elif etype == window.EVENT_TYPE_HOVER_LEAVE: + if self.hovered: + self.hovered = 0 + return 1 + elif etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + self.pressed = 1 + return 1 + elif etype == window.EVENT_TYPE_RELEASE: + if self.pressed: + self.pressed = 0 + if self.contains(ex, ey) and self.callback is not None: + c.Asm(f"""push r12 +push r13 +mov r13, {c.AsmInp(self.callback, t.ASM_DESCR.REG_ANY)} +mov r12, rsp +and rsp, -16 +call r13 +mov rsp, r12 +pop r13 +pop r12""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, + t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + return 1 + return 0 + + def config(self, text: t.CConst | t.CChar | t.CPtr = None, fill_color: t.CUInt32T = None, text_color: t.CUInt32T = None, callback: t.CVoid | t.CPtr = None): + if text is not None: + i: t.CInt = 0 + while i < 31: + ch: t.CChar = c.Deref(text + i) + self.text[i] = ch + if ch == 0: break + i += 1 + self.text[i] = 0 + if fill_color is not None: + self.fill_color = fill_color + if text_color is not None: + self.text_color = text_color + if callback is not None: + self.callback = callback + +@t.Object +@t.CVTable +class Entry(Widget): + text: t.CArray[t.CChar, 64] + text_len: t.CInt + cursor_pos: t.CInt + max_len: t.CInt + bg_color: t.CUInt32T + border_color: t.CUInt32T + text_color: t.CUInt32T + cursor_color: t.CUInt32T + + def __init__(self, master: t.CVoid | t.CPtr, w: t.CInt, h: t.CInt): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self.master = master + self.widget_type = WIDGET_ENTRY + self.focused = 0 + self.hovered = 0 + self.text_len = 0 + self.cursor_pos = 0 + self.max_len = 30 + self.bg_color = t.CUInt32T(0xFF231919) + self.border_color = t.CUInt32T(0x606080) + self.text_color = t.CUInt32T(0x000000) + self.cursor_color = t.CUInt32T(0x000000) + for i in range(64): + self.text[i] = 0 + _register_widget(master, t.CType(self, Widget, t.CPtr)) + + def draw(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt): + bg: t.CUInt32T = self.bg_color + bdr: t.CUInt32T = self.border_color + if self.focused: + bg = t.CUInt32T(0xFFFFFF) + bdr = t.CUInt32T(0x4488FF) + elif self.hovered: + bdr = t.CUInt32T(0x8080B0) + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, bg) + _draw_hline(fb, fw, fh, self.x, self.y, self.w, bdr) + _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bdr) + _draw_vline(fb, fw, fh, self.x, self.y, self.h, bdr) + _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bdr) + if self.text_len > 0: + tc: t.CUInt32T = self.text_color + if not self.focused: + tc = t.CUInt32T(0xCCCCCC) + _draw_text(fb, fw, fh, self.x + 6, self.y + 7, c.Addr(self.text[0]), tc) + elif not self.focused: + _draw_text(fb, fw, fh, self.x + 6, self.y + 7, "Type here...", t.CUInt32T(0x888888)) + if self.focused: + cx_pos: t.CInt = self.x + 6 + self.cursor_pos * CHAR_W + if cx_pos < self.x + self.w - 2: + _draw_vline(fb, fw, fh, cx_pos, self.y + 5, self.h - 10, self.cursor_color) + + def handle_event(self, evt: window.InputEvent | t.CPtr) -> t.CInt: + etype: t.CUInt32T = evt.type + ex: t.CInt = evt.x + ey: t.CInt = evt.y + if etype == window.EVENT_TYPE_HOVER: + if self.contains(ex, ey): + if not self.hovered: + self.hovered = 1 + tk: Tk | t.CPtr = t.CType(self.master, Tk, t.CPtr) + window.set_cursor_type(tk.win_id, window.CURSOR_IBEAM) + return 1 + else: + if self.hovered: + self.hovered = 0 + tk2: Tk | t.CPtr = t.CType(self.master, Tk, t.CPtr) + window.set_cursor_type(tk2.win_id, window.CURSOR_DEFAULT) + return 1 + elif etype == window.EVENT_TYPE_HOVER_LEAVE: + if self.hovered: + self.hovered = 0 + tk3: Tk | t.CPtr = t.CType(self.master, Tk, t.CPtr) + window.set_cursor_type(tk3.win_id, window.CURSOR_DEFAULT) + return 1 + elif etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + if not self.focused: + self.focused = 1 + tk4: Tk | t.CPtr = t.CType(self.master, Tk, t.CPtr) + tk4.focused_widget = t.CType(self, Widget, t.CPtr) + rel_x: t.CInt = ex - self.x - 6 + if rel_x < 0: rel_x = 0 + new_pos: t.CInt = (rel_x + CHAR_W / 2) / CHAR_W + if new_pos > self.text_len: new_pos = self.text_len + self.cursor_pos = new_pos + return 1 + else: + if self.focused: + self.focused = 0 + tk5: Tk | t.CPtr = t.CType(self.master, Tk, t.CPtr) + tk5.focused_widget = None + return 1 + elif etype == window.EVENT_TYPE_KEY: + if self.focused: + ebtn: t.CUInt8T = evt.button + if ebtn == window.KEY_STATE_RELEASE: + return 0 + ch: t.CInt = ex + if ch == 8: + if self.cursor_pos > 0: + for i in range(self.cursor_pos, self.text_len): + self.text[i - 1] = self.text[i] + self.text_len -= 1 + self.cursor_pos -= 1 + self.text[self.text_len] = 0 + return 1 + elif ch == window.VK_LEFT: + if self.cursor_pos > 0: + self.cursor_pos -= 1 + return 1 + elif ch == window.VK_RIGHT: + if self.cursor_pos < self.text_len: + self.cursor_pos += 1 + return 1 + elif ch == window.VK_HOME: + self.cursor_pos = 0 + return 1 + elif ch == window.VK_END: + self.cursor_pos = self.text_len + return 1 + elif ch == window.VK_DELETE: + if self.cursor_pos < self.text_len: + for i in range(self.cursor_pos, self.text_len - 1): + self.text[i] = self.text[i + 1] + self.text_len -= 1 + self.text[self.text_len] = 0 + return 1 + elif ch >= 32 and ch < 127: + if self.text_len < self.max_len: + i: t.CInt = self.text_len + while i > self.cursor_pos: + self.text[i] = self.text[i - 1] + i -= 1 + self.text[self.cursor_pos] = t.CChar(ch) + self.text_len += 1 + self.cursor_pos += 1 + self.text[self.text_len] = 0 + return 1 + return 0 + + def get(self) -> t.CConst | t.CChar | t.CPtr: + return c.Addr(self.text[0]) + + def delete(self, start: t.CInt, end: t.CInt): + self.text_len = 0 + for i in range(64): + self.text[i] = 0 + + def config(self, bg_color: t.CUInt32T = None, text_color: t.CUInt32T = None, border_color: t.CUInt32T = None): + if bg_color is not None: + self.bg_color = bg_color + if text_color is not None: + self.text_color = text_color + if border_color is not None: + self.border_color = border_color + +@t.Object +@t.CVTable +class Scrollbar(Widget): + orient: t.CInt + command: t.CVoid | t.CPtr + slider_first: t.CInt + slider_last: t.CInt + action: t.CInt + action_value: t.CInt + pressed: t.CInt + dragging: t.CInt + drag_start_y: t.CInt + drag_start_first: t.CInt + trough_color: t.CUInt32T + slider_color: t.CUInt32T + arrow_color: t.CUInt32T + slider_hover: t.CInt + + def __init__(self, master: t.CVoid | t.CPtr, w: t.CInt, h: t.CInt, orient: t.CInt): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self.master = master + self.widget_type = WIDGET_SCROLLBAR + self.focused = 0 + self.hovered = 0 + self.orient = orient + self.command = None + self.slider_first = 0 + self.slider_last = 1000 + self.action = 0 + self.action_value = 0 + self.pressed = 0 + self.dragging = 0 + self.drag_start_y = 0 + self.drag_start_first = 0 + self.trough_color = t.CUInt32T(0x1E1E2E) + self.slider_color = t.CUInt32T(0x4A4A6A) + self.arrow_color = t.CUInt32T(0xAAAAAA) + self.slider_hover = 0 + _register_widget(master, t.CType(self, Widget, t.CPtr)) + + def draw(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt): + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.trough_color) + if self.orient != VERTICAL: return + arrow_sz: t.CInt = self.w + if arrow_sz > 16: arrow_sz = 16 + self._draw_arrow_btn(fb, fw, fh, self.x, self.y, self.w, arrow_sz, 0, 1 if self.pressed == 1 else 0) + self._draw_arrow_btn(fb, fw, fh, self.x, self.y + self.h - arrow_sz, self.w, arrow_sz, 1, 1 if self.pressed == 2 else 0) + trough_y: t.CInt = self.y + arrow_sz + trough_h: t.CInt = self.h - 2 * arrow_sz + if trough_h <= 0: return + if self.slider_last > self.slider_first: + sl_h: t.CInt = (self.slider_last - self.slider_first) * trough_h / 1000 + if sl_h < 8: sl_h = 8 + sl_y: t.CInt = trough_y + self.slider_first * trough_h / 1000 + if sl_y + sl_h > trough_y + trough_h: + sl_y = trough_y + trough_h - sl_h + if sl_y < trough_y: sl_y = trough_y + color: t.CUInt32T = self.slider_color + if self.dragging: + color = _lighten(self.slider_color, 40) + elif self.slider_hover: + color = _lighten(self.slider_color, 20) + _fill_rect(fb, fw, fh, self.x + 1, sl_y, self.w - 2, sl_h, color) + top_c: t.CUInt32T = _lighten(color, 60) + bot_c: t.CUInt32T = _darken(color, 60) + _draw_hline(fb, fw, fh, self.x + 1, sl_y, self.w - 2, top_c) + _draw_hline(fb, fw, fh, self.x + 1, sl_y + sl_h - 1, self.w - 2, bot_c) + _draw_vline(fb, fw, fh, self.x + 1, sl_y, sl_h, top_c) + _draw_vline(fb, fw, fh, self.x + self.w - 2, sl_y, sl_h, bot_c) + + def _draw_arrow_btn(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt, bx: t.CInt, by: t.CInt, bw: t.CInt, bh: t.CInt, direction: t.CInt, is_pressed: t.CInt): + color: t.CUInt32T = self.slider_color + if is_pressed: + color = _lighten(self.slider_color, 30) + _fill_rect(fb, fw, fh, bx, by, bw, bh, color) + top_c: t.CUInt32T = _lighten(color, 60) + bot_c: t.CUInt32T = _darken(color, 60) + if is_pressed: + top_c = _darken(color, 40) + bot_c = _lighten(color, 30) + _draw_hline(fb, fw, fh, bx, by, bw, top_c) + _draw_hline(fb, fw, fh, bx, by + bh - 1, bw, bot_c) + _draw_vline(fb, fw, fh, bx, by, bh, top_c) + _draw_vline(fb, fw, fh, bx + bw - 1, by, bh, bot_c) + cx: t.CInt = bx + bw / 2 + cy: t.CInt = by + bh / 2 + if direction == 0: + _fill_rect(fb, fw, fh, cx - 1, cy - 3, 3, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 2, cy - 2, 5, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color) + else: + _fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 2, cy, 5, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 1, cy + 1, 3, 1, self.arrow_color) + + def handle_event(self, evt: window.InputEvent | t.CPtr) -> t.CInt: + etype: t.CUInt32T = evt.type + ex: t.CInt = evt.x + ey: t.CInt = evt.y + if etype == window.EVENT_TYPE_HOVER: + if self.dragging: + self._handle_drag(ey) + return 1 + if self.contains(ex, ey): + if not self.slider_hover: + self.slider_hover = 1 + return 1 + else: + if self.slider_hover and self.pressed == 0: + self.slider_hover = 0 + return 1 + return 0 + elif etype == window.EVENT_TYPE_HOVER_LEAVE: + if self.slider_hover: + self.slider_hover = 0 + if not self.dragging: + self.pressed = 0 + return 1 + return 0 + elif etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + if self.orient == VERTICAL: + arrow_sz: t.CInt = self.w + if arrow_sz > 16: arrow_sz = 16 + if ey < self.y + arrow_sz: + self.pressed = 1 + self.action = 1 + self._fire_command() + return 1 + elif ey >= self.y + self.h - arrow_sz: + self.pressed = 2 + self.action = 2 + self._fire_command() + return 1 + else: + trough_y: t.CInt = self.y + arrow_sz + trough_h: t.CInt = self.h - 2 * arrow_sz + if trough_h <= 0: return 1 + sl_h: t.CInt = (self.slider_last - self.slider_first) * trough_h / 1000 + if sl_h < 8: sl_h = 8 + sl_y: t.CInt = trough_y + self.slider_first * trough_h / 1000 + if sl_y + sl_h > trough_y + trough_h: + sl_y = trough_y + trough_h - sl_h + if ey >= sl_y and ey < sl_y + sl_h: + self.pressed = 4 + self.dragging = 1 + self.drag_start_y = ey + self.drag_start_first = self.slider_first + else: + self.pressed = 3 + if ey < sl_y: + self.action = 3 + else: + self.action = 4 + self._fire_command() + return 1 + return 0 + elif etype == window.EVENT_TYPE_RELEASE: + if self.pressed: + self.pressed = 0 + self.dragging = 0 + return 1 + return 0 + return 0 + + def _handle_drag(self, ey: t.CInt): + arrow_sz: t.CInt = self.w + if arrow_sz > 16: arrow_sz = 16 + trough_y: t.CInt = self.y + arrow_sz + trough_h: t.CInt = self.h - 2 * arrow_sz + if trough_h <= 0: return + delta_y: t.CInt = ey - self.drag_start_y + delta_first: t.CInt = delta_y * 1000 / trough_h + new_first: t.CInt = self.drag_start_first + delta_first + slider_size: t.CInt = self.slider_last - self.slider_first + if slider_size <= 0: slider_size = 1 + if new_first < 0: new_first = 0 + if new_first + slider_size > 1000: new_first = 1000 - slider_size + if new_first != self.slider_first: + self.slider_first = new_first + self.slider_last = new_first + slider_size + self.action = 5 + self.action_value = new_first + self._fire_command() + + def _fire_command(self): + if self.command is not None: + c.Asm(f"""push r12 +push r13 +mov r13, {c.AsmInp(self.command, t.ASM_DESCR.REG_ANY)} +mov r12, rsp +and rsp, -16 +call r13 +mov rsp, r12 +pop r13 +pop r12""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, + t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + + def set(self, first: t.CInt, last: t.CInt): + self.slider_first = first + self.slider_last = last + +@t.Object +@t.CVTable +class FBCanvas(Widget): + _fb: UINT32PTR + _cw: t.CInt + _ch: t.CInt + + def __init__(self, master: t.CVoid | t.CPtr, w: t.CInt, h: t.CInt): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self.master = master + self.widget_type = WIDGET_FBCANVAS + self.focused = 0 + self.hovered = 0 + self._fb = None + self._cw = w + self._ch = h + _register_widget(master, t.CType(self, Widget, t.CPtr)) + + def draw(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt): + if not self._fb: return + if self._cw <= 0 or self._ch <= 0: return + copy_w: t.CInt = self._cw + if copy_w > self.w: copy_w = self.w + copy_h: t.CInt = self._ch + if copy_h > self.h: copy_h = self.h + for py in range(copy_h): + ty: t.CInt = self.y + py + if ty < 0 or ty >= fh: continue + for px in range(copy_w): + tx: t.CInt = self.x + px + if tx < 0 or tx >= fw: continue + fb[ty * fw + tx] = self._fb[py * self._cw + px] + + def handle_event(self, evt: window.InputEvent | t.CPtr) -> t.CInt: + return 0 + + def set_fb(self, fb: UINT32PTR, cw: t.CInt, ch: t.CInt): + self._fb = fb + self._cw = cw + self._ch = ch + + def get_fb(self) -> UINT32PTR: + return self._fb + + def get_w(self) -> t.CInt: + return self._cw + + def get_h(self) -> t.CInt: + return self._ch + +@t.Object +@t.CVTable +class Text(Widget): + lines: t.CArray[t.CArray[t.CChar, 80], TEXT_MAX_LINES] + line_count: t.CInt + input_buf: t.CArray[t.CChar, 80] + input_len: t.CInt + cursor_pos: t.CInt + bg_color: t.CUInt32T + text_color: t.CUInt32T + prompt_color: t.CUInt32T + cursor_color: t.CUInt32T + border_color: t.CUInt32T + on_command: t.CVoid | t.CPtr + history: t.CArray[t.CArray[t.CChar, 80], 16] + history_count: t.CInt + history_idx: t.CInt + scroll_offset: t.CInt + max_lines: t.CInt + yscrollbar: t.CVoid | t.CPtr + + def __init__(self, master: t.CVoid | t.CPtr, w: t.CInt, h: t.CInt): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self.master = master + self.widget_type = WIDGET_TEXT + self.focused = 0 + self.hovered = 0 + self.line_count = 0 + self.input_len = 0 + self.cursor_pos = 0 + self.bg_color = t.CUInt32T(0x0C0C0C) + self.text_color = t.CUInt32T(0xCCCCCC) + self.prompt_color = t.CUInt32T(0x00FF00) + self.cursor_color = t.CUInt32T(0x00FF00) + self.border_color = t.CUInt32T(0x333333) + self.on_command = None + self.history_count = 0 + self.history_idx = 0 + self.scroll_offset = 0 + self.max_lines = TEXT_MAX_LINES + self.yscrollbar = None + i: t.CInt = 0 + while i < TEXT_MAX_LINES: + j: t.CInt = 0 + while j <= 79: + self.lines[i][j] = 0 + j += 1 + i += 1 + i = 0 + while i < 80: + self.input_buf[i] = 0 + i += 1 + _register_widget(master, t.CType(self, Widget, t.CPtr)) + + def draw(self, fb: UINT32PTR, fw: t.CInt, fh: t.CInt): + self._update_scrollbar() + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.bg_color) + _fill_rect(fb, fw, fh, self.x, self.y + self.h - 2, self.w, 2, self.border_color) + visible: t.CInt = self._visible_lines() + for i in range(visible): + line_idx: t.CInt = self.scroll_offset + i + if line_idx < self.line_count: + _draw_text(fb, fw, fh, self.x + 4, self.y + i * CHAR_H, c.Addr(self.lines[line_idx][0]), self.text_color) + prompt_y: t.CInt = self.y + self.h - CHAR_H - 4 + _draw_text(fb, fw, fh, self.x + 4, prompt_y, "> ", self.prompt_color) + if self.input_len > 0: + _draw_text(fb, fw, fh, self.x + 4 + 2 * CHAR_W, prompt_y, c.Addr(self.input_buf[0]), t.CUInt32T(0xFFFFFF)) + cx_pos: t.CInt = self.x + 4 + (2 + self.cursor_pos) * CHAR_W + if cx_pos < self.x + self.w - 4: + _fill_rect(fb, fw, fh, cx_pos, prompt_y, CHAR_W // 2, CHAR_H, self.cursor_color) + + def handle_event(self, evt: window.InputEvent | t.CPtr) -> t.CInt: + etype: t.CUInt32T = evt.type + ex: t.CInt = evt.x + ey: t.CInt = evt.y + if etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + if not self.focused: + self.focused = 1 + tk: Tk | t.CPtr = t.CType(self.master, Tk, t.CPtr) + tk.focused_widget = t.CType(self, Widget, t.CPtr) + prompt_y: t.CInt = self.y + self.h - CHAR_H - 4 + if ey >= prompt_y and ey < prompt_y + CHAR_H: + rel_x: t.CInt = ex - self.x - 4 - 2 * CHAR_W + if rel_x < 0: rel_x = 0 + new_pos: t.CInt = (rel_x + CHAR_W / 2) / CHAR_W + if new_pos > self.input_len: new_pos = self.input_len + self.cursor_pos = new_pos + return 1 + else: + if self.focused: + self.focused = 0 + tk2: Tk | t.CPtr = t.CType(self.master, Tk, t.CPtr) + tk2.focused_widget = None + return 1 + elif etype == window.EVENT_TYPE_KEY: + if self.focused: + ebtn2: t.CUInt8T = evt.button + if ebtn2 == window.KEY_STATE_RELEASE: + return 0 + ch: t.CInt = ex + if ch == 8: + if self.cursor_pos > 0: + i: t.CInt = self.cursor_pos + while i < self.input_len: + self.input_buf[i - 1] = self.input_buf[i] + i += 1 + self.input_len -= 1 + self.cursor_pos -= 1 + self.input_buf[self.input_len] = 0 + return 1 + elif ch == 13: + self._exec_input() + self.input_len = 0 + self.cursor_pos = 0 + for i in range(80): + self.input_buf[i] = 0 + return 1 + elif ch == window.VK_LEFT: + if self.cursor_pos > 0: + self.cursor_pos -= 1 + return 1 + elif ch == window.VK_RIGHT: + if self.cursor_pos < self.input_len: + self.cursor_pos += 1 + return 1 + elif ch == window.VK_UP: + if self.history_count > 0: + if self.history_idx < self.history_count: + self.history_idx += 1 + hi: t.CInt = self.history_count - self.history_idx + if hi < 0: hi = 0 + for j in range(80): + self.input_buf[j] = self.history[hi][j] + self.input_len = 0 + while self.input_len < 79 and self.input_buf[self.input_len] != 0: + self.input_len += 1 + self.cursor_pos = self.input_len + return 1 + elif ch == window.VK_DOWN: + if self.history_count > 0: + if self.history_idx > 0: + self.history_idx -= 1 + if self.history_idx == 0: + self.input_len = 0 + self.cursor_pos = 0 + for j2 in range(80): + self.input_buf[j2] = 0 + else: + hi2: t.CInt = self.history_count - self.history_idx + for j3 in range(80): + self.input_buf[j3] = self.history[hi2][j3] + self.input_len = 0 + while self.input_len < 79 and self.input_buf[self.input_len] != 0: + self.input_len += 1 + self.cursor_pos = self.input_len + return 1 + elif ch == window.VK_HOME: + self.cursor_pos = 0 + return 1 + elif ch == window.VK_END: + self.cursor_pos = self.input_len + return 1 + elif ch == window.VK_DELETE: + if self.cursor_pos < self.input_len: + i: t.CInt = self.cursor_pos + while i < self.input_len - 1: + self.input_buf[i] = self.input_buf[i + 1] + i += 1 + self.input_len -= 1 + self.input_buf[self.input_len] = 0 + return 1 + elif ch == window.VK_PRIOR: + if self.scroll_offset > 0: + vis: t.CInt = self._visible_lines() + self.scroll_offset -= vis + if self.scroll_offset < 0: self.scroll_offset = 0 + self._update_scrollbar() + return 1 + elif ch == window.VK_NEXT: + vis2: t.CInt = self._visible_lines() + max_off: t.CInt = self.line_count - vis2 + if max_off < 0: max_off = 0 + if self.scroll_offset < max_off: + self.scroll_offset += vis2 + if self.scroll_offset > max_off: self.scroll_offset = max_off + self._update_scrollbar() + return 1 + elif ch >= 32 and ch < 127: + if self.input_len < 79: + i: t.CInt = self.input_len + while i > self.cursor_pos: + self.input_buf[i] = self.input_buf[i - 1] + i -= 1 + self.input_buf[self.cursor_pos] = t.CChar(ch) + self.input_len += 1 + self.cursor_pos += 1 + self.input_buf[self.input_len] = 0 + return 1 + return 0 + + def _exec_input(self): + self.insert("> ") + if self.input_len > 0: + last: t.CInt = self.line_count - 1 + j: t.CInt = 0 + while j < self.input_len and j < 77: + self.lines[last][2 + j] = self.input_buf[j] + j += 1 + self.lines[last][2 + j] = 0 + if self.history_count < 16: + k: t.CInt = 0 + while k < 80: + self.history[self.history_count][k] = self.input_buf[k] + k += 1 + self.history_count += 1 + else: + i: t.CInt = 0 + while i < 15: + k2: t.CInt = 0 + while k2 < 80: + self.history[i][k2] = self.history[i + 1][k2] + k2 += 1 + i += 1 + k3: t.CInt = 0 + while k3 < 80: + self.history[15][k3] = self.input_buf[k3] + k3 += 1 + self.history_idx = 0 + + cmd: t.CVoid | t.CPtr = self.on_command + if cmd is not None: + c.Asm(f"""push r12 +push r13 +mov r13, {c.AsmInp(cmd, t.ASM_DESCR.REG_ANY)} +mov r12, rsp +and rsp, -16 +call r13 +mov rsp, r12 +pop r13 +pop r12""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, + t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, + t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_RDI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) + else: + self.insert("(no callback)") + + def insert(self, text: str): + start: t.CInt = 0 + pos: t.CInt = 0 + while True: + ch: t.CChar = c.Deref(text + pos) + if ch == 0: + if pos > start: + self._insert_substring(text, start, pos) + break + if ch == 10: + self._insert_substring(text, start, pos) + start = pos + 1 + pos += 1 + + def insert_ptr(self, text: t.CConst | t.CChar | t.CPtr): + start: t.CInt = 0 + pos: t.CInt = 0 + while True: + ch: t.CChar = c.Deref(text + pos) + if ch == 0: + if pos > start: + self._insert_substring(text, start, pos) + break + if ch == 10: + self._insert_substring(text, start, pos) + start = pos + 1 + pos += 1 + + def _insert_substring(self, text: t.CConst | t.CChar | t.CPtr, start: t.CInt, end: t.CInt): + if self.line_count >= self.max_lines: + i: t.CInt = 0 + while i < self.max_lines - 1: + j: t.CInt = 0 + while j <= 79: + self.lines[i][j] = self.lines[i + 1][j] + j += 1 + i += 1 + self.line_count -= 1 + if self.scroll_offset > 0: + self.scroll_offset -= 1 + idx: t.CInt = self.line_count + j: t.CInt = 0 + k: t.CInt = start + while k < end and j < 79: + ch: t.CChar = c.Deref(text + k) + if ch == 9: + tab_stop: t.CInt = 8 - (j % 8) + while tab_stop > 0 and j < 79: + self.lines[idx][j] = 32 + j += 1 + tab_stop -= 1 + elif ch == 13: + pass + else: + self.lines[idx][j] = ch + j += 1 + k += 1 + self.lines[idx][j] = 0 + self.line_count += 1 + vis: t.CInt = self._visible_lines() + if self.line_count > vis: + self.scroll_offset = self.line_count - vis + else: + self.scroll_offset = 0 + self._update_scrollbar() + + def delete(self, start: t.CInt, end: t.CInt): + self.line_count = 0 + self.scroll_offset = 0 + self._update_scrollbar() + + def get(self) -> t.CConst | t.CChar | t.CPtr: + return c.Addr(self.input_buf[0]) + + def config(self, text_color: t.CUInt32T = None, bg_color: t.CUInt32T = None, prompt_color: t.CUInt32T = None, cursor_color: t.CUInt32T = None, border_color: t.CUInt32T = None): + if text_color is not None: + self.text_color = text_color + if bg_color is not None: + self.bg_color = bg_color + if prompt_color is not None: + self.prompt_color = prompt_color + if cursor_color is not None: + self.cursor_color = cursor_color + if border_color is not None: + self.border_color = border_color + + def _visible_lines(self) -> t.CInt: + return (self.h - CHAR_H - 4) / CHAR_H + + def _update_scrollbar(self): + vis: t.CInt = self._visible_lines() + if self.line_count <= vis: + self.scroll_offset = 0 + else: + max_off: t.CInt = self.line_count - vis + if self.scroll_offset > max_off: + self.scroll_offset = max_off + if self.yscrollbar is not None: + sb: Scrollbar | t.CPtr = t.CType(self.yscrollbar, Scrollbar, t.CPtr) + if self.line_count <= vis: + sb.set(0, 1000) + else: + first: t.CInt = self.scroll_offset * 1000 / self.line_count + last: t.CInt = (self.scroll_offset + vis) * 1000 / self.line_count + sb.set(first, last) + +@t.Object +class Tk: + win_id: t.CInt + buf: UINT32PTR + bw: t.CInt + bh: t.CInt + style: t.CInt + bg_color: t.CUInt32T + widgets: t.CArray[Widget | t.CPtr, MAX_WIDGETS] + widget_count: t.CInt + focused_widget: Widget | t.CPtr + _win_x: t.CInt + _win_y: t.CInt + _title: t.CArray[t.CChar, 32] + resizable_w: t.CInt + resizable_h: t.CInt + _grid_rows: t.CInt + _grid_cols: t.CInt + + def __init__(self): + self.widget_count = 0 + self.focused_widget = None + self.win_id = -1 + self._win_x = 0 + self._win_y = 0 + self.style = 0 + self.bg_color = t.CUInt32T(0xFF231919) + self.resizable_w = 1 + self.resizable_h = 1 + self._grid_rows = 0 + self._grid_cols = 0 + if _g_layout_init == 0: + i: t.CInt = 0 + while i < MAX_WIDGETS: + _g_wl_type[i] = 0 + _g_wl_side[i] = 0 + _g_wl_fill[i] = 0 + _g_wl_expand[i] = 0 + _g_wl_padx[i] = 0 + _g_wl_pady[i] = 0 + _g_wl_row[i] = 0 + _g_wl_col[i] = 0 + _g_wl_rowspan[i] = 0 + _g_wl_colspan[i] = 1 + _g_wl_sticky[i] = 0 + i += 1 + _g_layout_init = 1 + j: t.CInt = 0 + while j < 32: + self._title[j] = 0 + j += 1 + + def pack(self, idx: t.CInt, side: t.CInt, fill: t.CInt, expand: t.CInt, padx: t.CInt, pady: t.CInt): + _g_wl_type[idx] = 2 + _g_wl_side[idx] = side + _g_wl_fill[idx] = fill + _g_wl_expand[idx] = expand + _g_wl_padx[idx] = padx + _g_wl_pady[idx] = pady + + def grid(self, idx: t.CInt, row: t.CInt, col: t.CInt, rowspan: t.CInt, colspan: t.CInt, sticky: t.CInt): + _g_wl_type[idx] = 3 + _g_wl_row[idx] = row + _g_wl_col[idx] = col + _g_wl_rowspan[idx] = rowspan + _g_wl_colspan[idx] = colspan + _g_wl_sticky[idx] = sticky + + def title(self, text: str): + i: t.CInt = 0 + while i < 31: + ch: t.CChar = text[i] + if ch == 0: break + self._title[i] = ch + i += 1 + self._title[i] = 0 + if self.win_id >= 0: + window.set_title(self.win_id, c.Addr(self._title[0])) + + def set_style(self, s: t.CInt): + self.style = s + + def resizable(self, rw: t.CInt, rh: t.CInt): + self.resizable_w = rw + self.resizable_h = rh + if self.win_id >= 0: + window.set_resizable(self.win_id, rw, rh) + + def geometry(self, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt): + self._win_x = x + self._win_y = y + self.bw = w + self.bh = h - TITLE_H - 2 + if self.win_id < 0: + self.win_id = window.create_window(x, y, w, h, c.Addr(self._title[0]), self.style) + self.buf = window.get_winbuf(self.win_id) + window.set_resizable(self.win_id, self.resizable_w, self.resizable_h) + else: + window.set_geometry(self.win_id, x, y, w, h) + self.buf = window.get_winbuf(self.win_id) + + def setup(self, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | t.CChar | t.CPtr, style: t.CInt): + self._win_x = x + self._win_y = y + self.win_id = window.create_window(x, y, w, h, title, style) + self.buf = window.get_winbuf(self.win_id) + self.bw = w + self.bh = h - TITLE_H - 2 + self.style = style + self.bg_color = t.CUInt32T(0xFF231919) + + def redraw(self): + self._do_layout() + self.buf = window.get_winbuf(self.win_id) + sz: t.CInt = window.get_win_size(self.win_id) + if sz != 0: + self.bw = sz >> 16 + self.bh = sz & 0xFFFF + if not self.buf: return + if self.bw <= 0 or self.bh <= 0: return + _fill_rect(self.buf, self.bw, self.bh, 0, 0, self.bw, self.bh, self.bg_color) + i: t.CInt = 0 + while i < self.widget_count: + w: Widget | t.CPtr = self.widgets[i] + if w.visible: + w.draw(self.buf, self.bw, self.bh) + i += 1 + window.flush(self.win_id) + + def _do_layout(self): + self._grid_rows = 0 + self._grid_cols = 0 + cav_x: t.CInt = 0 + cav_y: t.CInt = 0 + cav_w: t.CInt = self.bw + cav_h: t.CInt = self.bh + i: t.CInt = 0 + while i < self.widget_count: + w: Widget | t.CPtr = self.widgets[i] + lt: t.CInt = _g_wl_type[i] + if lt == 2: + side: t.CInt = _g_wl_side[i] + fill: t.CInt = _g_wl_fill[i] + expand: t.CInt = _g_wl_expand[i] + px: t.CInt = _g_wl_padx[i] + py: t.CInt = _g_wl_pady[i] + ww: t.CInt = w.w + wh: t.CInt = w.h + if side == PACK_TOP: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + if expand: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + w.x = cav_x + px + w.y = cav_y + py + w.w = ww + w.h = wh + cav_y += wh + 2 * py + cav_h -= wh + 2 * py + elif side == PACK_BOTTOM: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + if expand: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + w.x = cav_x + px + w.y = cav_y + cav_h - wh - py + w.w = ww + w.h = wh + cav_h -= wh + 2 * py + elif side == PACK_LEFT: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + if expand: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + w.x = cav_x + px + w.y = cav_y + py + w.w = ww + w.h = wh + cav_x += ww + 2 * px + cav_w -= ww + 2 * px + elif side == PACK_RIGHT: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + if expand: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + w.x = cav_x + cav_w - ww - px + w.y = cav_y + py + w.w = ww + w.h = wh + cav_w -= ww + 2 * px + elif lt == 3: + r: t.CInt = _g_wl_row[i] + _g_wl_rowspan[i] + c: t.CInt = _g_wl_col[i] + _g_wl_colspan[i] + if r > self._grid_rows: self._grid_rows = r + if c > self._grid_cols: self._grid_cols = c + i += 1 + i = 0 + while i < self.widget_count: + w: Widget | t.CPtr = self.widgets[i] + if _g_wl_type[i] == 3: + if self._grid_rows <= 0: self._grid_rows = 1 + if self._grid_cols <= 0: self._grid_cols = 1 + cell_w: t.CInt = self.bw / self._grid_cols + cell_h: t.CInt = self.bh / self._grid_rows + wx: t.CInt = _g_wl_col[i] * cell_w + wy: t.CInt = _g_wl_row[i] * cell_h + ww: t.CInt = _g_wl_colspan[i] * cell_w + wh: t.CInt = _g_wl_rowspan[i] * cell_h + sticky: t.CInt = _g_wl_sticky[i] + if sticky == 0: + ox: t.CInt = (ww - w.w) / 2 + oy: t.CInt = (wh - w.h) / 2 + w.x = wx + ox + w.y = wy + oy + else: + w.x = wx + w.y = wy + if (sticky & 1) != 0: + w.w = ww + if (sticky & 2) != 0: + w.h = wh + i += 1 + + def update(self): + self.redraw() + + def winfo_exists(self) -> t.CInt: + if self.win_id < 0: return 0 + return window.is_active(self.win_id) + + def mainloop(self): + self.redraw() + evt: window.InputEvent + while window.is_active(self.win_id): + result: t.CInt = window.poll_event(c.Addr(evt)) + if result != 0: + if evt.win_id == self.win_id: + need_redraw: t.CInt = 0 + etype: t.CUInt32T = evt.type + if etype == window.EVENT_TYPE_PING: + window.flush(self.win_id) + elif etype == window.EVENT_TYPE_RESIZE: + self.buf = window.get_winbuf(self.win_id) + sz: t.CInt = window.get_win_size(self.win_id) + self.bw = sz >> 16 + self.bh = sz & 0xFFFF + need_redraw = 1 + elif etype == window.EVENT_TYPE_KEY or etype == window.EVENT_TYPE_KEY_RELEASE: + if self.focused_widget is not None: + handled: t.CInt = self.focused_widget.handle_event(c.Addr(evt)) + if handled: need_redraw = 1 + else: + if etype == window.EVENT_TYPE_HOVER: + i: t.CInt = self.widget_count - 1 + while i >= 0: + w: Widget | t.CPtr = self.widgets[i] + if w.visible: + handled2: t.CInt = w.handle_event(c.Addr(evt)) + if handled2: + need_redraw = 1 + break + i -= 1 + else: + i: t.CInt = 0 + while i < self.widget_count: + w: Widget | t.CPtr = self.widgets[i] + if w.visible: + handled2: t.CInt = w.handle_event(c.Addr(evt)) + if handled2: need_redraw = 1 + i += 1 + if need_redraw: + self.redraw() + else: + process.yield_cpu() diff --git a/vpsdk/wiki/01-overview.md b/vpsdk/wiki/01-overview.md new file mode 100644 index 0000000..efec04b --- /dev/null +++ b/vpsdk/wiki/01-overview.md @@ -0,0 +1,357 @@ +# 01 - 语言概述与编译流程 + +## 语言定位 + +`Viper` 是一种 **系统级编程语言**,其语法基于 `Python`,但编译目标为 `LLVM IR`,最终生成原生机器码。`Viper` 不是 `Python` 的超集或子集,也不是"`C` 的语法糖"——它是一门拥有独立类型系统、独立编译模型、独立模块体系的语言。`Viper` 选择 `Python` 语法作为表达形式,是因为 `Python` 是人类公认的可读性最高的语言。 + +当前编译器 `TransPyC`(用 Python 实现)使用 Python 标准库 `ast` 解析源文件,将精力集中在语义翻译而非词法/语法分析上。但项目已在 [includes/ast](../includes/ast) 中实现**完全自研的 Python 解析器**(词法器 + 语法器 + AST 构造,约 4176 LOC,`AstTest` 综合测试通过),覆盖 CPython 几乎全部语法。这是向 `TransPyV`(用 Viper 重写编译器自身)完全自举的关键准备——自举后编译器将脱离 Python 运行时,解析、类型检查、IR 生成都由原生机器码完成。详见 [13-bootstrapping.md](13-bootstrapping.md)。 + +而实际上,下文中所有提到的 `C` 语言都是为了便于使用 `C` 的底层开发者理解。由于历史原因,在旧版本中我们使用 `C` 来作为中间语言,如同旧版本的 `C++` 一样,但现在 `target="c"` 的路径已经完全删除,`C` 路径永久不再受维护。 + +### 核心设计决策 + +1. **Python 语法,LLVM 语义**:源文件是完全合法的 `Python` 语法,但通过 `t` 模块的类型注解系统赋予完全不同的语义 —— `t.CInt` 不是 `Python` 的 `int`,而是 `LLVM` 的 `i32` +2. **类型注解即编译指令**:Viper 的类型注解不是可选的提示,而是编译器生成 `LLVM IR` 的决定性依据。`x: t.CInt` 生成 `i32`,`x: t.CInt | t.CPtr` 生成 `i32*`,但无注解时会自动推导,详见下文。 +3. **两阶段编译**:先从源文件提取声明接口(`.pyi` + `.stub.ll`),再使用声明接口翻译源文件为含代码的 `.ll`。这是 `Viper` 区别于简单"`Python` 转 `C`"工具的关键架构 +4. **`SHA1` 命名空间**:每个源文件按内容 `SHA1` 哈希命名,非导出函数和结构体自动加上 `SHA1` 前缀,从根本上消除跨模块的符号冲突 +5. **万物皆数据**:为便于底层开发,实际上所有的变量和类型一般并不适用于鸭子类型,但在语义层面我们会尽可能贴近鸭子类型。 + +### 与 `Python` 的关系 + +| 特性 | `Python` | `Viper` | +|------|--------|-------| +| 类型系统 | 动态类型 | 静态类型(通过 `t` 模块注解,编译时确定) | +| 内存管理 | `GC` 自动回收 | 手动管理,无 `GC`,无运行时 | +| 运行时 | 解释执行 + 字节码 | 编译为原生机器码(通过 `LLVM`) | +| 对象模型 | 万物皆对象 | `@t.Object` 启用 OOP,`@t.CVTable` 为多态(vtable),`@t.NoVTable` 为非多态继承(C++ 风格,零运行时开销),普通 `class` 仅为结构体布局 | +| 标准库 | `Python` 标准库 | `Viper` 标准库(`includes/`)+ `ViperOS SDK` | +| 空值 | `None` | `None`(编译为 `NULL`/零值) | +| 模块系统 | 运行时 `import` | 编译时解析,插入 `.stub.ll`,`SHA1` 命名空间隔离 | + +### 与 C 的关系 + +Viper 编译后的代码在底层等价于 `C` 编译后的机器码(都经过 LLVM),但 Viper 在语言层面提供了 C 所没有的能力: + +- **SHA1 命名空间**:自动消除符号冲突,无需手动管理 `static`/命名前缀 +- **类型组合语法**:`t.CConst | t.CInt | t.CPtr` 比 `const int*` 更具组合性 +- **声明式内联汇编**:`c.Asm(f"mov {c.AsmOut(x, t.ASM_DESCR.OUTPUT_REG)}, rdi")` 比裸 `__asm__` 更安全,也更具可读性。 +- **结构化预处理**:`c.CIfdef`/`c.CEndif()` 替代 `#ifdef`/`#endif`。 +- **面向对象**:`@t.Object` + `@t.CVTable` 提供 `vtable` 支持的多态 `OOP`;`@t.NoVTable` 提供 C++ 风格的非多态继承(零 vtable 开销);PEP 695 泛型 + 递归泛型继承实现强类型自引用结构 +- **存根驱动的模块系统**:`.pyi` 存根文件实现跨模块类型解析,无需头文件 +- **更多内容**:额外更多的不依赖操作系统的函数和语法 + +## 两阶段编译流程 + +Viper 的编译由 `Projectrans.py` 驱动,分为两个阶段。这是 Viper 编译模型的核心,理解两阶段编译是理解 `t.CDefine`、`t.CExport`、`t.CInline` 等关键概念的前提。 + +``` +源文件 (.py) ────────────────────────────────────────────────────── + │ │ + │ ┌─────────────── 阶段一:声明提取 ───────────────┐ │ + │ │ │ │ + │ │ 1. 计算源文件 SHA1 │ │ + │ │ 2. 生成 .pyi(签名存根) │ │ + │ │ 3. 构建结构体注册表 │ │ + │ │ 4. 生成 .stub.ll(LLVM IR 声明) │ │ + │ │ │ │ + │ └────────────────────────────────────────────────┘ │ + │ │ + │ ┌─────────────── 阶段二:代码翻译 ───────────────┐ │ + │ │ │ │ + │ │ 1. 加载所有 .pyi 和 .stub.ll │ │ + │ │ 2. 构建共享符号表 │ │ + │ │ 3. 收集内联函数符号 │ │ + │ │ 4. 翻译源文件 → .ll(含代码) │ │ + │ │ 5. llc 编译 → .o(目标文件) │ │ + │ │ 6. ld.lld 链接 → 可执行文件 │ │ + │ │ │ │ + │ └────────────────────────────────────────────────┘ │ + │ │ + └──────────────────────────────────────────────────────────────┘ +``` + +### 阶段一:声明提取(Phase1Generator) + +阶段一的目标是从每个源文件提取**声明接口**,使其他模块在阶段二翻译时能够正确解析跨模块引用。 + +#### 步骤 1:可达文件发现与拓扑排序 + +从入口文件(`main.py` 或 `project.json` 指定)出发,通过 `import` 语句递归遍历,找出所有可达的 `.py` 源文件。然后对文件进行拓扑排序,确保被依赖的模块先被处理。 + +``` +main.py → import serial → import drivers.serial.uart.serial → ... +``` + +#### 步骤 2:生成 `.pyi` 签名存根 + +对每个源文件,计算其内容的 SHA1 哈希(16位),然后调用 `PythonToStubConverter` 生成签名存根文件 `.pyi`。 + +**SHA1 计算方式**: +```python +sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16] +``` + +**存根文件的内容规则**: + +| 源文件元素 | 存根文件中的表示 | +|-----------|----------------| +| `t.CDefine` 常量 | 保留原样(含赋值):`MAX_SIZE: t.CDefine = 1024` | +| `t.CDefine` 函数(返回 `t.CDefine` 的函数) | 保留完整函数体(宏展开需要) | +| 普通函数 | 仅保留签名,添加 `| c.State`:`def foo(x: t.CInt) -> t.CVoid | c.State: pass` | +| 类定义 | 保留成员类型注解和方法签名 | +| 全局变量 | 添加 `t.CExtern`:`x: t.CExtern | t.CInt` | +| `c.CIfdef`/`c.CEndif()` 等预处理指令 | 保留原样 | +| `import` 语句 | 保留原样 | + +**关键设计**:`t.CDefine` 常量和 `t.CDefine` 函数在存根中**保留完整定义**(包括赋值和函数体),因为它们是编译时常量/宏,其他模块引用时需要完整展开。普通函数只保留签名并标记 `c.State`(仅声明),因为函数体在阶段二生成。 + + +#### 步骤 3:构建结构体注册表 + +扫描所有已生成的 `.pyi` 文件,提取: +- **结构体名称集合**:所有非枚举、非异常的 `class` 定义 +- **枚举名称集合**:继承 `t.CEnum` 的类 +- **异常名称集合**:继承 `Exception` 的类 +- **结构体→SHA1 映射**:记录每个结构体定义在哪个模块中 + +这个注册表用于阶段一的声明生成和阶段二的类型解析,确保跨模块的结构体引用使用正确的 SHA1 命名空间。 + +#### 步骤 4:生成 `.stub.ll` LLVM IR 声明 + +对每个 `.pyi` 文件,由 `DeclarationGenerator` 生成对应的 LLVM IR 声明文件 `.stub.ll`。 + +**声明生成规则**: + +| 元素 | 生成的 LLVM IR | +|------|---------------| +| 普通函数 | `declare void @".foo"(i32)` — 非 CExport 函数加 SHA1 前缀 | +| CExport 函数 | `declare i32 @main()` — CExport 函数不加前缀,全局可见 | +| 结构体 | `%".ClassName" = type { i32, i32* }` — 类型名加 SHA1 前缀 | +| 全局变量 | `@varname = external global i32` | +| `t.CDefine` 常量 | **不生成声明**(编译时常量,直接内联展开) | +| `t.CTypedef` 别名 | **不生成声明**(类型别名,在类型解析时展开) | +| 枚举 | 为每个枚举成员生成 `@__config_EnumName_member = external global i32` | + +**增量编译**:如果 `.pyi` 或 `.stub.ll` 已存在,则跳过生成(缓存命中)。只有源文件内容变化导致 SHA1 变化时才重新生成。 + +### 阶段二:代码翻译(Phase2Translator) + +阶段二使用阶段一生成的声明接口,将源文件翻译为包含完整代码的 LLVM IR。 + +#### 步骤 1:加载声明接口 + +加载 `temp/` 目录中所有的 `.pyi` 和 `.stub.ll` 文件,构建: +- `sig_files`:SHA1 → `.pyi` 文件路径映射 +- `stub_files`:SHA1 → `.stub.ll` 文件路径映射 +- `sha1_map`:SHA1 → 源文件相对路径映射 + +#### 步骤 2:构建共享符号表 + +一次性构建所有文件共享的符号表数据,避免每个文件重复加载。将所有 `.pyi` 存根和 `.stub.ll` 声明中的类型信息注册到统一的 `SymbolTable` 中。 + +#### 步骤 3:收集内联函数符号 + +扫描 `includes/` 目录中被引用的模块,收集标记为 `t.CInline` 的函数。内联函数的完整 AST body 被保存,在翻译调用点时直接展开。值得注意的是,和 C 语言不同,`t.CInline` 不是优化建议,而是强制性的。 + +#### 步骤 4:翻译源文件 + +对每个源文件,使用 `TransPyC` 翻译器将 Python AST 翻译为 LLVM IR: +1. 将对应模块的 `.stub.ll` 声明嵌入到生成的 `.ll` 文件头部 +2. 所有被引用模块的 `.stub.ll` 声明也被嵌入 +3. 翻译 AST 节点为 LLVM IR 指令 +4. 输出 `.ll` 文件 + +#### 步骤 5:编译与链接 + +- `llc`:将每个 `.ll` 编译为 `.o` 目标文件 +- `ld.lld`:将所有 `.o` 链接为最终可执行文件 + +## SHA1 命名空间机制 + +SHA1 命名空间是 Viper 解决跨模块符号冲突的核心机制。 + +### 问题 + +在 C 语言中,多个模块可能定义同名函数或结构体,导致链接时符号冲突。C 的解决方案是 `static`(限制可见性)和命名前缀(手动避免冲突)。 + +### Viper 的解决方案 + +Viper 使用源文件内容的 SHA1 哈希作为命名空间前缀: + +``` +源文件 A.py(SHA1 = a1b2c3d4e5f6g7h8)中定义: + class Point: x: t.CInt; y: t.CInt + def draw(p: Point) -> t.CVoid: ... + +源文件 B.py(SHA1 = i9j0k1l2m3n4o5p6)中也定义: + class Point: x: t.CFloat; y: t.CFloat # 不同的结构体! + def draw(p: Point) -> t.CVoid: ... + +编译后: + A.py 的结构体 → %"a1b2c3d4e5f6g7h8.Point" = type { i32, i32 } + A.py 的函数 → declare void @"a1b2c3d4e5f6g7h8.draw"(%"a1b2c3d4e5f6g7h8.Point"*) + + B.py 的结构体 → %"i9j0k1l2m3n4o5p6.Point" = type { float, float } + B.py 的函数 → declare void @"i9j0k1l2m3n4o5p6.draw"(%"i9j0k1l2m3n4o5p6.Point"*) +``` + +**没有符号冲突**——即使两个模块定义了同名类型和函数,它们的 LLVM IR 符号也是不同的。即便有两个内容完全相同的文件也不会冲突,他们会被识别为同一个文件,然后进行单次编译。 + +### SHA1 前缀的豁免:`t.CExport` + +标记为 `t.CExport` 的函数**不加 SHA1 前缀**,保持原始函数名。这是模块向外部暴露 API/ABI 的机制: + +```python +# main.py — 入口函数,必须全局可见 +def main() -> t.CInt | t.CExport: + return 0 + +# 编译后:define i32 @main() — 无 SHA1 前缀 +``` + +```python +# serial.py — 驱动接口,对外暴露 +def init() -> t.CVoid | t.CExport: + serial_puts("init\n") + +# 编译后:define void @init() — 无 SHA1 前缀,其他模块可直接调用 +``` + +### SHA1 与增量编译 + +SHA1 同时服务于增量编译: +- 源文件内容不变 → SHA1 不变 → `.pyi` 和 `.stub.ll` 缓存命中,跳过阶段一 +- 源文件内容变化 → SHA1 变化 → 重新生成声明接口 +- `temp/_sha1_map.txt` 记录 SHA1 → 源文件路径的映射,供阶段二加载 + +理论上,如果一个未经变动的模块的依赖路径上存在变动的模块,暂时的方法是将其简单做 SHA1 替换,暨将旧 SHA1 替换为新 SHA1,但不适用于签名改变的情况,不过签名改变这个未经变动的模块必然也需要改变,但是由于宏展开的存在,不能完全保证不会发生错误情况。且依赖路径的 SHA1 替换成功性未经完整测试,可能出现未定义行为。 + +## `t.CDefine` 深度解析 + +`t.CDefine` 是 Viper 中最特殊的类型——它不是数据类型,而是**编译时元指令**,控制编译器的代码生成行为。 + +### `t.CDefine` 常量 + +```python +MAX_SIZE: t.CDefine = 1024 +PAGE_SIZE: t.CDefine = 4096 +FA_READ: t.CDefine = 0x01 +CPU_FEATURE_FPU: t.CDefine = (1 << 0) +``` + +**编译行为**: +1. **阶段一**:存根生成器保留 `t.CDefine` 常量的完整定义(包括赋值值),因为其他模块可能引用此常量 +2. **阶段一**:`.stub.ll` 声明生成器**跳过** `t.CDefine` 常量,不生成 `external global` 声明 +3. **阶段二**:翻译器遇到 `t.CDefine` 常量的引用时,直接内联其值,不生成任何加载指令 + +这意味着 `t.CDefine` 常量在 LLVM IR 层面**不存在**——它们在编译时被完全展开,等价于 C 的 `#define` 宏。 + +### `t.CDefine` 函数 + +当函数的返回类型注解包含 `t.CDefine` 时,该函数被编译器视为**宏函数**: + +```python +def MAKE_FLAG(bit) -> t.CDefine: + return (1 << bit) + +def GET_PAGE_ORDER(size) -> t.CDefine: + return size // PAGE_SIZE +``` + +**编译行为**: +1. **阶段一**:存根生成器保留 `t.CDefine` 函数的**完整函数体**(不像普通函数那样只保留签名) +2. **阶段二**:翻译器遇到 `t.CDefine` 函数的调用时,将调用内联展开为函数体的计算结果 + +`t.CDefine` 函数在 LLVM IR 中**不生成函数定义**——它们是纯粹的编译时宏。 + +### `t.CDefine` 与类型组合 + +`t.CDefine` 可以与具体类型组合使用,为常量指定底层类型: + +```python +CODE_SEG: t.CDefine | t.CUInt16T = 0x08 +DATA_SEG: t.CDefine | t.CUInt16T = 0x10 +``` + +这表示常量在类型检查时被视为 `t.CUInt16T`(`uint16_t`),但在代码生成时仍然内联展开。 + +## `t.CExport` 深度解析 + +`t.CExport` 控制函数的符号可见性,是 SHA1 命名空间的豁免机制。 + +### 语义 + +| 修饰 | LLVM IR 函数名 | 链接可见性 | 用途 | +|------|---------------|-----------|------| +| 无修饰 | `@.funcname` | 模块内部 | 模块私有函数 | +| `t.CExport` | `@funcname` | 全局可见 | 对外暴露的 API | +| `t.CExtern` | `@funcname`(仅声明) | 外部定义 | 引用外部函数 | + +### 使用场景 + +```python +# 入口函数 — 必须全局可见,链接器需要找到它 +def _start() -> t.CInt | t.CExport: + return kernel_main() + +# 驱动接口 — 其他模块/应用需要调用 +def init() -> t.CVoid | t.CExport: + uart_init() + +# 内部辅助函数 — 不需要全局可见,自动加 SHA1 前缀 +def _helper() -> t.CInt: + return 42 +``` + +### `t.CExport` 在存根中的表现 + +在 `.pyi` 存根文件中,`t.CExport` 函数与普通函数一样只保留签名(添加 `c.State`,表示单纯声明),但 `t.CExport` 标记被保留在返回类型注解中。阶段一的 `DeclarationGenerator` 检查 `t.CExport` 标记来决定是否添加 SHA1 前缀。 + +## `t.CInline` 深度解析 + +`t.CInline` 标记函数为内联函数,编译器在调用点直接展开函数体。 + +### 语义 + +```python +def fast_add(a: t.CInt, b: t.CInt) -> t.CInt | t.CInline: + return a + b +``` + +**编译行为**: +1. **阶段二预收集**:`_collect_inline_symbols` 扫描 `includes/` 目录中被引用的模块,收集所有 `t.CInline` 函数的 AST body +2. **翻译时展开**:遇到内联函数调用时,将函数体的 AST 直接嵌入调用点,替换参数为实际值 +3. **不生成独立函数**:内联函数不生成 LLVM IR 函数定义(除非也被非内联调用) + +### `t.CInline` 与 `t.CDefine` 函数的区别 + +| 特性 | `t.CInline` | `t.CDefine` 函数 | +|------|------------|-----------------| +| 展开时机 | 阶段二翻译时 | 阶段二翻译时 | +| 类型检查 | 完整的参数和返回类型检查 | 返回类型为宏,类型检查较弱 | +| 存根表示 | 签名 + `c.State` | 完整函数体 | +| LLVM IR | 可能生成函数定义(如果有非内联调用) | 不生成函数定义 | +| 适用场景 | 性能关键的短函数 | 编译时常量和宏计算 | + +## 目标平台 + +默认目标三元组:`x86_64-none-elf` + +数据布局:`e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128` + +## Hello World 示例 + +```python +import t, c + +def main() -> t.CInt | t.CExport: + print("Hello, ViperOS!\n") + return 0 +``` + +编译流程: + +1. **阶段一**:计算 `main.py` 的 SHA1,生成 `.pyi` 存根(`def main() -> t.CInt | t.CExport | c.State: pass`),生成 `.stub.ll` 声明(`declare i32 @main()`,因为是 CExport 不加前缀) +2. **阶段二**:翻译 `main.py` 为 `.ll`,嵌入 `print` 对应的 `puts`/`printf` 声明,生成 `define i32 @main()` 函数体 +3. **编译**:`llc` 将 `.ll` 编译为 `.o` +4. **链接**:`ld.lld` 链接为可执行文件 diff --git a/vpsdk/wiki/02-type-system.md b/vpsdk/wiki/02-type-system.md new file mode 100644 index 0000000..d4bcaef --- /dev/null +++ b/vpsdk/wiki/02-type-system.md @@ -0,0 +1,665 @@ +# 02 - 类型系统 + +Viper 的类型系统通过 `t` 模块提供,所有类型注解继承 `t.CType` 。大都以 `t.C{TypeName:PascalCase}`为类型,类型注解是 Viper 的核心机制,决定了变量的内存布局、大小和对齐方式。 + +## 基本类型 + +### 整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CChar` | `char` | 8 | 是 | +| `t.CShort` | `short` | 16 | 是 | +| `t.CInt` | `int` | 32 | 是 | +| `t.CLong` | `long` | 64 | 是 | +| `t.CUnsignedChar` | `unsigned char` | 8 | 否 | +| `t.CUnsignedShort` | `unsigned short` | 16 | 否 | +| `t.CUnsigned` / `t.CUnsignedInt` | `unsigned int` | 32 | 否 | +| `t.CUnsignedLong` | `unsigned long` | 64 | 否 | +| `t.CSignedChar` | `signed char` | 8 | 是 | + +值得注意的是,t.CUnsigned 一般不单独使用,而是配合其它有符号类型,替换表示无符号,比如 `t.CUnsigned | t.CInt`,由于为了便于使用一些常用类型,则将其组合为常用的 `t.CUnsignedInt` 等。 + +### 固定宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CInt8T` | `int8_t` | 8 | 是 | +| `t.CInt16T` | `int16_t` | 16 | 是 | +| `t.CInt32T` | `int32_t` | 32 | 是 | +| `t.CInt64T` | `int64_t` | 64 | 是 | +| `t.CUInt8T` | `uint8_t` | 8 | 否 | +| `t.CUInt16T` | `uint16_t` | 16 | 否 | +| `t.CUInt32T` | `uint32_t` | 32 | 否 | +| `t.CUInt64T` | `uint64_t` | 64 | 否 | + +### 最小宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CIntLeast8T` | `int_least8_t` | ≥8 | 是 | +| `t.CIntLeast16T` | `int_least16_t` | ≥16 | 是 | +| `t.CIntLeast32T` | `int_least32_t` | ≥32 | 是 | +| `t.CIntLeast64T` | `int_least64_t` | ≥64 | 是 | +| `t.CUIntLeast8T` | `uint_least8_t` | ≥8 | 否 | +| `t.CUIntLeast16T` | `uint_least16_t` | ≥16 | 否 | +| `t.CUIntLeast32T` | `uint_least32_t` | ≥32 | 否 | +| `t.CUIntLeast64T` | `uint_least64_t` | ≥64 | 否 | + +### 最快最小宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CIntFast8T` | `int_fast8_t` | ≥8 | 是 | +| `t.CIntFast16T` | `int_fast16_t` | ≥16 | 是 | +| `t.CIntFast32T` | `int_fast32_t` | ≥32 | 是 | +| `t.CIntFast64T` | `int_fast64_t` | ≥64 | 是 | +| `t.CUIntFast8T` | `uint_fast8_t` | ≥8 | 否 | +| `t.CUIntFast16T` | `uint_fast16_t` | ≥16 | 否 | +| `t.CUIntFast32T` | `uint_fast32_t` | ≥32 | 否 | +| `t.CUIntFast64T` | `uint_fast64_t` | ≥64 | 否 | + +### 最大宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CIntMaxT` | `intmax_t` | ≥64 | 是 | +| `t.CUIntMaxT` | `uintmax_t` | ≥64 | 否 | + +### stdint 短名别名 + +通过 `import stdint` 引入,这些短名是上述类型的 `t.CTypedef` 别名,便于在系统编程场景中快速使用。允许直接使用 `from stdint import *` 来直接使用。 + +#### 基本类型短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.INT` | `t.CInt` | - | +| `stdint.UINT` | `t.CUnsignedInt` | - | +| `stdint.BOOL` | `t.CInt` | 1 为真,0 为假 | +| `stdint.SHORT` | `t.CShort` | - | +| `stdint.USHORT` | `t.CUnsignedShort` | - | +| `stdint.LONG` | `t.CLong` | - | +| `stdint.ULONG` | `t.CUnsignedLong` | - | +| `stdint.LONGLONG` | `t.CLong \| t.CLong` | - | +| `stdint.ULONGLONG` | `t.CUnsignedLong \| t.CLong` | - | +| `stdint.FLOAT` | `t.CFloat` | - | +| `stdint.DOUBLE` | `t.CDouble` | - | + +#### 固定宽度短名 + +| 短名 | 等价展开 | +|------|---------| +| `stdint.INT8` | `t.CInt8T` | +| `stdint.INT16` | `t.CInt16T` | +| `stdint.INT32` | `t.CInt32T` | +| `stdint.INT64` | `t.CInt64T` | +| `stdint.UINT8` | `t.CUInt8T` | +| `stdint.UINT16` | `t.CUInt16T` | +| `stdint.UINT32` | `t.CUInt32T` | +| `stdint.UINT64` | `t.CUInt64T` | + +#### 字节与字短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.BYTE` | `t.CUnsignedChar` | 8 位无符号 | +| `stdint.WORD` | `t.CUInt16T` | 16 位无符号 | +| `stdint.DWORD` | `t.CUInt32T` | 32 位无符号 | +| `stdint.QWORD` | `t.CUInt64T` | 64 位无符号 | + +#### 字符类型短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.TCHAR` | `t.CChar` | - | +| `stdint.CHARLIST` | `str \| t.CPtr` / `list[str, None]` | - | +| `stdint.WCHAR` | `stdint.WORD` | UTF-16 代码单元 | +| `stdint.CHAR8` | `t.CChar8T` | - | +| `stdint.CHAR16` | `t.CChar16T` | - | +| `stdint.CHAR32` | `t.CChar32T` | - | + +#### 指针短名 + +| 短名 | 等价展开 | +|------|---------| +| `stdint.INTPTR` | `t.CInt \| t.CPtr` | +| `stdint.UINTPTR` | `t.CUnsignedInt \| t.CPtr` | +| `stdint.BYTEPTR` | `stdint.BYTE \| t.CPtr` | +| `stdint.SHORTPTR` | `t.CShort \| t.CPtr` | +| `stdint.USHORTPTR` | `t.CUnsignedShort \| t.CPtr` | +| `stdint.WCHARPTR` | `stdint.WORD \| t.CPtr` | +| `stdint.VOIDPTR` | `t.CVoid \| t.CPtr` | +| `stdint.INT8PTR` | `t.CInt8T \| t.CPtr` | +| `stdint.INT16PTR` | `t.CInt16T \| t.CPtr` | +| `stdint.INT32PTR` | `t.CInt32T \| t.CPtr` | +| `stdint.INT64PTR` | `t.CInt64T \| t.CPtr` | +| `stdint.UINT8PTR` | `t.CUInt8T \| t.CPtr` | +| `stdint.UINT16PTR` | `t.CUInt16T \| t.CPtr` | +| `stdint.UINT32PTR` | `t.CUInt32T \| t.CPtr` | +| `stdint.UINT64PTR` | `t.CUInt64T \| t.CPtr` | +| `stdint.CHAR8PTR` | `t.CChar8T \| t.CPtr` | +| `stdint.CHAR16PTR` | `t.CChar16T \| t.CPtr` | +| `stdint.CHAR32PTR` | `t.CChar32T \| t.CPtr` | + +#### 平台特定短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.FSIZE_t` | `stdint.DWORD` | 文件大小 | +| `stdint.LBA_t` | `stdint.DWORD` | 逻辑块地址 | + +### 平台相关类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CSizeT` | `size_t` | 64 | 否 | +| `t.CIntPtrT` | `intptr_t` | 64 | 是 | +| `t.CUIntPtrT` | `uintptr_t` | 64 | 否 | +| `t.CPtrDiffT` | `ptrdiff_t` | 64 | 是 | + +### stdint 短名别名 + +Viper 在 `stdint` 模块中提供了更简短的类型别名,通过 `t.CTypedef` 映射到对应的完整类型。使用时需 `import stdint`。 + +#### 固定宽度整数短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `INT8` | `t.CInt8T` | `int8_t` | +| `INT16` | `t.CInt16T` | `int16_t` | +| `INT32` | `t.CInt32T` | `int32_t` | +| `INT64` | `t.CInt64T` | `int64_t` | +| `UINT8` | `t.CUInt8T` | `uint8_t` | +| `UINT16` | `t.CUInt16T` | `uint16_t` | +| `UINT32` | `t.CUInt32T` | `uint32_t` | +| `UINT64` | `t.CUInt64T` | `uint64_t` | + +#### 固定宽度指针短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `INT8PTR` | `t.CInt8T \| t.CPtr` | `int8_t*` | +| `INT16PTR` | `t.CInt16T \| t.CPtr` | `int16_t*` | +| `INT32PTR` | `t.CInt32T \| t.CPtr` | `int32_t*` | +| `INT64PTR` | `t.CInt64T \| t.CPtr` | `int64_t*` | +| `UINT8PTR` | `t.CUInt8T \| t.CPtr` | `uint8_t*` | +| `UINT16PTR` | `t.CUInt16T \| t.CPtr` | `uint16_t*` | +| `UINT32PTR` | `t.CUInt32T \| t.CPtr` | `uint32_t*` | +| `UINT64PTR` | `t.CUInt64T \| t.CPtr` | `uint64_t*` | + +#### 字符类型短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `CHAR8` | `t.CChar8T` | `char8_t` | +| `CHAR16` | `t.CChar16T` | `char16_t` | +| `CHAR32` | `t.CChar32T` | `char32_t` | +| `CHAR8PTR` | `t.CChar8T \| t.CPtr` | `char8_t*` | +| `CHAR16PTR` | `t.CChar16T \| t.CPtr` | `char16_t*` | +| `CHAR32PTR` | `t.CChar32T \| t.CPtr` | `char32_t*` | + +#### 基本类型短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `INT` | `t.CInt` | `int` | +| `UINT` | `t.CUnsignedInt` | `unsigned int` | +| `BOOL` | `t.CInt` | `int`(0/1) | +| `SHORT` | `t.CShort` | `short` | +| `USHORT` | `t.CUnsignedShort` | `unsigned short` | +| `LONG` | `t.CLong` | `long` | +| `ULONG` | `t.CUnsignedLong` | `unsigned long` | +| `LONGLONG` | `t.CLong \| t.CLong` | `long long` | +| `ULONGLONG` | `t.CUnsignedLong \| t.CLong` | `unsigned long long` | +| `FLOAT` | `t.CFloat` | `float` | +| `DOUBLE` | `t.CDouble` | `double` | + +#### 平台 / Windows 风格短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `BYTE` | `t.CUnsignedChar` | `unsigned char`(8位) | +| `BYTEPTR` | `BYTE \| t.CPtr` | `unsigned char*` | +| `WORD` | `t.CUInt16T` | `uint16_t`(16位) | +| `DWORD` | `t.CUInt32T` | `uint32_t`(32位) | +| `QWORD` | `t.CUInt64T` | `uint64_t`(64位) | +| `INTPTR` | `t.CInt \| t.CPtr` | `int*` | +| `UINTPTR` | `t.CUnsignedInt \| t.CPtr` | `unsigned int*` | +| `SHORTPTR` | `t.CShort \| t.CPtr` | `short*` | +| `USHORTPTR` | `t.CUnsignedShort \| t.CPtr` | `unsigned short*` | +| `VOIDPTR` | `t.CVoid \| t.CPtr` | `void*` | +| `TCHAR` | `t.CChar` | `char` | +| `WCHAR` | `WORD` | `uint16_t`(UTF-16) | +| `WCHARPTR` | `WORD \| t.CPtr` | `uint16_t*`(UTF-16) | +| `CHARLIST` | `str \| t.CPtr` | `char*` | +| `FSIZE_t` | `DWORD` | `uint32_t` | +| `LBA_t` | `DWORD` | `uint32_t` | + +### 浮点类型 + +| Viper 类型 | C 等价 | 大小(位) | +|------------|--------|-----------| +| `t.CFloat` | `float` | 32 | +| `t.CDouble` | `double` | 64 | + +### 其他基本类型 + +| Viper 类型 | C 等价 | 说明 | +|------------|--------|------| +| `t.CVoid` | `void` | 空类型 | +| `t.CBool` | `bool` | 布尔类型(8位) | +| `t.CWCharT` | `wchar_t` | 宽字符(32位) | +| `t.CChar8T` | `char8_t` | UTF-8 字符(8位,无符号) | +| `t.CChar16T` | `char16_t` | UTF-16 字符(16位,无符号) | +| `t.CChar32T` | `char32_t` | UTF-32 字符(32位,无符号) | + +### Python 便捷映射类型 +| Python 类型| Viper 类型 | Viper 值 | +|--------|--------|------| +|`int`|`t.CInt`|-| +|`float`|`t.CFloat`|-| +|`str`|`t.CChar \| t.CPtr`|-| +|`bool`|`t.CBool`|-| +|`True`|`t.CBool`|`1`| +|`False`|`t.CBool`|`0`| +|`None`|`NULL`|`t.CIntPtr(0)`| + +对于 t.py 和 c.py 的引用,必须使用 import,不能使用 from ... import ...,也不能使用 as,因为这两者都是直接以字符串识别的。 + +## 类型组合 + +Viper 使用 Python 的 `|` 来组合类型修饰符(TypeUnion),其仅在左值注解中有效,其它位置均为位或语义(至少重载前是这样的),这是 Viper 最重要的语法特性之一。 + +### 指针类型 + +使用 `t.CPtr` 表示指针,通过 `|` 与基本类型组合: + +```python +x: t.CInt | t.CPtr # int* — 指向 int 的指针 +p: t.CVoid | t.CPtr # void* — 通用指针 +s: t.CChar | t.CPtr # char* — 字符串指针 +pp: t.CInt | t.CPtr | t.CPtr # int** — 指向指针的指针 +``` + +等价 C 代码: +```c +int* x; +void* p; +char* s; +int** pp; +``` + +同时,指针也可以用 `Ptr[T]` 的方法表示,但在 Viper 中不太推荐,虽然说这样做一直是类似工程的常见用法,但是如果此处 `T` 是一个多态,同时我们想要复用 Python 的高亮插件,插件无法识别 `Ptr[T]` 的属性取值,但是对于 `T | Ptr` 可以。 + +```python +x: t.CPtr[t.CInt] +p: t.CPtr[t.CVoid] +s: t.CPtr[t.CChar] +pp: t.CPtr[t.CPtr[t.CInt]] +pp2: t.CInt | t.CPtr[t.CPtr] # 同时也可以这么表示 +``` + +等价 C 代码: + +```c +int* x; +void* p; +char* s: +int** pp; +int** pp2; +``` + +### 类型限定符组合 + +```python +x: t.CConst | t.CInt # const int +p: t.CConst | t.CChar | t.CPtr # const char* +``` + +### 存储类组合 + +```python +x: t.CStatic | t.CInt # static int +f: t.CExtern | t.CInt # extern int(声明其存在于外部) +``` + +### 函数返回类型组合 + +```python +def foo() -> t.CInt | t.CExport: # 导出函数,返回 int + return 0 + +def bar() -> t.CVoid | t.CExtern: # 外部函数声明 + pass +``` + +### `t.State` 声明性标记 + +`t.State` 是**声明性标记类型**,表示"仅声明不定义"(语义等价于 `t.CExport | t.CExtern`)。主要用于 FFI 外部函数声明:函数体为 `pass`,编译器只生成函数原型(declare),不生成定义(define),由链接器在外部库中解析符号。 + +```python +def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG, + dwShareMode: ULONG, + lpSecurityAttributes: SECURITY_ATTRIBUTES | t.CPtr, + dwCreationDisposition: ULONG, + dwFlagsAndAttributes: ULONG, + hTemplateFile: HANDLE) -> HANDLE | t.State: + pass +``` + +等价 C 声明: + +```c +HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*, ULONG, ULONG, HANDLE); +``` + +`t.State` 是 Viper FFI 机制的核心,无需任何特殊语法,仅靠返回类型注解即可声明外部函数。详见 [08-c-operations.md 的 FFI 章节](08-c-operations.md#ffi-外部函数声明) 和 [includes/w32](../includes/w32) 的 Win32 绑定。 + +## 数组类型 + +使用 Python 的 `list[ElementType, Size]` 语法表示固定大小数组(通常的,这用于栈上和 BSS 段上): + +```python +buf: list[t.CChar, 64] # char buf[64] +ids: list[t.CInt, 10] # int ids[10] +matrix: list[t.CFloat, 16] # float matrix[16] +entries: list[idt_entry, 256] # struct idt_entry entries[256] +s: list[t.CChar, None] # 自推长度 char s[]; +``` + +等价 C 代码: +```c +char buf[64]; +int ids[10]; +float matrix[16]; +struct idt_entry entries[256]; +``` + +### 数组指针和函数指针 + +```python +lp: list[t.CInt, 12] | t.CPtr +vp: t.Callable[[t.CInt, t.CInt], t.CInt] = x # 其中 t.Callable = typing.Callable,可以直接使用。 +``` + +## 结构体类型 + +使用 `class` 定义结构体,成员通过类型注解声明: + +```python +class point: + x: t.CInt + y: t.CInt +``` + +等价 C 代码: +```c +struct point { + int x; + int y; +}; +``` + +### 带指针成员的结构体 + +```python +class node: + value: t.CInt + next: node | t.CPtr # struct node* next +``` + +### 匿名结构体成员(❌) + +> WARNING: **警告:** 由于 Viper 在声明结构体,枚举等处不需要关键字,因此 `t.Anonymouse` 是不需要使用的。此关键字不再维护,强行使用可能导致未定义行为。 + +继承 `t.Anonymous` 的类作为匿名成员嵌入: + +```python +class header(t.Anonymous): + magic: t.CUInt32T + version: t.CUInt32T + +class packet: + header # 匿名嵌入,可直接访问 packet.magic + length: t.CUInt32T +``` + +### 字节序标记 + +> WARNING: **警告:** 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。 + +使用 `t.BigEndian` 或 `t.LittleEndian` 标记成员的字节序: + +```python +class network_packet: + length: t.CUInt16T | t.BigEndian # 大端序存储 + type: t.CUInt8T # 默认小端序 +``` + +## 联合体类型 + +继承 `t.CUnion` 定义联合体: + +```python +class value(t.CUnion): + ival: t.CInt + fval: t.CFloat + pval: t.CVoid | t.CPtr +``` + +等价 C 代码: +```c +union value { + int ival; + float fval; + void* pval; +}; +``` + +## 枚举类型 + +继承 `t.CEnum` 定义枚举: + +```python +class color(t.CEnum): + RED: t.CEnum = 0 + GREEN: t.CEnum = 1 + BLUE: t.CEnum = 2 +``` + +等价 C 代码: +```c +enum color { + RED = 0, + GREEN = 1, + BLUE = 2 +}; +``` + +枚举成员可直接使用名称: + +```python +c: t.CInt = color.RED +``` + +### REnum(标签联合体) + +`t.CEnum` 是普通枚举(整数常量集合),`t.REnum` 是**标签联合体**(tagged union / Rust enum 风格)。每个变体可携带不同的 payload,通过 `match` 模式匹配分派。适用于少变体、无通用字段、match 分派的场景(如类型系统、IR 表示)。 + +```python +class LLVMType(t.REnum): + class Int: + Bits: t.CInt + class Void: + pass + +match ty: + case LLVMType.Int: + snprintf(buf, size, "i%d", ty.Bits) + case LLVMType.Void: + string.strcpy(buf, "void") +``` + +REnum 的详细用法、与 CEnum/胖节点 AST 的对比见 [05-classes.md 的 REnum 章节](05-classes.md#renum标签联合体)。 + +## Typedef + +使用 `t.CTypedef` 创建类型别名: + +```python +irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] # typedef int (*irq_handler_t)(); +``` + +或通过注解赋值: + +```python +MyStruct: t.CTypedef = original_struct # typedef struct original_struct MyStruct; +``` + +更推荐通过后者的形式,前者编译器不一定能够识别到注解是对 t.CTypedef 的,还是后面的注解的,而且容易触发未定义行为。 + +## 泛型类型(PEP 695 语法) + +Viper 支持 PEP 695 泛型语法 `class Name[T]:`,定义类型参数化的类。类型参数 `T` 在类体内可作为占位类型使用,实例化时用 `Name[ConcreteType]` 特化。 + +```python +@t.NoVTable +class GSList[T]: + Head: T | t.CPtr + Tail: T | t.CPtr + Count: t.CSizeT + +lst: GSList[GNode] | t.CPtr = GSList[GNode]() +``` + +泛型类还支持**递归泛型继承**(如 `class GNode(GSListNode[GNode])`),实现强类型自引用结构,无需 `void*` + 转型。这是 C/C++/Rust 均无法如此简洁表达的能力。 + +泛型类的完整用法、递归继承、实例化示例见 [05-classes.md 的泛型类章节](05-classes.md#泛型类pep-695-语法)。 + +## 强制类型转换 +在 Viper 中的强制类型转换有些不同,强制类型转换有两种方案,都是基于 `t.CType` 的用法,符合 Python 本意。 +对于原先就继承自 `t.CType` 的类型来说,可以直接使用 call 的方法进行类型转换,比如 +```python +x: t.CChar | t.CPtr = "Hello" +t.CVoid(x, t.CPtr) +``` +其中用法为 `t.CType(x, ...)`,此处...可以是其它的 `t.CType` 或将要提到的 `Typedef`,将其视为组合类型对 `x` 进行强制转换。 + +额外补充一句,对于比较,四则运算,可以不使用强制类型转换,而是依靠隐式类型转换,但如果是严谨数学计算,推荐还是强制类型转换避免判断出错。 + +此外,若将一个不同类型的右值赋值到左值,则会隐式将右值转换为左值的类型。 + +如果你不喜欢 `t.CVoid(x, t.CPtr)` 的方法,认为这将 `void` 和 `ptr` 分开,不便于理解,那么还有一个方法,即将二者打包为一个 Typedef,即: +```python +VOIDPTR: t.CTypedef = t.CVoid | t.CPtr +``` +则如果你想,你可以直接通过 call 的方式进行强转,比如: +```python +y: VOIDPTR = VOIDPTR(x) +``` + +## 宏定义常量 + +使用 `t.CDefine` 定义编译时常量。`t.CDefine` 不是数据类型,而是编译时元指令——标记为 `t.CDefine` 的常量在 LLVM IR 中不生成任何全局变量,而是在引用处直接内联展开。详见 [01-overview.md 中 t.CDefine 深度解析](01-overview.md#tcdefine-深度解析)。 + +```python +MAX_SIZE: t.CDefine = 1024 +PAGE_SIZE: t.CDefine = 4096 +FA_READ: t.CDefine = 0x01 +``` + +语义上等价于 C 的 `#define`: +```c +#define MAX_SIZE 1024 +#define PAGE_SIZE 4096 +#define FA_READ 0x01 +``` + +宏定义支持表达式: + +```python +CPU_FEATURE_FPU: t.CDefine = (1 << 0) +CPU_FEATURE_APIC: t.CDefine = (1 << 6) +``` + +宏定义也可与类型组合使用: + +```python +CODE_SEG: t.CDefine | t.CUInt16T = 0x08 +``` + +## 位域 + +使用 `t.Bit(n)` 定义位域成员: + +```python +class flags: + a: t.CUInt32T | t.Bit[1] # 此处,如果存入 2,二进制是 10,会被截断为 0 + b: t.CUInt32T | t.Bit[3] + c: t.CUInt32T | t.Bit[4] +``` + +## LLVM IR 类型简写 + +Viper 提供了 LLVM IR 级别的类型简写: + +```python +i1 # 1位整数 +i8 # 8位有符号整数 +i16 # 16位有符号整数 +i32 # 32位有符号整数 +i64 # 64位有符号整数 +u1 # 1位无符号整数 +u8 # 8位无符号整数 +u16 # 16位无符号整数 +u32 # 32位无符号整数 +u64 # 64位无符号整数 +``` + +仍然通过 `t.x` 的方式调用,比如 `t.i1` 等。 + +## `__attribute__` 属性 + +通过 `t.attr` 命名空间访问 GCC `__attribute__` 属性: + +```python +t.attr.packed() # __attribute__((packed)) +t.attr.aligned(16) # __attribute__((aligned(16))) +t.attr.section(".text.startup") # __attribute__((section(".text.startup"))) +t.attr.always_inline() # __attribute__((always_inline)) +t.attr.noinline() # __attribute__((noinline)) +t.attr.noreturn() # __attribute__((noreturn)) +t.attr.weak() # __attribute__((weak)) +t.attr.unused() # __attribute__((unused)) +t.attr.constructor() # __attribute__((constructor)) +t.attr.destructor() # __attribute__((destructor)) +t.attr.deprecated("use X") # __attribute__((deprecated("use X"))) +``` + +### LLVM 函数属性 + +通过 `t.attr.llvm` 命名空间指定 LLVM 函数属性: + +```python +t.attr.llvm.nobuiltin +t.attr.llvm.nounwind +t.attr.llvm.noredzone +t.attr.llvm.willreturn +t.attr.llvm.mustprogress +t.attr.llvm.optnone +t.attr.llvm.noinline +t.attr.llvm.alwaysinline +t.attr.llvm.readnone +t.attr.llvm.readonly +t.attr.llvm.writeonly +t.attr.llvm.inaccessiblememonly +t.attr.llvm.inaccessiblemem_or_argmemonly +``` + +在函数返回类型注解中使用: + +```python +def foo() -> t.CInt | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind: + return 0 +``` diff --git a/vpsdk/wiki/03-variables.md b/vpsdk/wiki/03-variables.md new file mode 100644 index 0000000..684e8d1 --- /dev/null +++ b/vpsdk/wiki/03-variables.md @@ -0,0 +1,177 @@ +# 03 - 变量声明与赋值 + +Viper 的变量声明使用 Python 的类型注解语法,通过 `t` 模块指定 C 级别的类型信息。 + +## 带注解的变量声明 + +使用 `变量名: 类型` 语法声明变量: + +```python +x: t.CInt = 0 # int x = 0; +y: t.CUInt32T = 42 # uint32_t y = 42; +name: list[t.CChar, 64] # char name[64]; +ptr: t.CVoid | t.CPtr # void* ptr; +msg: t.CConst | str = "Hello" # const char* msg = "Hello"; +``` + +### 无初始值的声明 + +未赋值的变量声明会生成零初始化的变量,此处是未定义行为,即在不同环境位置可能会覆盖0,也可能不会,最好预先复制或用 memset 清除: + +```python +count: t.CInt # int count = 0; +buffer: list[t.CChar, 256] # char buffer[256] = {0}; +``` + +### 延迟赋值 + +变量可以先声明后赋值: + +```python +x: t.CInt +x = 10 +``` + +### 指针变量 + +```python +p: t.CInt | t.CPtr # int* p; +fb: t.CVoid | t.CPtr # void* fb; +str_ptr: t.CConst | t.CChar | t.CPtr # const char* str_ptr; +``` + +### 指针变量的特殊初始化 + +使用 `t.CVoid(0, t.CPtr)` 初始化指针为 NULL: + +```python +p: t.CVoid | t.CPtr = t.CVoid(0, t.CPtr) # void* p = NULL; +``` + +或使用 `None`: + +```python +p: t.CVoid | t.CPtr = None # void* p = NULL; +``` + +## 普通赋值 + +```python +x: t.CInt = 0 +x = 42 # x = 42; +``` + +## 增强赋值 + +支持所有 Python 增强赋值运算符: + +```python +x: t.CInt = 0 +x += 1 # x += 1; +x -= 1 # x -= 1; +x *= 2 # x *= 2; +x //= 3 # x /= 3; (整数除法) +x %= 5 # x %= 5; +x <<= 1 # x <<= 1; +x >>= 1 # x >>= 1; +x |= 0xFF # x |= 0xFF; +x &= 0x0F # x &= 0x0F; +x ^= 0xAA # x ^= 0xAA; +``` + +## 全局变量 + +模块顶层声明的变量即为全局变量: + +```python +kbd_tid: t.CInt = -1 # 全局 int kbd_tid = -1; +BootInfo: bootinfo | t.CPtr # 全局 struct bootinfo* BootInfo; +``` + +### 静态全局变量 + +```python +x: t.CStatic | t.CInt = 0 # static int x = 0; +``` + +### 外部声明 + +```python +x: t.CExtern | t.CInt # extern int x; +``` + +## 常量定义 + +使用 `t.CDefine` 定义编译时常量。`t.CDefine` 不是数据类型,而是编译时元指令——常量在 LLVM IR 中不生成全局变量,而是在引用处直接内联展开。详见 [01-overview.md 中 t.CDefine 深度解析](01-overview.md#tcdefine-深度解析)。 + +```python +MAX_SIZE: t.CDefine = 1024 +PAGE_SIZE: t.CDefine = 4096 +FA_READ: t.CDefine = 0x01 +FA_WRITE: t.CDefine = 0x02 +``` + +## 字符串常量 + +Viper 中的字符串字面量编译为 C 的字符串常量(`const char*`): + +```python +msg: t.CConst | str = "Hello, World!" +serial.puts(msg) +``` + +也可以直接传递字符串字面量: + +```python +serial.puts("Hello, World!") +``` + +## 类型转换 + +使用类型构造函数进行显式类型转换: + +```python +x: t.CInt = 42 +y: t.CUInt32T = t.CUInt32T(x) # (uint32_t)x +f: t.CFloat = float(x) # (float)x +i: t.CInt = int(f) # (int)f +``` + +### 指针类型转换 + +```python +ptr: t.CVoid | t.CPtr = some_ptr +int_val: t.CUInt64T = t.CUInt64T(ptr) # (uint64_t)ptr — 指针转整数 +``` + +## 结构体实例化 + +> 此处不理解参见 ([05-classes.md 中 结构体实例化与初始化](05-classes.md#结构体实例化与初始化)) + +直接使用类名作为构造函数: + +```python +info: fat32_types.fat32_fileinfo +dp: fat32_types.fat32_dirobj +``` + +带初始化的实例化: + +```python +obj: MyClass = MyClass(arg1, arg2) +``` + +使用 `c.Addr` 获取实例指针: + +```python +obj_ptr: MyClass | t.CPtr = c.Addr(MyClass(arg1, arg2)) +``` + +## delete 语句 + +`del` 语句可用于调用对象的 `__del__` 或 `__delete__` 方法: + +```python +del obj # 调用 obj.__del__() 或 obj.__delete__() +del arr[idx] # 调用 arr.__delitem__(idx) +``` diff --git a/vpsdk/wiki/04-functions.md b/vpsdk/wiki/04-functions.md new file mode 100644 index 0000000..48da6aa --- /dev/null +++ b/vpsdk/wiki/04-functions.md @@ -0,0 +1,254 @@ +# 04 - 函数定义与调用 + +Viper 的函数定义使用 Python 的 `def` 语法,通过类型注解指定参数类型和返回类型。 + +## 基本函数定义 + +```python +def add(a: t.CInt, b: t.CInt) -> t.CInt: + return a + b +``` + +等价 C 代码: +```c +int add(int a, int b) { + return a + b; +} +``` + +## 无返回值函数 + +```python +def print_msg(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid: + serial.puts(msg) +``` + +等价 C 代码: +```c +void print_msg(const char* msg) { + serial_puts(msg); +} +``` + +## 函数修饰符 + +通过返回类型的 `|` 组合添加函数修饰符: + +### 导出函数 + +` t.CExport` 控制函数的符号可见性——标记为 `t.CExport` 的函数不加 SHA1 前缀,全局可见。详见 [01-overview.md 中 t.CExport 深度解析](01-overview.md#tcexport-深度解析)。 + +```python +def _start() -> t.CInt | t.CExport: + return 0 +``` + +### 外部函数声明 + +使用 `c.State` 表示仅声明不定义: + +```python +def isr0() -> t.CExtern | t.CVoid | c.State: pass +def isr1() -> t.CExtern | t.CVoid | c.State: pass +``` + +等价 C 代码: +```c +extern void isr0(); +extern void isr1(); +``` + +### 内联函数 + +`t.CInline` 标记函数为内联函数,编译器在调用点直接展开函数体。详见 [01-overview.md 中 t.CInline 深度解析](01-overview.md#tcinline-深度解析)。 + +```python +def fast_add(a: t.CInt, b: t.CInt) -> t.CInt | t.CInline: + return a + b +``` + +### 宏函数 + +当函数返回类型注解包含 `t.CDefine` 时,该函数被视为宏函数,不生成 LLVM IR 函数定义,在调用处内联展开。详见 [01-overview.md 中 t.CDefine 函数](01-overview.md#tcdefine-函数)。 + +```python +def MAKE_FLAG(bit) -> t.CDefine: + return (1 << bit) +``` + +### 静态函数 + +```python +def helper() -> t.CVoid | t.CStatic: + pass +``` + +## 函数属性装饰器 + +使用 `@c.Attribute` 添加 GCC `__attribute__` 属性: + +```python +@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16)) +def _start() -> t.CInt: + return 0 +``` + +等价 C 代码: +```c +__attribute__((section(".text.startup"), aligned(16))) +int _start() { + return 0; +} +``` + +### 常用属性组合 + +```python +@c.Attribute(t.attr.always_inline()) +def hot_path() -> t.CInt: + return 0 + +@c.Attribute(t.attr.noreturn()) +def panic(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid: + serial.puts(msg) + while True: pass + +@c.Attribute(t.attr.packed) +``` + +## LLVM 函数属性 + +在返回类型注解中通过 `t.attr.llvm` 指定 LLVM 属性: + +```python +def foo() -> t.CInt | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind: + return 0 +``` + +## 多返回值(CReturn) + +使用 `@c.CReturn` 装饰器实现多返回值,通过指针参数实现: + +```python +@c.CReturn(t.CInt, t.CInt) +def divmod(a: t.CInt, b: t.CInt) -> t.CVoid: + q: t.CInt = a // b + r: t.CInt = a % b + return q, r +``` + +规则: +1. `CReturn` 中的类型数量决定返回值个数 +2. 自动为每个返回类型添加指针参数(`t.CInt` → `t.CInt | t.CPtr`) +3. 参数名自动生成为 `__ReturnValue0__`、`__ReturnValue1__` 等 +4. 调用处自动传参 `&xxx` +5. 必须使用左右值赋值,返回值不能直接传入函数。 + +## 指针参数 + +```python +def read_data(buf: t.CChar | t.CPtr, size: t.CSizeT) -> t.CInt: + pass +``` + +等价 C 代码: +```c +int read_data(char* buf, size_t size); +``` + +### 输出参数模式 + +使用 `c.Addr` 传递变量地址作为输出参数: + +```python +res: t.CInt = fat32.opendir("/", c.Addr(dp)) +``` + +等价 C 代码: +```c +int res = fat32_opendir("/", &dp); +``` + +后续会使用隐式左值获取来减少 `c.Addr` 的直接使用。 + +## 函数指针类型 + +使用 `t.Callable` 定义函数指针类型: + +```python +irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] +``` + +等价 C 代码: +```c +typedef int (*irq_handler_t)(); +``` + +带参数的函数指针: + +```python +callback_t: t.Callable[[t.CInt, t.CInt], t.CVoid] +``` + +## 默认参数 + +Viper 支持函数默认参数值: + +```python +def create_window(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | str = "Window") -> t.CInt: + pass +``` + +## return 语句 + +```python +return 0 # return 0; +return # return; (void 函数) +return a, b # 多返回值(需配合 @c.CReturn) +``` + +## 全局变量声明 + +函数外的变量声明为全局变量,函数内通过 `global` 关键字访问: + +```python +BootInfo: bootinfo | t.CPtr + +def init() -> t.CVoid: + global BootInfo + BootInfo = saved_rdi +``` + +支持 func-in-func 嵌套函数。 + +## Call Func 函数调用 + +函数调用最好通过顺序调用来实现 + +```python +def add(x: int, y: int, z: int): + return (x + y) * z + +def main() -> int | t.CExport: + print(add(1, 2, 3)) # (1 + 2) * 3 = 9 +``` + +这也是 C 中的标准实现方法,但实际上,你也可以乱序传参。 + +```python +# 仍然用刚才的 add 举例 +def main() -> int | t.CExport: + print(add(z=3, x=1, y=2)) # (1 + 2) * 3 = 9 +``` + +乱序传参或带参传参对于结构体的初始化同样有效。同时,你也能够给函数指定默认值。 + +```python +def add(x: int, y: int, z: int = 1): + return (x + y) * z + +def main() -> int | t.CExport: + print(add(1, 2)) # (1 + 2) * 1 = 3 +``` + +在结构体中也可以指定默认值。 diff --git a/vpsdk/wiki/05-classes.md b/vpsdk/wiki/05-classes.md new file mode 100644 index 0000000..231beab --- /dev/null +++ b/vpsdk/wiki/05-classes.md @@ -0,0 +1,274 @@ +# 05 - 类与数据布局 + +Viper 中的 `class` 有两种用途:定义**数据布局**(结构体/联合体/枚举,本章内容),以及定义**面向对象的对象类型**(见 [06-oop.md](06-oop.md))。 + +## 结构体定义 + +普通 `class` 定义编译为 LLVM 结构体类型: + +```python +class point: + x: t.CInt + y: t.CInt +``` + +在 C 中: +```c +struct point { + int x; + int y; +}; +``` + +在 LLVM IR 中: +```llvm +%".point" = type { i32, i32 } +``` + +### 带属性的结构体 + +```python +@c.Attribute(t.attr.packed) +class idt_entry: + base_low: t.CUInt16T + selector: t.CUInt16T + ist_attr: t.CUInt8T + type_attr: t.CUInt8T + base_middle: t.CUInt16T + base_high: t.CUInt32T + reserved1: t.CUInt32T +``` + +### 结构体数组 + +```python +idt: list[idt_entry, 256] = [] +``` + +### 结构体指针成员 + +```python +class node: + value: t.CInt + next: node | t.CPtr # 指向自身的指针(有向图链表) +``` + +### 结构体实例化与初始化 + +```python +entry: idt_entry = idt_entry() +``` + +或带构造函数: + +```python +mousepoint = Point(x=1, y=2) +``` + +乱序传参或带参传参对于结构体的初始化同样有效: + +```python +entry: idt_entry = idt_entry(selector=0x08, base_low=0x0000) +``` + +也可以给结构体成员指定默认值: + +```python +class config: + width: t.CInt = 640 + height: t.CInt = 480 + depth: t.CInt = 32 +``` + +使用 `c.Addr` 获取实例指针: + +```python +obj_ptr: MyClass | t.CPtr = c.Addr(MyClass(arg1, arg2)) +``` + +### 匿名结构体成员(❌) + +> WARNING: **警告:** 由于 Viper 在声明结构体,枚举等处不需要关键字,因此 `t.Anonymouse` 是不需要使用的。此关键字不再维护,强行使用可能导致未定义行为。 + +继承 `t.Anonymous` 的类作为匿名成员嵌入: + +```python +class header(t.Anonymous): + magic: t.CUInt32T + version: t.CUInt32T + +class packet: + header # 匿名嵌入,可直接访问 packet.magic + length: t.CUInt32T +``` + +### 字节序标记 + +> WARNING: **警告:** 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。 + +使用 `t.BigEndian` 或 `t.LittleEndian` 标记成员的字节序: + +```python +class network_packet: + length: t.CUInt16T | t.BigEndian # 大端序存储 + type: t.CUInt8T # 默认小端序 +``` + +## 联合体 + +继承 `t.CUnion` 定义联合体: + +```python +class value(t.CUnion): + ival: t.CInt + fval: t.CFloat + pval: t.CVoid | t.CPtr +``` + +等价 C 代码: +```c +union value { + int ival; + float fval; + void* pval; +}; +``` + +## 枚举 + +继承 `t.CEnum` 定义枚举: + +```python +class color(t.CEnum): + RED: t.CEnum = 0 + GREEN: t.CEnum = 1 + BLUE: t.CEnum = 2 +``` + +等价 C 代码: +```c +enum color { + RED = 0, + GREEN = 1, + BLUE = 2 +}; +``` + +枚举成员可直接使用名称: + +```python +c: t.CInt = color.RED +``` + +## REnum(标签联合体) + +`t.CEnum` 是普通枚举(整数常量集合),`t.REnum` 是**标签联合体**(tagged union / Rust enum 风格)。每个变体可携带不同的 payload,通过 `match` 模式匹配分派。 + +### 适用场景 + +| 类型 | 变体数 | 通用字段 | 访问模式 | 适用 | +|------|--------|---------|---------|------| +| `t.CEnum` | 任意 | 无 | 整数比较 | 状态码、opcode、token 类型 | +| `t.REnum` | 少(~10) | 无 | match 分派 | 类型系统、AST 少变体场景 | +| 胖节点(如 AST) | 多(70+) | 有 | vtype + 直接属性 | AST 节点(见 [includes/ast](../includes/ast)) | + +### 定义 + +```python +class LLVMType(t.REnum): + class Int: # 变体 Int,携带 payload + Bits: t.CInt + + class Ptr: # 变体 Ptr,携带 payload + Pointee: LLVMType | t.CPtr # 自引用用指针 + + class Void: # 变体 Void,无 payload + pass +``` + +布局为 `{ i32 __tag, }`,所有变体共享最大变体的 payload 空间。 + +### match 分派 + +```python +match ty: + case LLVMType.Int: # 位置绑定 payload:ty.Bits 可用 + snprintf(buf, size, "i%d", ty.IntBits) + case LLVMType.Ptr: # ty.PtrPointee 可用 + TypePrint(buf, size, ty.PtrPointee) + case LLVMType.Void: + string.strcpy(buf, "void") +``` + +详见 [includes/llvmlite/__types.py](../includes/llvmlite/__types.py) 的 `LLVMType` 实现。 + +## Typedef + +使用 `t.CTypedef` 创建类型别名: + +```python +irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] # typedef int (*irq_handler_t)(); +``` + +或通过注解赋值: + +```python +MyStruct: t.CTypedef = original_struct # typedef struct original_struct MyStruct; +``` + +更推荐通过后者的形式,前者编译器不一定能够识别到注解是对 `t.CTypedef` 的,还是后面的注解的,而且容易触发未定义行为。 + +## 泛型类(PEP 695 语法) + +TransPyC 支持 PEP 695 泛型语法 `class Name[T]:`,定义类型参数化的类。 + +### 基本泛型类 + +```python +@t.NoVTable +class GSListNode[T]: + Next: T | t.CPtr + +@t.NoVTable +class GSList[T]: + Head: T | t.CPtr + Tail: T | t.CPtr + Count: t.CSizeT + def append(self, node: T): ... +``` + +### 递归泛型继承 + +泛型类可递归引用自身作为类型参数,实现强类型自引用结构(无需转型): + +```python +@t.NoVTable +class GNode(GSListNode[GNode]): # 递归泛型继承 + value: t.CInt +# 此时 GNode.Next: GNode|CPtr —— 强类型,零转型 +``` + +这是 C(`void*` + cast)、C++(模板继承但无 `@t.NoVTable` 精细控制)、Rust(无继承,需 trait bound)均无法如此简洁表达的能力。 + +### 实例化 + +```python +lst: GSList[GNode] | t.CPtr = GSList[GNode]() +node: GNode | t.CPtr = GNode() +lst.append(node) # 类型安全:append 只接受 GNode +``` + +详见 [includes/linkedlist.py](../includes/linkedlist.py) 和 [includes/vector.py](../includes/vector.py)。 + +## 位域 + +使用 `t.Bit(n)` 定义位域成员: + +```python +class flags: + a: t.CUInt32T | t.Bit[1] + b: t.CUInt32T | t.Bit[3] + c: t.CUInt32T | t.Bit[4] +``` + +注意:如果存入超出位域宽度的值,会被截断。例如 `a` 为 1 位,存入 2(二进制 `10`)会被截断为 0。 diff --git a/vpsdk/wiki/06-oop.md b/vpsdk/wiki/06-oop.md new file mode 100644 index 0000000..cdb349f --- /dev/null +++ b/vpsdk/wiki/06-oop.md @@ -0,0 +1,692 @@ +# 06 - 面向对象与运算符重载 + +Viper 中的 `class` 有两种语义:**数据布局**(结构体/联合体/枚举,见 [05-classes.md](05-classes.md))和**面向对象**。面向对象特性通过 `@t.Object` 装饰器启用,多态通过 `@t.CVTable` 装饰器启用,非多态继承(C++ 风格,无 vtable 开销)通过 `@t.NoVTable` 装饰器启用。 + +## `@t.Object` 面向对象 + +使用 `@t.Object` 装饰器启用面向对象特性,类将拥有成员方法、运算符重载、属性装饰器等 OOP 支持。编译器也会自动识别:如果结构体内有函数定义,会自动使用 `@t.Object` 行为,但在实际工程中更推荐明确标记,避免未定义行为。 + +### 基本定义 + +```python +@t.Object +class ViperKernel: + BootInfo: bootinfo | t.CPtr + VGA: vga._VGAScreenDriver | t.CPtr + VGA_drv: vga._VGAScreenDriver + + def __init__(self): + self.BootInfo = BootInfo + serial.init() + serial.puts("ViperOS VKernel Test\n") +``` + +避免在 `__init__` 中使用 `return`,虽然不会报错,但是你永远无法获得 `return` 的结果,如果需要提前结束代码流,`return` 也不失为一种办法。 + +### 成员方法 + +```python +@t.Object +class Sheet: + id: t.CInt + x: t.CInt + y: t.CInt + w: t.CInt + h: t.CInt + + def __init__(self, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt): + self.x = x + self.y = y + self.w = w + self.h = h + + def MoveTo(self, nx: t.CInt, ny: t.CInt): + self.x = nx + self.y = ny + + def Resize(self, nw: t.CInt, nh: t.CInt): + self.w = nw + self.h = nh +``` + +### 方法调用 + +```python +sheet: Sheet = Sheet(0, 0, 640, 480) +sheet.MoveTo(100, 100) +sheet.Resize(800, 600) +``` + +### 属性装饰器 + +支持 Python 的 `@property` 和 `@xxx.setter` 装饰器: + +> `@xxx.setter` 和自定义装饰器正在试验阶段,尽可能不要使用 + +```python +@t.Object +class MyObj: + _value: t.CInt + + @property + def value(self) -> t.CInt: + return self._value + + @value.setter + def value(self, v: t.CInt): + self._value = v +``` + +### 静态方法 + +```python +@staticmethod +def _yield(): + pass +``` + +### 对象指针与解引用 + +`@t.Object` 的实例通常通过指针操作: + +```python +obj_ptr: MyClass | t.CPtr = c.Addr(MyClass()) +obj: MyClass = c.Deref(obj_ptr) +``` + +### 成员访问 + +通过 `self` 访问成员变量和方法: + +```python +def method(self) -> t.CVoid: + self.x = 10 + y: t.CInt = self.y + self.DoSomething() +``` + +### 内嵌对象 + +`@t.Object` 可以包含其他对象实例(非指针): + +```python +@t.Object +class Sheet: + _surf_obj: gfx.Surface + _renderer_obj: gfx.Renderer + surf: gfx.Surface | t.CPtr + renderer: gfx.Renderer | t.CPtr + + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: t.CInt, h: t.CInt): + self._surf_obj = gfx.Surface(fb, zb, w, h) + self.surf = c.Addr(self._surf_obj) + self._renderer_obj = gfx.Renderer(self.surf) + self.renderer = c.Addr(self._renderer_obj) +``` + +### `__before_init__` 方法 + +所有类(不限 `@t.Object`/`@t.CVTable`)自动生成 `__before_init__` 方法,在 `__init__` 之前**自动填充成员默认值**,并对 `@t.CVTable` 类初始化 vtable: + +```llvm +define void @".ClassName.__before_init__"(%".ClassName"* %self) { +entry: + ; 对 @t.CVTable 类:存储 vtable 指针到 slot 0 + ; 遍历所有成员:若有默认值则 store 默认值,否则 store 零值 + ret void +} +``` + +**职责**: +- 填充类定义中声明的成员默认值(如 `x: t.CInt = 0` 中的 `0`) +- 对无默认值的成员填充类型零值 +- 对 `@t.CVTable` 类额外初始化 vtable 指针 + +**调用时机**(由编译器在构造时自动插入): +1. `alloca` 栈分配后调用一次 +2. 若存在 `__new__` 方法且返回堆指针,对堆指针再调用一次(alloca 的默认值已随栈帧丢弃,必须重新填充) +3. `__init__` 调用前 + +**就地构造**:用户也可手动调用 `__before_init__` 实现就地构造(placement new 语义),典型用法见 `mpool.alloc_buf`: + +```python +buf: Buf | t.CPtr = pool.alloc(Buf.__sizeof__()) +buf.__before_init__() # 填充默认值 +buf.__init__(args) # 用户初始化逻辑 +``` + +跨模块导入类时,导入侧也会声明 `__before_init__`,保证跨模块构造行为一致。 + +## `@t.NoVTable` 非多态继承 + +`@t.NoVTable` 启用 C++ 风格的非多态继承(对应 `struct A : B {}` 无 virtual)。与 `@t.CVTable` 的区别: + +| 特性 | `@t.NoVTable` | `@t.CVTable` | +|------|---------------|--------------| +| vtable 指针 | ❌ 无 | ✅ 自动插入 slot 0 | +| 虚派发 | ❌ 无 | ✅ 支持 | +| 方法继承 | ✅ 生成包装函数转发 | ✅ 通过 vtable | +| 字段展平 | ✅ 父类字段前置嵌入 | ✅ 父类字段前置嵌入(vtable 之后) | +| 运行时开销 | 零 | 每次虚调用一次间接跳转 | + +### 基本定义 + +```python +@t.NoVTable +class LinkedNode: + next: "LinkedNode" | t.CPtr + prev: "LinkedNode" | t.CPtr + def append(self, node: "LinkedNode" | t.CPtr): ... + +@t.NoVTable +class GNode(LinkedNode): # 非多态继承 + value: t.CInt +``` + +子类 `GNode` 的结构体布局为 `{next, prev, value}`(父类字段前置,无 vtable 指针)。 + +### 方法继承机制 + +子类调用父类方法时,编译器通过 `_generate_inherited_method_wrappers` 自动生成包装函数:将 `self` bitcast 为父类指针后尾调用父类方法。例如 `gnode.append(child)` 会生成: + +```llvm +define void @".GNode.append"(%".GNode"* %self, ...) { +entry: + %base = bitcast %".GNode"* %self to %".LinkedNode"* + call void @".LinkedNode.append"(%".LinkedNode"* %base, ...) + ret void +} +``` + +### 跨模块继承 + +跨模块父类(如 `linkedlist.LinkedNode`)的 `@t.NoVTable` 标记会在导入时被检测并记录,子类继承时同样跳过 vtable 插入。 + +### 向下转型 + +父类指针→子类指针需显式转型(生成 bitcast IR): + +```python +root: Node = Node() # Node 继承 LinkedNode +child: LinkedNode | t.CPtr = root.child +# child 是 LinkedNode*,需要转回 Node*: +node: Node | t.CPtr = (Node | t.CPtr)(child) +``` + +隐式转型仅向上(子→父)。 + +## `@t.CVTable` 虚函数表与多态 + +> 尽可能避免使用 `self[0]` 来获取结构体成员,其不稳定,也不要手动操作虚表。 + +使用 `@t.CVTable` 装饰器可以启用虚函数表支持。启用后,结构体第一个成员自动插入 `i8*` 类型的 vtable 指针。`@t.CVTable` 的操作必须配合 `@t.Object`,但有时编译器会自动识别是否存在结构体内函数,自动使用 `@t.Object`。 + +### 基本定义 + +```python +@t.Object +@t.CVTable +class Base: + def __init__(self): + pass + def method(self) -> t.CInt: + return 0 +``` + +在 LLVM IR 中,`@t.CVTable` 类的结构体类型自动插入 vtable 指针: + +```llvm +%".Base" = type { i8*, i32 } +``` + +同时,编译器会生成全局 vtable 变量: + +```llvm +@".Base_Vtable" = internal global [1 x i8*] zeroinitializer +``` + +### 继承与多态 + +虚表可以帮你使用继承语法实现多态: + +```python +@t.Object +@t.CVTable +class Base: + def __init__(self): + pass + def method(self) -> t.CInt: + return 0 + +@t.Object +@t.CVTable +class M1(Base): + def method(self) -> t.CInt: + return 1 +``` + +当 M1 被初始化时,VTable 会自动修改,这点和 C++ 相同,尽可能避免直接操作 VTable。 + +继承时,子类会自动继承父类的成员变量和方法。子类重写父类方法时,vtable 中对应位置的函数指针被子类方法替换,实现多态。 + +### VTable 运行时修改 + +如果需要临时构造一个额外的 `Base` 结构体,还可以使用: + +```python +@t.Object +@t.CVTable +class Base: + def __init__(self): + pass + def method(self) -> t.CInt: + return 0 + +def __method1(self) -> t.CInt: + return 1 + +def main() -> t.CInt | t.CExport: + b: Base = Base() + b.method = __method1 +``` + +启用 `VTable` 的表中,允许直接修改内部函数为函数指针,在底层,此操作将修改 `VTable` 来达成目的。编译器会检测当前 vtable 是否与类 vtable 相同,如果相同则创建 vtable 的副本(memcpy),避免修改影响所有实例。 + +### 虚方法调度机制 + +虚方法调用的核心流程: + +1. 从对象指针 GEP 获取第 0 个 slot(vtable 指针) +2. 加载 vtable 指针 +3. bitcast 为 `[N x i8*]*` 类型 +4. GEP 获取方法索引处的函数指针 +5. 加载函数指针 +6. bitcast 为正确的函数指针类型 +7. 间接调用 + +## 继承 + +### 结构体成员继承 + +当子类继承父类(父类在 `Gen.class_members` 中存在)时: + +- 父类的成员变量被继承到子类(排在子类成员之前) +- 父类的默认值也被继承 +- 父类的方法被继承(重命名为 `子类名.方法名`) + +```python +@t.Object +class Animal: + name: list[t.CChar, 32] + age: t.CInt + + def __init__(self, age: t.CInt): + self.age = age + + def Speak(self) -> t.CVoid: + serial.puts("...\n") + +@t.Object +class Dog(Animal): + breed: t.CInt + + def Speak(self) -> t.CVoid: + serial.puts("Woof!\n") +``` + +### 异常类继承 + +Viper 支持异常类的继承,用于 `try/except` 的异常匹配: + +```python +class MyError(Exception): + pass + +class SpecificError(MyError): + pass +``` + +详见 [09-exceptions.md](09-exceptions.md)。 + +## 运算符重载 + +`@t.Object` 类支持运算符重载。编译器在遇到运算符时,首先检查左操作数是否为结构体类型,如果是则尝试调用对应的 dunder 方法。 + +### 算术运算符重载 + +| 运算符 | Dunder 方法 | 示例 | +|--------|------------|------| +| `+` | `__add__` | `a + b` → `a.__add__(b)` | +| `-` | `__sub__` | `a - b` → `a.__sub__(b)` | +| `*` | `__mul__` | `a * b` → `a.__mul__(b)` | +| `/` | `__truediv__` | `a / b` → `a.__truediv__(b)` | +| `//` | `__floordiv__` | `a // b` → `a.__floordiv__(b)` | +| `%` | `__mod__` | `a % b` → `a.__mod__(b)` | +| `**` | `__pow__` | `a ** b` → `a.__pow__(b)` | + +示例: + +```python +@t.Object +class Vec2: + x: t.CFloat + y: t.CFloat + + def __init__(self, x: t.CFloat, y: t.CFloat): + self.x = x + self.y = y + + def __add__(self, other: Vec2) -> Vec2: + result: Vec2 = Vec2(0.0, 0.0) + result.x = self.x + other.x + result.y = self.y + other.y + return result + + def __mul__(self, scalar: t.CFloat) -> Vec2: + result: Vec2 = Vec2(0.0, 0.0) + result.x = self.x * scalar + result.y = self.y * scalar + return result +``` + +### 增量赋值运算符重载 + +| 增量运算符 | 优先 Dunder | 回退 Dunder | +|-----------|------------|------------| +| `+=` | `__iadd__` | `__add__` | +| `-=` | `__isub__` | `__sub__` | +| `*=` | `__imul__` | `__mul__` | +| `/=` | `__itruediv__` | `__truediv__` | +| `//=` | `__ifloordiv__` | `__floordiv__` | +| `%=` | `__imod__` | `__mod__` | +| `**=` | `__ipow__` | `__pow__` | + +如果未定义增量版本(如 `__iadd__`),编译器自动回退到普通版本(如 `__add__`)。 + +### 比较运算符重载 + +> **实验性**:以下比较运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。定义它们不会报错,但比较运算不会自动调用。 + +| 运算符 | Dunder 方法 | 状态 | +|--------|------------|------| +| `==` | `__eq__` | 仅类型推断识别 | +| `!=` | `__ne__` | 仅类型推断识别 | +| `<` | `__lt__` | 仅类型推断识别 | +| `<=` | `__le__` | 仅类型推断识别 | +| `>` | `__gt__` | 仅类型推断识别 | +| `>=` | `__ge__` | 仅类型推断识别 | + +### 反向运算符重载 + +> **实验性**:以下反向运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。 + +| Dunder 方法 | 预期语义 | 状态 | +|------------|---------|------| +| `__radd__` | 右操作数加法 | 仅类型推断识别 | +| `__rsub__` | 右操作数减法 | 仅类型推断识别 | +| `__rmul__` | 右操作数乘法 | 仅类型推断识别 | +| `__rtruediv__` | 右操作数真除法 | 仅类型推断识别 | +| `__rfloordiv__` | 右操作数整除 | 仅类型推断识别 | +| `__rmod__` | 右操作数取模 | 仅类型推断识别 | +| `__rpow__` | 右操作数幂运算 | 仅类型推断识别 | + +### 一元运算符重载 + +> **实验性**:以下一元运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。 + +| 运算符 | Dunder 方法 | 状态 | +|--------|------------|------| +| `-a` | `__neg__` | 仅类型推断识别 | +| `+a` | `__pos__` | 仅类型推断识别 | + +## 可调用对象 `__call__` + +```python +@t.Object +class Counter: + value: t.CInt + + def __init__(self): + self.value = 0 + + def __call__(self) -> t.CInt: + self.value += 1 + return self.value + +c: Counter = Counter() +x: t.CInt = c() # x = 1 +y: t.CInt = c() # y = 2 +``` + +## 下标访问重载 + +### `__getitem__` — 读取 + +```python +def __getitem__(self, key: t.CInt) -> t.CInt: + return self.data[key] +``` + +### `__setitem__` — 赋值 + +```python +def __setitem__(self, key: t.CInt, value: t.CInt): + self.data[key] = value +``` + +### `__delitem__` — 删除 + +```python +def __delitem__(self, key: t.CInt): + pass +``` + +## 布尔转换 `__bool__` + +```python +@t.Object +class Buffer: + size: t.CInt + data: list[t.CChar, 1024] + + def __bool__(self) -> t.CBool: + return self.size > 0 + +buf: Buffer = Buffer() +if buf: + serial.puts("buffer is not empty\n") +``` + +## 长度 `__len__` + +```python +@t.Object +class Buffer: + size: t.CInt + + def __len__(self) -> t.CInt: + return self.size + +buf: Buffer = Buffer() +n: t.CInt = len(buf) +``` + +## 字符串转换 `__str__` + +```python +@t.Object +class Point: + x: t.CInt + y: t.CInt + + def __str__(self) -> t.CConst | t.CChar | t.CPtr: + return "Point" + +p: Point = Point(1, 2) +print(p) # 调用 p.__str__() +``` + +## 绝对值 `__abs__` + +```python +@t.Object +class SignedValue: + val: t.CInt + + def __abs__(self) -> t.CInt: + if self.val < 0: + return -self.val + return self.val + +sv: SignedValue = SignedValue() +sv.val = -42 +a: t.CInt = abs(sv) # a = 42 +``` + +## 迭代器协议 + +Viper 的迭代器协议与 Python 相同,但 `__next__` 的实现方式通过 `StopIteration` 异常来结束。 + +### `__iter__` 和 `__next__` + +```python +@t.Object +class IntRange: + start: t.CInt + end: t.CInt + current: t.CInt + + def __init__(self, start: t.CInt, end: t.CInt): + self.start = start + self.end = end + self.current = start + + def __iter__(self): + return self + + def __next__(self, stop_flag) -> t.CInt: + if self.current >= self.end: + raise StopIteration + val: t.CInt = self.current + self.current += 1 + return val +``` + +### for 循环使用 + +```python +r: IntRange = IntRange(0, 10) +for i in r: + print(i) +``` + +编译器生成的等价逻辑: + +```c +IntRange r = IntRange_init(0, 10); +IntRange iter = IntRange___iter__(&r); +while (1) { + i1 stop_flag = 0; + int i = IntRange___next__(&iter, &stop_flag); + if (stop_flag == 1) break; + print_int(i); +} +``` + +## 上下文管理器 + +`with` 语句要求对象实现 `__enter__` 和 `__exit__` 方法: + +```python +@t.Object +class File: + fd: t.CInt + path: t.CConst | t.CChar | t.CPtr + + def __init__(self, path: t.CConst | t.CChar | t.CPtr, flags: t.CInt): + self.path = path + self.fd = -1 + + def __enter__(self): + self.fd = fat32.open(self.path, self.flags) + return self + + def __exit__(self): + fat32.close(self.fd) + + def read(self) -> t.CInt: + return fat32.read(self.fd) +``` + +使用方式: + +```python +with File("/test.txt", FA_READ) as f: + data: t.CInt = f.read() +``` + +编译器会自动保证 `__exit__` 在 `with` 块结束时被调用。 + +## 析构 `__del__` / `__delete__` + +```python +del obj # 调用 obj.__del__() 或 obj.__delete__() +del arr[idx] # 调用 arr.__delitem__(idx) +``` + +## Dunder 方法完整参考 + +### 已实现自动调度的 Dunder 方法 + +| Dunder 方法 | 触发语法 | 说明 | +|------------|---------|------| +| `__init__` | `ClassName(args)` | 构造函数 | +| `__before_init__` | 自动生成 | vtable 初始化,在 `__init__` 之前执行 | +| `__add__` | `a + b` | 加法 | +| `__sub__` | `a - b` | 减法 | +| `__mul__` | `a * b` | 乘法 | +| `__truediv__` | `a / b` | 真除法 | +| `__floordiv__` | `a // b` | 整除 | +| `__mod__` | `a % b` | 取模 | +| `__pow__` | `a ** b` | 幂运算 | +| `__iadd__` | `a += b` | 增量加法(回退到 `__add__`) | +| `__isub__` | `a -= b` | 增量减法(回退到 `__sub__`) | +| `__imul__` | `a *= b` | 增量乘法(回退到 `__mul__`) | +| `__itruediv__` | `a /= b` | 增量真除法(回退到 `__truediv__`) | +| `__ifloordiv__` | `a //= b` | 增量整除(回退到 `__floordiv__`) | +| `__imod__` | `a %= b` | 增量取模(回退到 `__mod__`) | +| `__ipow__` | `a **= b` | 增量幂运算(回退到 `__pow__`) | +| `__call__` | `obj(args)` | 可调用对象 | +| `__getitem__` | `obj[key]` | 下标读取 | +| `__setitem__` | `obj[key] = value` | 下标赋值 | +| `__delitem__` | `del obj[key]` | 下标删除 | +| `__bool__` | `if obj:` | 布尔转换 | +| `__len__` | `len(obj)` | 长度 | +| `__str__` | `str(obj)` / `print(obj)` | 字符串转换 | +| `__abs__` | `abs(obj)` | 绝对值 | +| `__iter__` | `for x in obj:` | 获取迭代器 | +| `__next__` | `for x in obj:` | 获取下一个值(标志位方式) | +| `__enter__` | `with obj as x:` | 上下文管理器进入 | +| `__exit__` | `with obj as x:` | 上下文管理器退出 | +| `__del__` | `del obj` | 析构 | +| `__delete__` | `del obj` | 删除描述符 | + +### 声明支持但未实现自动调度的 Dunder 方法 + +| Dunder 方法 | 预期触发语法 | 当前状态 | +|------------|------------|---------| +| `__eq__` | `a == b` | 仅类型推断识别,比较运算不自动调度 | +| `__ne__` | `a != b` | 同上 | +| `__lt__` | `a < b` | 同上 | +| `__le__` | `a <= b` | 同上 | +| `__gt__` | `a > b` | 同上 | +| `__ge__` | `a >= b` | 同上 | +| `__neg__` | `-a` | 仅类型推断识别,一元负号不自动调度 | +| `__pos__` | `+a` | 同上 | +| `__radd__` | 右操作数加法 | 仅类型推断识别 | +| `__rsub__` | 右操作数减法 | 同上 | +| `__rmul__` | 右操作数乘法 | 同上 | +| `__rtruediv__` | 右操作数真除法 | 同上 | +| `__rfloordiv__` | 右操作数整除 | 同上 | +| `__rmod__` | 右操作数取模 | 同上 | +| `__rpow__` | 右操作数幂运算 | 同上 | diff --git a/vpsdk/wiki/07-control-flow.md b/vpsdk/wiki/07-control-flow.md new file mode 100644 index 0000000..6115aee --- /dev/null +++ b/vpsdk/wiki/07-control-flow.md @@ -0,0 +1,256 @@ +# 07 - 控制流 + +Viper 支持 Python 风格的控制流语句,编译为 LLVM IR 的基本块和分支指令。 + +## 条件语句 + +### if / elif / else + +```python +if x > 0: + y = 1 +elif x == 0: + y = 0 +else: + y = -1 +``` + +等价 C 代码: +```c +if (x > 0) { + y = 1; +} else if (x == 0) { + y = 0; +} else { + y = -1; +} +``` + +### 条件表达式 + +```python +is_dir: t.CInt = 1 if (info.attr & AM_DIR) else 0 +``` + +等价 C 代码: +```c +int is_dir = (info.attr & AM_DIR) ? 1 : 0; +``` + +### None 检查 + +```python +if BootInfo != None: + paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize) +else: + paging.init(0, 0) +``` + +等价 C 代码: +```c +if (BootInfo != NULL) { + paging_init(BootInfo->MemmapAddr, BootInfo->MemmapSize); +} else { + paging_init(0, 0); +} +``` + +## 循环语句 + +### while 循环 + +```python +i: t.CInt = 0 +while i < 10: + buf[i] = 0 + i += 1 +``` + +等价 C 代码: +```c +int i = 0; +while (i < 10) { + buf[i] = 0; + i += 1; +} +``` + +### while-else + +```python +while i < count: + if found: + break +else: + serial.puts("not found\n") +``` + +`else` 块在循环正常结束(非 `break` 退出)时执行。 + +### 无限循环 + +```python +while True: + sched.Scheduler._yield() +``` + +等价 C 代码: +```c +while (1) { + scheduler_yield(); +} +``` + +### for 循环(range)(关于 for 循环的更多用法,参见 [06-oop.md 中 迭代器协议](06-oop.md#迭代器协议)) + +```python +for i in range(10): + buf[i] = 0 + +for i in range(5, 10): + buf[i] = 0 + +for i in range(0, 100, 2): + buf[i] = 0 +``` + +等价 C 代码: +```c +for (int i = 0; i < 10; i++) { + buf[i] = 0; +} + +for (int i = 5; i < 10; i++) { + buf[i] = 0; +} + +for (int i = 0; i < 100; i += 2) { + buf[i] = 0; +} +``` + +### for 循环(迭代器) + +如果类实现了 `__iter__` 和 `__next__` 方法,可以使用 `for ... in` 迭代: + +```python +container: Iter = Iter() +for item in container: + process(item) +``` + +### for-else + +```python +for i in range(count): + if items[i] == target: + break +else: + serial.puts("not found\n") +``` + +### break 和 continue + +```python +while True: + if done: + break + if skip: + continue + process() +``` + +## match 语句 + +Viper 支持 Python 3.10+ 的 `match` 语句,编译为 C 的 `switch` 语句: + +```python +match self.ctype: + case UI_LABEL: + self.RenderLabel(sh) + case UI_BUTTON: + self.RenderButton(sh) + case UI_PANEL: + self.RenderPanel(sh) + case _: + pass +``` + +等价 C 代码: +```c +switch (self->ctype) { + case UI_LABEL: + self_RenderLabel(self, sh); + break; + case UI_BUTTON: + self_RenderButton(self, sh); + break; + case UI_PANEL: + self_RenderPanel(self, sh); + break; + default: + break; +} +``` + +### match 值匹配 + +```python +match code: + case 0: + serial.puts("OK\n") + case 1: + serial.puts("Error\n") + case _: + serial.puts("Unknown\n") +``` + +### match 或模式 + +```python +match value: + case 1 | 2 | 3: + serial.puts("small\n") + case _: + serial.puts("other\n") +``` + +### fallthrough(无 break) + +默认每个 `case` 自动添加 `break`。如需 fallthrough,使用 `c.NoBreak`: + +```python +match value: + case 1: + do_one() + c.NoBreak + case 2: + do_two() +``` + +如果需要提前 break,需要使用 `c.Break()`,因为在 `Python` 中,`match` 分支不支持直接使用 `break`。 + +## assert 语句 +> 避免使用此语句,此语句未经充分测试,在多数情况下,编译器不知道如何展示结果。 + +```python +assert ptr != None, "null pointer" +``` + +编译为条件检查和错误报告。 + +## with 语句 + +`with` 语句用于资源管理,要求对象实现 `__enter__` 和 `__exit__` 方法: + +```python +with File("/test.txt", FA_READ) as f: + data: t.CInt = f.read() +``` + +等价 C 代码: +```c +File* f = File_enter(File_new("/test.txt", FA_READ)); +int data = File_read(f); +File_exit(f); +``` diff --git a/vpsdk/wiki/08-c-operations.md b/vpsdk/wiki/08-c-operations.md new file mode 100644 index 0000000..253a74c --- /dev/null +++ b/vpsdk/wiki/08-c-operations.md @@ -0,0 +1,304 @@ +# 08 - C 语言操作 + +Viper 通过 `c` 模块提供对 C 语言底层特性的访问,包括指针操作、内联汇编、预处理指令等。 + +## 指针操作 + +### 取地址 `c.Addr` + +```python +x: t.CInt = 42 +p: t.CInt | t.CPtr = c.Addr(x) # int* p = &x; +``` + +对结构体成员取地址: + +```python +res: t.CInt = fat32.opendir("/", c.Addr(dp)) # res = fat32_opendir("/", &dp); +``` + +对数组取地址: + +```python +viperlib.snprintf(c.Addr(buf), 64, "hello %d", 42) # snprintf(&buf, 64, "hello %d", 42); +``` + +### 解引用 `c.Deref` + +```python +ptr: Sheet | t.CPtr = c.Addr(sheet_obj) +obj: Sheet = c.Deref(ptr) # struct Sheet obj = *ptr; +``` + +### 内存拷贝 `c.Load` + +```python +c.Load(ptr, value) # *ptr = *value; +``` + +### 解引用赋值 `c.DerefAs` + +```python +c.DerefAs(ptr, value) # *ptr = value; +``` + + +### 赋值 `c.Set` + +```python +c.Set(target, value) # target = value; +``` + +## 内联汇编 `c.Asm` + +Viper 提供了声明式的内联汇编语法,编译为 GCC 风格的 `__asm__ __volatile__` 语句。 + +### 基本用法 + +```python +c.Asm("nop") # __asm__ __volatile__("nop"); +``` + +### 带操作数的汇编 + +使用 f-string 和 `c.AsmInp` / `c.AsmOut` 标记操作数: + +```python +saved_rdi: t.CUnsignedLong +c.Asm(f"mov {c.AsmOut(saved_rdi, t.ASM_DESCR.OUTPUT_REG)}, rdi", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RDI]) +``` + +等价 C 代码: +```c +__asm__ __volatile__( + "mov %0, rdi" + : "=r"(saved_rdi) + : + : "memory", "rdi" +); +``` + +### 输入操作数 `c.AsmInp` + +```python +msg: t.CConst | t.CChar | t.CPtr = "Hello" +c.Asm(f"mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)}", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) +``` + +### 完整示例 + +```python +c.Asm(f"""mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(log_info_fn, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) +``` + +### ASM_DESCR 约束字符 + +#### 操作数修饰符 + +| 常量 | 值 | 说明 | +|------|----|------| +| `MODIFIER_OUTPUT` | `=` | 输出操作数 | +| `MODIFIER_READWRITE` | `+` | 读写操作数 | +| `MODIFIER_INPUT` | `` | 输入操作数(默认) | +| `MODIFIER_GLOBAL` | `&` | 全局操作数 | + +#### 寄存器约束 + +| 常量 | 值 | 说明 | +|------|----|------| +| `REG_ANY` | `r` | 任何通用寄存器 | +| `REG_EAX` / `REG_RAX` | `a` | EAX/RAX | +| `REG_EBX` / `REG_RBX` | `b` | EBX/RBX | +| `REG_ECX` / `REG_RCX` | `c` | ECX/RCX | +| `REG_EDX` / `REG_RDX` | `d` | EDX/RDX | +| `REG_ESI` / `REG_RSI` | `S` | ESI/RSI | +| `REG_EDI` / `REG_RDI` | `D` | EDI/RDI | +| `REG_XMM` | `x` | XMM 寄存器 | + +#### 内存与立即数 + +| 常量 | 值 | 说明 | +|------|----|------| +| `MEMORY` | `m` | 内存操作数 | +| `IMMEDIATE` | `i` | 立即数 | +| `ANY` | `g` | 通用寄存器/内存/立即数 | + +#### 预定义组合约束 + +| 常量 | 值 | 说明 | +|------|----|------| +| `OUTPUT_REG` | `=r` | 输出,通用寄存器 | +| `OUTPUT_MEM` | `=m` | 输出,内存 | +| `OUTPUT_EAX` | `=a` | 输出,EAX/RAX | +| `INPUT_REG` | `r` | 输入,通用寄存器 | +| `INPUT_MEM` | `m` | 输入,内存 | +| `INPUT_IMM` | `i` | 输入,立即数 | + +#### 破坏描述符 + +| 常量 | 说明 | +|------|------| +| `CLOBBER_EAX` / `CLOBBER_RAX` | 破坏 EAX/RAX | +| `CLOBBER_EBX` / `CLOBBER_RBX` | 破坏 EBX/RBX | +| `CLOBBER_ECX` / `CLOBBER_RCX` | 破坏 ECX/RCX | +| `CLOBBER_EDX` / `CLOBBER_RDX` | 破坏 EDX/RDX | +| `CLOBBER_ESI` / `CLOBBER_RSI` | 破坏 ESI/RSI | +| `CLOBBER_EDI` / `CLOBBER_RDI` | 破坏 EDI/RDI | +| `CLOBBER_CC` | 破坏条件码(标志寄存器) | +| `CLOBBER_MEMORY` | 破坏内存 | +| `CLOBBER_R8` ~ `CLOBBER_R15` | 破坏 R8~R15 | + +## 预处理指令 + +Viper 通过 `c` 模块的函数调用实现 C 预处理指令。 + +### #define + +```python +c.CDefine("MAX_SIZE", 1024) # #define MAX_SIZE 1024 +``` + +上述方法容易引起未定义行为,至少是不便于理解,更常用的方式是使用类型注解: + +```python +MAX_SIZE: t.CDefine = 1024 # #define MAX_SIZE 1024 +``` + +### 条件编译 + +```python +c.CIfndef(HEADER_H) # #ifndef HEADER_H +c.CDefine(HEADER_H) # #define HEADER_H +c.CEndif() # #endif +``` + +```python +c.CIfdef(DEBUG) # #ifdef DEBUG +c.CEndif() # #endif +``` + +```python +c.CIf(VERSION > 2) # #if VERSION > 2 +c.CElif(VERSION > 1) # #elif VERSION > 1 +c.CElse() # #else +c.CEndif() # #endif +``` + +### #undef + +```python +c.Undef(MACRO_NAME) # #undef MACRO_NAME +``` + +### #error + +```python +c.CError("Platform not supported") # #error "Platform not supported" +``` + +### #pragma +> 此关键字已不再支持 +```python +c.CPragma("GCC diagnostic push") # #pragma GCC diagnostic push +``` + +### ## 连接符 +> 此方法可能已经不再支持 +```python +c.TokenPast("PREFIX_", "NAME") # PREFIX_ ## NAME +``` + +由于编译器设计,以及不需要像 `C` 一样做大量字符替换,对于上述所有的条件宏都远比 `C` 差,实际工程中不推荐使用条件宏,后期将完善宏的设计,引入模板等。 + +## FFI 外部函数声明 + +Viper 的 FFI(Foreign Function Interface)**无需任何特殊语法**——仅靠返回类型注解中的 `t.State` 标记即可声明外部函数。`t.State` 是声明性标记类型,表示"仅声明不定义"(语义等价于 `t.CExport | t.CExtern`),编译器只生成 `declare` 原型,不生成 `define` 函数体,由链接器在外部库中解析符号。 + +### 基本声明 + +函数体为 `pass`,返回类型用 `RetType | t.State`: + +```python +def isr0() -> t.CVoid | t.State: pass # extern void isr0(); — 来自汇编或外部 +def getchar() -> t.CInt | t.State: pass # extern int getchar(); — 来自 libc +``` + +编译为 LLVM IR: + +```llvm +declare void @"isr0"() ; 不加 SHA1 前缀(t.State 含 CExport 语义) +declare i32 @"getchar"() +``` + +### Win32 API 绑定示例 + +[includes/w32](../includes/w32) 中的 Win32 绑定全部通过 `t.State` 声明,零特殊语法: + +```python +def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG, + dwShareMode: ULONG, + lpSecurityAttributes: SECURITY_ATTRIBUTES | t.CPtr, + dwCreationDisposition: ULONG, + dwFlagsAndAttributes: ULONG, + hTemplateFile: HANDLE) -> HANDLE | t.State: + pass +``` + +等价 C 声明: + +```c +HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*, + ULONG, ULONG, HANDLE); +``` + +链接时通过 `project.json` 指定外部库(如 `kernel32.lib`、`user32.lib`),链接器解析这些符号。 + +### `t.State` 与 `t.CExtern` 的区别 + +| 修饰 | SHA1 前缀 | 生成内容 | 用途 | +|------|----------|---------|------| +| 无修饰 | ✅ 加前缀 | `declare` + `define` | 模块私有函数 | +| `t.CExtern` | ❌ 不加 | `declare`(引用外部) | 引用外部 C 函数 | +| `t.CExport` | ❌ 不加 | `declare` + `define` | 对外暴露 API | +| `t.State` | ❌ 不加 | 仅 `declare` | FFI 外部函数声明(= `CExport \| CExtern`) | + +### 声明外部全局变量 + +外部全局变量通过 `t.CExtern` 注解声明: + +```python +errno: t.CExtern | t.CInt # extern int errno; +``` + +详见 [02-type-system.md 的 t.State 章节](02-type-system.md#tstate-声明性标记) 和 [includes/w32](../includes/w32) 的 Win32 绑定实现。 + +## LLVM IR 内联 + +### c.LLVMIR + +直接嵌入 LLVM IR 指令来实现高效且跨平台的汇编操作: + +```python +c.LLVMIR(f"add i32 {c.LInp(a)}, {c.LInp(b)}", t.CInt) +``` + +### c.LInp / c.LOut + +标记 LLVM IR 的输入/输出操作数: + +```python +c.LInp(expr) # 输入操作数 +c.LOut(expr) # 输出操作数 +``` + +## 运算符重载 + +`@t.Object` 类支持运算符重载,详见 [06-oop.md 中 运算符重载](06-oop.md#运算符重载)。 diff --git a/vpsdk/wiki/09-exceptions.md b/vpsdk/wiki/09-exceptions.md new file mode 100644 index 0000000..7f93516 --- /dev/null +++ b/vpsdk/wiki/09-exceptions.md @@ -0,0 +1,186 @@ +# 09 - 异常处理 + +Viper 支持 Python 风格的 `try/except/finally` 异常处理,编译为基于 jmp_buf 和错误码的 C 级别异常处理机制。 + +## 异常处理机制 + +Viper 的异常处理不使用 C++ 风格的零开销异常,而是通过函数参数传递错误码和错误消息实现。编译器自动为可能抛出异常的函数添加 `__eh_msg_out__` 和 `__eh_code_out__` 参数。 + +## try / except + +```python +try: + result = risky_operation() +except ValueError: + serial.puts("ValueError occurred\n") +except OSError: + serial.puts("OSError occurred\n") +except Exception: + serial.puts("Unknown error\n") +``` + +### 内置异常类型 + +Viper 预定义了以下异常类型及其错误码: + +| 异常类型 | 错误码 | +|---------|--------| +| `ValueError` | 1 | +| `TypeError` | 2 | +| `RuntimeError` | 3 | +| `ZeroDivisionError` | 4 | +| `IndexError` | 5 | +| `KeyError` | 6 | +| `IOError` | 7 | +| `OSError` | 8 | +| `AssertionError` | 9 | +| `Exception` | 99 | + +### 自定义异常 + +```python +class DiskFullError(Exception): + pass + +class ReadOnlyError(DiskFullError): + pass +``` + +自定义异常会自动分配递增的错误码(从 100 开始),并支持继承匹配。 + +### 多异常捕获 + +```python +try: + result = operation() +except (ValueError, TypeError): + serial.puts("Value or Type error\n") +``` + +### 获取异常信息 + +```python +try: + result = operation() +except Exception as e: + serial.puts("Error occurred\n") +``` + +## try / finally + +```python +try: + result = operation() +finally: + cleanup() +``` + +`finally` 块无论是否发生异常都会执行。 + +## try / except / finally + +```python +try: + result = operation() +except ValueError: + handle_value_error() +finally: + cleanup() +``` + +## raise 语句 + +### 抛出异常 + +```python +raise ValueError("invalid value") +``` + +### 抛出自定义异常 + +```python +raise MyCustomError("something went wrong") +``` + +### 重新抛出异常 + +```python +raise +``` + +### StopIteration + +`StopIteration` 有特殊处理,用于迭代器协议: + +```python +raise StopIteration +``` + +## 异常处理的编译实现 + +### 函数签名变换 + +可能抛出异常的函数会自动添加错误输出参数: + +```python +def risky_operation() -> t.CInt: + if error: + raise ValueError("bad value") + return 0 +``` + +编译后等价于: + +```c +int risky_operation(char** __eh_msg_out__, int* __eh_code_out__) { + if (error) { + *__eh_code_out__ = 1; // ValueError code + *__eh_msg_out__ = "bad value"; + return 0; + } + return 0; +} +``` + +### 调用点变换 + +```python +try: + result = risky_operation() +except ValueError: + handle_error() +``` + +编译后等价于: + +```c +char* __eh_msg = NULL; +int __eh_code = 0; +int result = risky_operation(&__eh_msg, &__eh_code); +if (__eh_msg != NULL) { + if (__eh_code == 1) { // ValueError + handle_error(); + } +} +``` + +## 异常继承匹配 + +异常匹配支持继承体系。捕获父类异常时,子类异常也会被匹配: + +```python +class FileSystemError(Exception): + pass + +class FileNotFoundError(FileSystemError): + pass + +class PermissionError(FileSystemError): + pass + +try: + operation() +except FileSystemError: + # 也会捕获 FileNotFoundError 和 PermissionError + handle_fs_error() +``` diff --git a/vpsdk/wiki/10-imports.md b/vpsdk/wiki/10-imports.md new file mode 100644 index 0000000..0324803 --- /dev/null +++ b/vpsdk/wiki/10-imports.md @@ -0,0 +1,258 @@ +# 10 - 模块与导入系统 + +Viper 的模块系统基于 Python 的 `import` 语法,但编译时通过 TransPyC 的符号表和类型系统实现跨模块的类型解析。 + +## 导入语法 + +### 标准导入 + +```python +import t, c # Viper 内置类型和操作模块 +import asm # 汇编辅助模块 +import string # 字符串操作模块 +``` + +### 模块导入 + +```python +import bootinfo +import intr +import platform.pch as pch +import drivers.serial.uart.serial as serial +``` + +### from 导入 + +```python +from stdint import * # 导入所有 stdint 类型别名 +from drivers.video.vesafb.vga import _VGAScreenDriver +``` + +### 相对导入 + +```python +from . import submodule +from .. import parent_module +``` + +## 特殊模块 + +### `t` 模块 + +类型定义模块,提供所有 C 类型对应的 Viper 类型。每个 Viper 文件通常都需要导入: + +```python +import t +``` + +### `c` 模块 + +C 语言操作模块,提供指针操作、内联汇编、预处理指令等: + +```python +import c +``` + +### `asm` 模块 + +汇编辅助模块,提供常用汇编指令的封装: + +```python +import asm + +asm.sti() # __asm__ __volatile__("sti"); +asm.hlt() # __asm__ __volatile__("hlt"); +asm.cli() # __asm__ __volatile__("cli"); +asm.nop() # __asm__ __volatile__("nop"); +asm.BSSClean() # 清零 BSS 段 +``` + +### `string` 模块 + +字符串/内存操作模块,对应 C 标准库的 `string.h`: +> string 部分使用 x86_64 专有汇编进行加速,因此某些函数在其它架构可能需要手动重新编写。 + +```python +import string + +string.memset(c.Addr(buf), 0, 64) # memset(&buf, 0, 64); +string.memcpy(dst, src, size) # memcpy(dst, src, size); +string.memmove(dst, src, size) # memmove(dst, src, size); +string.memcmp(a, b, size) # memcmp(a, b, size); +string.strcmp(a, b) # strcmp(a, b); +string.strcpy(dst, src) # strcpy(dst, src); +``` + +### `stdint` 模块 + +提供 C `stdint.h` 中的类型别名: + +```python +from stdint import * + +# 提供以下类型别名: +# UINT8PTR, UINT16PTR, UINT32PTR, UINT64PTR +# INT8PTR, INT16PTR, INT32PTR, INT64PTR +# 等等 +``` + +### `viperlib` 模块 + +ViperOS 标准库,提供常用 C 库函数: + +```python +import viperlib + +viperlib.snprintf(c.Addr(buf), 64, "value=%d", 42) # snprintf(&buf, 64, "value=%d", 42); +``` + +### `vpsdk` 模块 + +> vpsdk 模块 专供于基于 Viper 开发的示例操作系统 ViperOS,不可用于其它平台,且需要特殊配置。 + +ViperOS 应用 SDK,提供系统调用和窗口管理: + +```python +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.dynlib as dynlib +import vpsdk.syscall as syscall +``` + +### `ast` 模块(自研解析器) + +[includes/ast](../includes/ast) 是**完全自研的 Python 解析器**(词法器 + 语法器 + AST 构造,约 4176 LOC),覆盖 CPython 几乎全部语法。这是向 `TransPyV` 完全自举的关键组件——自举后编译器不再依赖 Python 运行时解析源文件。 + +```python +from ast import parse, parse_expression + +root: AST | t.CPtr = parse(src, pool) # 解析整个源文件 +expr: AST | t.CPtr = parse_expression(src, pool) # 解析单个表达式 +``` + +AST 采用**胖节点**设计:70+ 变体共用一个 struct,用 `vtype`(`ASTVType` 枚举)区分类型,通用字段直接属性访问。详见 [13-bootstrapping.md](13-bootstrapping.md) 和 [Test/AstTest](../Test/AstTest)。 + +### `llvmlite` 模块(自研 IR 生成库) + +[includes/llvmlite](../includes/llvmlite) 是**完全自研的 LLVM IR 文本生成库**(约 1500 LOC),封装类型/值/模块/函数/基本块/构造器的创建与打印。同样是自举的关键组件。 + +```python +from llvmlite import LLVMTypeCore, LLVMModuleCore + +type_core: LLVMTypeCore = LLVMTypeCore(pool) +i32: LLVMType | t.CPtr = type_core.Int32() +ptr_ty: LLVMType | t.CPtr = type_core.Ptr(i32) + +mod_core: LLVMModuleCore = LLVMModuleCore(pool) +mod: Module | t.CPtr = mod_core.NewModule("main") +func: Function | t.CPtr = mod_core.CreateFunction(mod, "main", ret_ty) +``` + +类型系统用 `t.REnum`(`LLVMType`,少变体适合 match),指令 opcode 用 `t.CEnum`(`IROp`),对象分配用普通类 + `mpool`。详见 [includes/llvmlite/README.md](../includes/llvmlite/README.md) 的选型论证。 + +### `linkedlist` 模块(NoVTable + PEP 695 泛型示范) + +[includes/linkedlist](../includes/linkedlist.py) 提供链表实现,是 `@t.NoVTable` 非多态继承和 PEP 695 递归泛型继承的典范用例: + +```python +from linkedlist import LinkedNode, GSList + +@t.NoVTable +class Node(LinkedNode): # 非多态继承,零 vtable 开销 + value: t.CInt + +lst: GSList[Node] | t.CPtr = GSList[Node]() +node: Node | t.CPtr = Node() +node.value = 42 +lst.append(node) # 类型安全:append 只接受 Node +``` + +包含 `LinkedNode`/`SListNode`(双向/单向节点)和 `GSList[T]`(泛型链表,支持递归泛型继承 `class GNode(GSListNode[GNode])`)。详见 [05-classes.md 的泛型类章节](05-classes.md#泛型类pep-695-语法) 和 [06-oop.md 的 NoVTable 章节](06-oop.md#tnovtable-非多态继承)。 + +### `w32` 模块(Win32 FFI 绑定) + +[includes/w32](../includes/w32) 提供 Win32 API 绑定,全部通过 `t.State` 的 FFI 机制声明(零特殊语法): + +```python +from w32.win32file import CreateFileA, ReadFile, WriteFile +from w32.win32console import WriteConsole +from w32.win32memory import HeapAlloc, HeapFree +from w32.win32process import CreateProcess +from w32.win32sync import CreateMutex, WaitForSingleObject + +handle: HANDLE = CreateFileA("test.txt", GENERIC_READ, FILE_SHARE_READ, + None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, None) +``` + +子模块:`win32base`(基础类型/常量)、`win32file`(文件 IO)、`win32console`(控制台)、`win32memory`(内存)、`win32process`(进程/线程)、`win32sync`(同步对象)、`fileio`(高层文件操作封装)。详见 [08-c-operations.md 的 FFI 章节](08-c-operations.md#ffi-外部函数声明)。 + +## 模块解析流程 + +### 编译时模块加载 + +Viper 的 `import` 是编译时操作,不涉及运行时模块加载。完整的模块解析流程与两阶段编译紧密耦合: + +1. **阶段一**:从入口文件出发,通过 `import` 语句递归发现所有可达的源文件 +2. **阶段一**:对每个源文件生成 `.pyi` 签名存根和 `.stub.ll` LLVM IR 声明 +3. **阶段二**:加载所有 `.pyi` 和 `.stub.ll`,构建共享符号表 +4. **阶段二**:翻译源文件时,通过符号表解析跨模块的类型和函数引用 +5. **阶段二**:将被引用模块的 `.stub.ll` 声明嵌入到生成的 `.ll` 文件头部 + +### 类型存根文件(.pyi) + +`.pyi` 文件是模块的声明接口,由 `PythonToStubConverter` 在阶段一生成。存根文件的内容规则: + +| 元素 | 存根表示 | +|------|---------| +| `t.CDefine` 常量 | 保留完整定义(含赋值) | +| `t.CDefine` 函数 | 保留完整函数体 | +| 普通函数 | 仅签名 + `t.State` | +| 全局变量 | 添加 `t.CExtern` | +| 类定义 | 保留成员类型注解和方法签名 | + +存根文件还自动添加宏守卫(`c.CIfndef`/`c.CEndif()`),防止重复包含。 + +### SHA1 命名空间与导入 + +每个模块的函数和结构体在 LLVM IR 中自动加上 SHA1 前缀,消除跨模块符号冲突。只有标记为 `t.CExport` 的函数保持原始名称。详见 [01-overview.md 中 SHA1 命名空间机制](01-overview.md#sha1-命名空间机制)。 + +### 符号表 + +所有模块的类型信息统一存储在 `SymbolTable` 中,包含: +- 结构体/联合体/枚举定义 +- 函数签名 +- 全局变量 +- typedef 别名 +- `t.CDefine` 常量 + +### 跨模块类型引用 + +```python +# 在 fat32_types.py 中定义 +class fat32_fileinfo: + fname: list[t.CChar, 13] + attr: t.CUInt8T + file_size: t.CUInt32T + +# 在其他模块中引用 +import fat32_types +info: fat32_types.fat32_fileinfo +``` + +跨模块引用的结构体在 LLVM IR 中使用 SHA1 前缀的类型名: +```llvm +%".fat32_fileinfo" = type { [13 x i8], i8, i32 } +``` + +## 项目 includes 配置 + +在 `project.json` 中配置模块搜索路径: + +```json +{ + "includes": ["../.."], + "source_dir": "./Kernel" +} +``` + +编译器会在 `includes` 指定的目录中搜索导入的模块。 diff --git a/vpsdk/wiki/11-builtins.md b/vpsdk/wiki/11-builtins.md new file mode 100644 index 0000000..877b367 --- /dev/null +++ b/vpsdk/wiki/11-builtins.md @@ -0,0 +1,204 @@ +# 11 - 内置函数与运算符 + +Viper 支持 Python 内置函数和运算符,编译为对应的 C/LLVM 操作。 + +## 内置函数 + +### print + +```python +print("Hello, World!") # 调用 puts 或 printf +print(x, y) # 多参数输出 +``` + +### len + +```python +n: t.CInt = len(array) # 获取数组长度(编译时常量) +``` + +### sizeof + +```python +s: t.CSizeT = sizeof(t.CInt) # sizeof(int) +s: t.CSizeT = obj.__sizeof__() # sizeof(obj) +``` + +### abs + +```python +a: t.CInt = abs(x) # abs(x) +``` + +### int / float / bool + +```python +i: t.CInt = int(3.14) # (int)3.14 +f: t.CFloat = float(42) # (float)42 +b: t.CBool = bool(x) # x != 0 +``` + +### min / max + +```python +m: t.CInt = min(a, b) # a < b ? a : b +m: t.CInt = max(a, b) # a > b ? a : b +``` + +### chr / ord + +```python +c: t.CChar = chr(65) # 'A' (整数转字符) +n: t.CInt = ord('A') # 65 (字符转整数) +``` + +### range + +用于 `for` 循环: + +```python +for i in range(10): pass # for (int i = 0; i < 10; i++) +for i in range(5, 10): pass # for (int i = 5; i < 10; i++) +for i in range(0, 10, 2): pass # for (int i = 0; i < 10; i += 2) +``` + +## 运算符 + +### 算术运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a + b` | `a + b` | 加法 | +| `a - b` | `a - b` | 减法 | +| `a * b` | `a * b` | 乘法 | +| `a / b` | `a / b` | 浮点除法 | +| `a // b` | `a / b` | 整数除法 | +| `a % b` | `a % b` | 取模 | +| `a ** b` | `pow(a, b)` | 幂运算 | +| `-a` | `-a` | 取负 | +| `+a` | `+a` | 正号 | + +### 位运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a & b` | `a & b` | 按位与 | +| `a \| b` | `a \| b` | 按位或 | +| `a ^ b` | `a ^ b` | 按位异或 | +| `~a` | `~a` | 按位取反 | +| `a << n` | `a << n` | 左移 | +| `a >> n` | `a >> n` | 右移 | + +> **注意**:`|` 运算符在类型注解中表示类型组合,在表达式中表示按位或。编译器根据上下文区分。 + +### 比较运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a == b` | `a == b` | 等于 | +| `a != b` | `a != b` | 不等于 | +| `a < b` | `a < b` | 小于 | +| `a > b` | `a > b` | 大于 | +| `a <= b` | `a <= b` | 小于等于 | +| `a >= b` | `a >= b` | 大于等于 | + +### 逻辑运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a and b` | `a && b` | 逻辑与 | +| `a or b` | `a \|\| b` | 逻辑或 | +| `not a` | `!a` | 逻辑非 | + +### 增强赋值运算符 + +| Viper | C 等价 | +|-------|--------| +| `a += b` | `a += b` | +| `a -= b` | `a -= b` | +| `a *= b` | `a *= b` | +| `a //= b` | `a /= b` | +| `a %= b` | `a %= b` | +| `a <<= b` | `a <<= b` | +| `a >>= b` | `a >>= b` | +| `a &= b` | `a &= b` | +| `a \|= b` | `a \|= b` | +| `a ^= b` | `a ^= b` | + +## 类型自动转换 + +Viper 在算术运算中自动进行类型提升: + +1. **整数宽度提升**:较窄的整数类型自动扩展为较宽的类型 + - 有符号类型使用符号扩展(`sext`) + - 无符号类型使用零扩展(`zext`) + +2. **整数到浮点**:整数与浮点数运算时,整数自动转换为浮点 + +3. **浮点精度提升**:`float` 与 `double` 运算时,`float` 提升为 `double` + +## 指针算术 + +指针与整数的加法支持自动计算偏移: + +```python +buf: t.CUInt32T | t.CPtr +buf[i] = value # buf[i] = value; (自动计算偏移) +``` + +## 数组下标 + +```python +buf: list[t.CChar, 64] +buf[0] = 'H' # buf[0] = 'H'; +buf[i] = value # buf[i] = value; + +info.fname[0] # info.fname[0] (结构体数组成员访问) +``` + +## 成员访问 + +```python +obj.x # obj.x (直接成员) +obj.method() # obj.method() (方法调用) +ptr.x # ptr->x (指针自动解引用) +``` + +## 字符串字面量 + +字符串字面量编译为 C 字符串常量(`const char*`),其为只读: + +```python +serial.puts("Hello") # 传递 const char* +``` + +### 字符串索引 + +```python +msg: t.CConst | str = "Hello" +serial.puts(c.Addr(msg[4])) # 访问 msg[4] 的地址,输出 'o' +``` + +## C 标准库函数 + +Viper 内置支持以下 C 标准库函数的直接调用: + +| 函数 | 签名 | +|------|------| +| `strlen(s)` | `size_t strlen(const char* s)` | +| `memset(s, c, n)` | `void* memset(void* s, int c, size_t n)` | +| `memcpy(d, s, n)` | `void* memcpy(void* d, const void* s, size_t n)` | +| `memmove(d, s, n)` | `void* memmove(void* d, const void* s, size_t n)` | +| `memcmp(a, b, n)` | `int memcmp(const void* a, const void* b, size_t n)` | +| `strcmp(a, b)` | `int strcmp(const char* a, const char* b)` | +| `strncmp(a, b, n)` | `int strncmp(const char* a, const char* b, size_t n)` | +| `strcpy(d, s)` | `char* strcpy(char* d, const char* s)` | +| `strncpy(d, s, n)` | `char* strncpy(char* d, const char* s, size_t n)` | +| `strcat(d, s)` | `char* strcat(char* d, const char* s)` | +| `puts(s)` | `int puts(const char* s)` | +| `atoi(s)` | `int atoi(const char* s)` | +| `atol(s)` | `long atol(const char* s)` | +| `abs(x)` | `int abs(int x)` | +| `labs(x)` | `long labs(long x)` | + +部分模式比如裸机没有 libc,则不能使用这些函数,即使环境满足,Viper 标准库也可能和 C 标准库冲突。 diff --git a/vpsdk/wiki/12-project.md b/vpsdk/wiki/12-project.md new file mode 100644 index 0000000..f289240 --- /dev/null +++ b/vpsdk/wiki/12-project.md @@ -0,0 +1,252 @@ +# 12 - 项目配置与构建 + +Viper 项目使用 `project.json` 配置文件管理编译和链接参数。 + +## project.json 结构 + +```json +{ + "name": "Kernel", + "version": "1.0.0", + "source_dir": "./Kernel", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "--strip-all" + ], + "output": "kernel.bin" + }, + "includes": ["../../includes"], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} +``` + +## 配置项说明 + +### 基本信息字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `name` | string | 项目名称 | +| `version` | string | 项目版本 | +| `source_dir` | string | 源代码目录(相对于 project.json 所在目录) | +| `temp_dir` | string | 临时文件目录(存放 .pyi 存根等) | +| `output_dir` | string | 输出文件目录 | + +### 编译器配置 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `compiler.cmd` | string | 编译器命令(通常为 `llc`) | +| `compiler.flags` | string[] | 编译器标志 | + +常用 `llc` 标志: +- `-filetype=obj`:输出目标文件 +- `-mtriple=x86_64-none-elf`:目标三元组 +- `-relocation-model=static`:静态重定位 +- `-O2`:优化级别 + +### 链接器配置 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `linker.cmd` | string | 链接器命令(通常为 `ld.lld.exe`) | +| `linker.flags` | string[] | 链接器标志 | +| `linker.output` | string | 输出文件名 | + +常用 `ld.lld` 标志: +- `-m elf_x86_64`:ELF x86_64 格式 +- `-T linker.ld`:链接脚本 +- `--oformat elf64-x86-64`:输出 ELF 格式 +- `--oformat binary`:输出原始二进制(用于内核) +- `--strip-all`:去除所有符号信息 + +### 目标平台配置 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `target.triple` | string | LLVM 目标三元组 | +| `target.datalayout` | string | LLVM 数据布局字符串 | + +默认值: +- triple: `x86_64-none-elf` +- datalayout: `e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128` + +### 包含路径 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `includes` | string[] | 模块搜索路径列表 | + +### 编译选项 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `options.slice_level` | int | 切片优化级别(0-3) | +| `options.target` | string | 编译目标(目前仅支持 `llvm`) | +| `options.strict_mode` | bool | 严格模式 | + +## 切片优化级别 + +| 级别 | 名称 | 说明 | +|------|------|------| +| `0` | `no_optimize` | 无优化 | +| `1` | `conservative` | 保守优化 | +| `2` | `moderate` | 适度优化 | +| `3` | `aggressive` | 激进优化(默认) | +| `s` | `size` | 体积优化 | + +## 项目结构示例 + +### 内核项目 + +``` +VKernel/ +├── project.json # 项目配置 +├── linker.ld # 链接脚本 +├── Kernel/ # 源代码目录 +│ ├── main.py # 内核入口 +│ ├── bootinfo.py +│ ├── intr/ +│ │ ├── gdt.py +│ │ ├── idt.py +│ │ └── syscall.py +│ ├── drivers/ +│ │ ├── serial/ +│ │ ├── video/ +│ │ ├── storage/ +│ │ └── ... +│ └── ... +├── temp/ # 临时文件(自动生成) +│ ├── .pyi # 签名存根(阶段一生成) +│ ├── .stub.ll # LLVM IR 声明(阶段一生成) +│ └── _sha1_map.txt # SHA1→源文件路径映射 +└── output/ # 输出文件(自动生成) + ├── kernel.bin # 内核二进制 + ├── *.deps.json # 依赖信息 + └── linker.ld # 生成的链接脚本 +``` + +### 应用项目 + +``` +HelloWorld/ +├── project.json # 项目配置 +├── linker.ld # 链接脚本 +├── main.py # 应用入口 +├── temp/ # 临时文件 +└── output/ # 输出文件 + └── helloworld.elf # ELF 可执行文件 +``` + +### 库项目 + +``` +SerialLogger/ +├── project.json # 项目配置 +├── linker.ld # 链接脚本 +├── serial_logger.py # 库源码 +├── temp/ # 临时文件 +└── output/ # 输出文件 +``` + +## 链接脚本 + +ViperOS 使用自定义链接脚本控制内存布局: + +```ld +ENTRY(_start) + +SECTIONS +{ + . = 0x100000; /* 加载地址 */ + + .text : { + *(.text.startup) + *(.text*) + } + + .rodata : { + *(.rodata*) + } + + .data : { + *(.data*) + } + + .bss : { + *(.bss*) + *(COMMON) + } +} +``` + +## 编译命令 + +TransPyC 编译器的基本用法(注:适用于单个文件,而不适用于工程): + +```bash +python TransPyC.py -f InputFile -o OutputFile [options] +``` + +### 命令行参数 + +| 参数 | 说明 | +|------|------| +| `-f` | 输入文件路径 | +| `-o` | 输出文件路径 | +| `-wh` | 头文件列表 | +| `-debug` | 调试输出文件 | +| `-cc` | 编译命令 | +| `-cflags` | 编译标志 | +| `-run` | 编译后运行 | +| `-args` | 运行参数 | +| `-h` | 辅助文件(C 或 Python) | + +工程编译使用: +```bash +python Projectrans.py xxx.json +``` +即可 + +## 增量编译 + +TransPyC 支持基于 SHA1 的增量编译,与两阶段编译模型紧密耦合: + +### 阶段一增量 + +- 每个源文件按内容计算 SHA1 哈希(16位) +- 如果 `.pyi` 和 `.stub.ll` 已存在,则跳过生成(缓存命中) +- 只有源文件内容变化导致 SHA1 变化时才重新生成声明接口 +- `temp/_sha1_map.txt` 记录 SHA1 → 源文件路径的映射,格式为 `:` + +### 阶段二增量 + +- 阶段二加载 `temp/_sha1_map.txt` 重建 SHA1 映射 +- 过滤掉不在当前 SHA1 映射中的旧 `.pyi` 和 `.stub.ll` 文件 +- 共享符号表一次性构建,避免每个文件重复加载 + +### SHA1 命名空间与增量编译的协同 + +SHA1 同时服务于命名空间隔离和增量编译: +- **命名空间**:非 `t.CExport` 函数和结构体使用 SHA1 前缀,消除符号冲突 +- **增量编译**:SHA1 不变则缓存命中,SHA1 变化则重新生成 + +详见 [01-overview.md 中 SHA1 命名空间机制](01-overview.md#sha1-命名空间机制)。 diff --git a/vpsdk/wiki/13-bootstrapping.md b/vpsdk/wiki/13-bootstrapping.md new file mode 100644 index 0000000..acddd9d --- /dev/null +++ b/vpsdk/wiki/13-bootstrapping.md @@ -0,0 +1,252 @@ +# TransPyC 自举可行性分析 + +> **状态**:进行中 +> **创建时间**:2026-06-26 +> **最后更新**:2026-06-30 +> **结论**:完全自举可行,核心障碍已被攻克,剩余工作明确 + +--- + +## 1. 背景 + +自举(bootstrapping)指用 TransPyC 语言重写 TransPyC 编译器自身,然后用现有编译器编译,得到新的原生编译器。本文档分析自举的可行性、进展与剩余工作。 + +**当前目标**:完全自举。TransPyC 的定位是独立的系统级语言,不是"Python → 原生 的工具"。自举是语言成熟度的证明,也是脱离 Python 运行时性能限制的必经之路。 + +--- + +## 2. LOC 统计 + +### 2.1 编译器主体(lib 目录) + +| 子目录 | 文件数 | LOC | 说明 | +|--------|--------|-----|------| +| `lib/core` | 64 | 28,610 | 编译器核心 | +| ├─ `core/Handles` | 30 | 18,885 | AST 节点处理器(最大模块) | +| ├─ `core/LLVMCG` | 8 | 2,782 | LLVM 代码生成 | +| └─ `core/Translator` | 6 | 1,751 | 翻译器框架 | +| `lib/Projectrans` | 6 | 4,371 | 项目级翻译(Phase1/Phase2) | +| `lib/StubGen` | 4 | 1,187 | Stub 生成器 | +| `lib/Shell` | 4 | 630 | CLI 入口 | +| `lib/includes` | 2 | 1,609 | t.py(968) + c.py(641) 类型定义 | +| `lib/utils` | 1 | 40 | 辅助 | +| `lib/constants` | 1 | 36 | 配置 | +| **lib 总计** | **80** | **36,483** | | + +### 2.2 Top 15 文件 + +| 文件 | LOC | +|------|-----| +| `lib/core/Handles/HandlesExprCall.py` | 3,153 | +| `lib/Projectrans/Phase2Translator.py` | 2,693 | +| `lib/core/Handles/HandlesImports.py` | 1,994 | +| `lib/core/Handles/HandlesFunctions.py` | 1,612 | +| `lib/core/Handles/HandlesAssign.py` | 1,544 | +| `lib/core/Handles/HandlesExprAttr.py` | 1,168 | +| `lib/core/Handles/HandlesClassDef.py` | 1,039 | +| `lib/core/Handles/HandlesFor.py` | 997 | +| `lib/core/Handles/HandlesTypeMerge.py` | 991 | +| `lib/includes/t.py` | 968 | +| `lib/StubGen/Converter.py` | 893 | +| `lib/core/Handles/HandlesExprBuiltin.py` | 868 | +| `lib/Projectrans/DeclarationGenerator.py` | 845 | +| `lib/core/Handles/HandlesBase.py` | 756 | +| `lib/core/stub_generator.py` | 699 | + +### 2.3 运行时库(includes 目录) + +| 模块 | LOC | 说明 | +|------|-----|------| +| `includes/numpy/__init__.py` | 1,016 | ndarray n 维数组 | +| `includes/zlib/` | ~1,900 | 完整 deflate/inflate/huffman/checksum | +| `includes/ast/` | 4,176 | **自研 Python 解析器(自举组件)** | +| `includes/llvmlite/` | ~1,500 | **自研 LLVM IR 生成库(自举组件)** | +| `includes/linkedlist.py` | ~400 | 泛型链表(@t.NoVTable + PEP 695) | +| `includes/vipermath.py` | 450 | 数学库 | +| ...(30+ 文件) | ... | mpool/mbuddy/string/stdio/json/w32 等 | +| **includes 总计** | **~12,000** | | + +### 2.4 全项目总计 + +``` +全项目 .py 总计:~48,000 LOC +``` + +**自举相关核心数字:** +- 编译器主体(lib 不含 t.py/c.py):~34,874 LOC +- 自举基础组件(includes/ast + includes/llvmlite):~5,700 LOC ✅ 已完成 +- 运行时库(includes 其余):~9,500 LOC + +--- + +## 3. 自举进展(已完成的核心组件) + +### 3.1 ✅ includes/ast —— Python 解析器自举 + +**状态**:完成,AstTest 通过(解析 CPython 几乎全部语法) + +| 文件 | LOC | 功能 | +|------|-----|------| +| `__tokens.py` | ~600 | TokenType/Keyword/TokOp 枚举 + 查找表 | +| `__nodes.py` | ~1,400 | AST 胖节点 + ASTVType/ASTCtx/OpKind CEnum | +| `__lexer.py` | ~800 | 词法分析器 | +| `__parser.py` | 4,176(含表格) | 递归下降语法分析器 | + +**覆盖语法**:装饰器、async/await、match(含复杂模式 `[x,*rest]`/`{'type':t,**rest}`/`Point(x,y)`/`0|1|2`)、try/except/finally/raise from、with、lambda、列表/集合/字典/生成器推导式、f-string(含 `!r`/`!s` 转换和 `:>{width}` 格式说明)、链式比较、walrus、yield from、global/nonlocal、切片、嵌套三元。 + +**设计要点**: +- 胖节点设计(所有节点共用一个 `AST` struct,用 `vtype` 区分)—— 避免 REnum 在 70+ 变体下的内存膨胀 +- 从 `mpool.MPool` 分配,零 Python 运行时依赖 +- API 与 Python `ast` 模块对齐:`parse(src, pool) -> AST` + +### 3.2 ✅ includes/llvmlite —— LLVM IR 生成库自举 + +**状态**:完成,LLvmLiteTest 18/18 通过,TransPyV 自举实验跑通 + +| 文件 | 功能 | +|------|------| +| `__types.py` | `LLVMType` REnum + 类型构造/打印 | +| `__values.py` | Value/Constant/SSA 值表示 | +| `__function.py` | Function + BasicBlock + 参数管理 | +| `__module.py` | Module 容器 + 目标三元组 + 输出 .ll | +| `__builder.py` | IRBuilder(指令发射 + SSA 命名) | + +**设计要点**: +- `LLVMType` 用 `t.REnum`(tagged union)—— 少变体、无通用字段、match 分派的理想场景 +- 指令 opcode 用 `t.CEnum`(IROp)—— 类型安全且零开销 +- IR 文本生成用 `viperlib.snprintf` 格式化,避免手写逐字符拼接 +- 对象分配用普通类 + `mpool`,不持有全局 `mbuddy` + +**验证**:[TransPyV/App/main.py](../TransPyV/App/main.py) 10 个测试覆盖类型打印、值打印、build_add、if-else、模块打印、lli 实际执行。TransPyV 程序运行时自己生成合法 IR,被 `lli` 成功执行。 + +### 3.3 ✅ 基础设施库 + +以下 includes/ 库为自举后的编译器提供数据结构和系统调用基础: + +| 库 | 自举中的作用 | +|----|-------------| +| `linkedlist.py` | AST 节点树、符号表链、IR 指令链的容器(`@t.NoVTable` + PEP 695 递归泛型继承) | +| `mpool.py` / `mbuddy.py` | 编译器内存管理(替代 Python GC) | +| `string.py` / `viperlib.py` | 字符串处理 + snprintf(替代 Python 字符串方法) | +| `w32/` | FFI 声明,自举后编译器调用 llc/clang/linker 的基础 | +| `json/` | 符号表序列化(替代 pickle) | + +--- + +## 4. 障碍分析(修正版) + +原版本文档列出的"三大致命依赖"已被逐一攻克或降级: + +### 4.1 ~~llvmlite 依赖(原:致命)~~ → ✅ 已解决 + +**原问题**:编译器通过 `llvmlite.ir` 生成 IR,TransPyC 无法调用 Python 库。 + +**解决**:`includes/llvmlite/` 用 TransPyC 自身实现了 LLVM IR 文本生成库,API 与 Python `llvmlite.ir` 对齐。采用"字符串拼 IR"路径(路径 A),不绑定 LLVM C API。18/18 测试通过,TransPyV 自举实验验证可生成可执行 IR。 + +### 4.2 ~~ast 模块依赖(原:致命)~~ → ✅ 已解决 + +**原问题**:编译器用 Python `ast` 模块解析源码,自举需自己写 Python 解析器(预计 5,000-10,000 LOC)。 + +**解决**:`includes/ast/` 4,176 LOC 实现完整 Python 解析器,AstTest 解析 CPython 凥乎全部语法通过。预言已兑现,且实际规模(4,176 LOC)低于预估下限(5,000 LOC)。 + +### 4.3 ~~动态反射 4241 处(原:致命)~~ → ⚠️ 降级为可控 + +**原问题**:`getattr`/`setattr`/`hasattr`/`isinstance`/`issubclass` 共 4,241 处,静态语言无法直接表达。 + +**修正分析**:4241 这个数字按 Python 语法 grep 统计,未区分"语义上需要反射"和"用反射偷懒"。实际分类: + +| 用法 | 数量估计 | 是否真反射 | TransPyC 解法 | +|------|----------|-----------|--------------| +| `isinstance(node, ast.Xxx)` AST 节点分派 | ~2000 | ❌ 否 | 胖节点 `node.vtype == ASTVType.Xxx` 整数比较 | +| `isinstance(x, ir.IntType)` IR 类型分派 | ~500 | ❌ 否 | REnum `match x: case LLVMType.Int:` tag 比较 | +| `getattr(t, name, None)` 字符串→类型查表 | ~35 | ❌ 否 | 注册表 `t_type_registry: dict[str, t.CInt]` | +| `CTypeInfo.__getattr__` 门面路由 | ~100 | ✅ 是 | 重构为 REnum + match 显式分派 | +| `copy.deepcopy` / `pickle` | ~10 | ✅ 是 | 已解决(`__getstate__/__setstate__`) | +| `hasattr` 防御性检查 | ~1500 | ⚠️ 多数可消除 | 显式字段声明 + None 检查 | + +**真实反射障碍约 200-500 处**,集中体现在 `CTypeInfo` 门面模式(FIELD_ROUTES 动态路由到 `_ts`/`_sm`)。重构方案:用 REnum + match 替换 `__getattr__` 路由,已在 `includes/llvmlite/__types.py` 验证此范式可行。 + +### 4.4 Python 标准库依赖(原:严重)→ ⚠️ 部分已解决 + +| Python 库 | 用途 | TransPyC 现状 | +|-----------|------|--------------| +| `ast` | 源码解析 | ✅ `includes/ast/` | +| `llvmlite.ir` | LLVM IR 生成 | ✅ `includes/llvmlite/` | +| `json` | 序列化 | ✅ `includes/json/` | +| `os`/`sys` | 文件/路径 | ⚠️ `includes/os/` + `w32/`(Windows) | +| `subprocess` | 调用 llc/clang | ❌ 待实现(w32 CreateProcess 可基础) | +| `pickle` | 符号表序列化 | ❌ 待实现(json 替代) | +| `concurrent.futures` | 并行编译 | ❌ 待实现(w32 线程可基础) | +| `re` | 正则表达式 | ❌ 待实现 | +| `traceback` | 异常栈 | ❌ 待实现 | + +--- + +## 5. 自举路径 + +### 5.1 路径 A:完全自举(当前目标,可行) + +**已完成**: +1. ✅ `includes/ast/` Python 解析器(4,176 LOC) +2. ✅ `includes/llvmlite/` LLVM IR 生成库(~1,500 LOC) +3. ✅ 基础设施库(linkedlist/mpool/mbuddy/string/json/w32) + +**剩余工作**: +1. **CTypeInfo 门面重构**(~200-500 处真反射 → REnum + match) +2. **lib/core 重写**(~20K LOC TransPyC,含 Handles/LLVMCG/Translator) +3. **lib/Projectrans 重写**(~5K LOC,Phase1/Phase2/DeclarationGenerator) +4. **lib/StubGen 重写**(~2K LOC) +5. **Python 标准库缺口补齐**(os/subprocess/re/pickle,~5K LOC) + +**预计新增代码量**:30-35K LOC TransPyC。与已完成的 includes/ast(4,176 LOC)+ includes/llvmlite(~1,500 LOC)+ 其他基础库为同一量级——已经证明过两次能做这件事。 + +### 5.2 关键技术验证点(已完成) + +以下能力已在 includes/ 库中验证,证明 TransPyC 能承载编译器复杂度: + +- **复杂泛型容器**:`linkedlist.py` 的递归泛型继承 `class GNode(GSListNode[GNode])`(C/C++/Rust 均无法如此简洁表达) +- **复杂算法**:`zlib/` 的完整 deflate/inflate/huffman(~1,900 LOC 生产级压缩) +- **n 维数组**:`numpy/__init__.py` 的 ndarray + 运算符重载 + `__new__` 堆分配 +- **平台 FFI**:`w32/` 的 Win32 API 绑定(file/memory/process/sync/console) +- **LLVM IR 生成**:`llvmlite/` 的 REnum 类型系统 + IRBuilder + ModulePrint + +--- + +## 6. 自举的意义 + +1. **性能**:当前 Python 编译器翻译 6 个文件 0.6s,全量编译 36K LOC 编译器自身时 Python 解释器开销显著。自举后编译器是原生码,编译速度数量级提升。 + +2. **部署**:当前分发需 Python 运行时 + llvmlite + ast 依赖。自举后只需一个可执行文件 + llc/clang——系统级语言编译器该有的形态。 + +3. **语言成熟度证明**:能写自己的编译器是系统级语言的"成人礼"。C、C++、Rust、Go、Zig 全都走过这条路。 + +4. **dogfooding 闭环**:自举后每次改编译器都用编译器自己编译自己,持续暴露 bug 和性能瓶颈,形成正反馈。`includes/ast` 和 `includes/llvmlite` 的开发过程已经产出了大量深层 bug 修复(REnum 变体名冲突、跨模块继承字段展平、NoVTable OOP 方法继承等)。 + +--- + +## 7. 结论 + +| 维度 | 评估 | +|------|------| +| 理论可行性 | ✅ 可行(图灵完备) | +| 实践可行性 | ✅ 完全自举可行(核心障碍已攻克) | +| 当前进展 | ⚠️ 前端(ast)+ 后端(llvmlite)+ 基础库已就位,剩中间层(lib/core)重构 | +| 剩余工作量 | ~30-35K LOC TransPyC 重写 + CTypeInfo 门面重构 | +| 推荐策略 | 路径 A:完全自举,按 lib/core → lib/Projectrans → lib/StubGen 顺序推进 | + +**核心判断**:原版本文档的"三大致命依赖"结论已全部失效——llvmlite 和 ast 已被 includes/ 下的自研库替代,4241 处反射中 90% 是工程偷懒而非语义需要。完全自举从"几乎不可能"变为"明确可行",剩余工作是体量问题而非技术障碍。 + +--- + +## 8. 附录:原版本文档的修正说明 + +本文档(2026-06-30 版)修正了 2026-06-26 原版的以下错误判断: + +| 原版断言 | 修正 | +|---------|------| +| "完全自举几乎不可能" | 完全自举可行,核心障碍已攻克 | +| "llvmlite 依赖致命" | 已被 `includes/llvmlite/` 解决(18/18 测试) | +| "ast 模块依赖致命" | 已被 `includes/ast/` 解决(4,176 LOC,AstTest 通过) | +| "4241 处动态反射致命" | 90% 是工程偷懒(isinstance 节点分派/IR 类型分派),真实反射约 200-500 处 | +| "推荐路径 C:不自举" | 推荐路径 A:完全自举 | +| "预计自举后 60K-80K LOC" | 修正为 30-35K LOC(基础组件已 5,700 LOC 完成) | diff --git a/vpsdk/wiki/all.md b/vpsdk/wiki/all.md new file mode 100644 index 0000000..4b80528 --- /dev/null +++ b/vpsdk/wiki/all.md @@ -0,0 +1,3555 @@ +# 01 - 语言概述与编译流程 + +## 语言定位 + +`Viper` 是一种 **系统级编程语言**,其语法基于 `Python`,但编译目标为 `LLVM IR`,最终生成原生机器码。`Viper` 不是 `Python` 的超集或子集,也不是"`C` 的语法糖"——它是一门拥有独立类型系统、独立编译模型、独立模块体系的语言。`Viper` 选择 `Python` 语法作为表达形式,是因为 `Python` 的 `AST` 可被标准库直接解析,从而将编译器的精力集中在语义翻译而非词法/语法分析上。同时也是因为 `Python` 是人类公认的可读性最高的语言。 + +而实际上,下文中所有提到的 `C` 语言都是为了便于使用 `C` 的底层开发者理解。由于历史原因,在旧版本中我们使用 `C` 来作为中间语言,如同旧版本的 `C++` 一样,但现在 `target="c"` 的路径已经完全删除,`C` 路径永久不再受维护。 + +### 核心设计决策 + +1. **Python 语法,LLVM 语义**:源文件是完全合法的 `Python` 语法,但通过 `t` 模块的类型注解系统赋予完全不同的语义 —— `t.CInt` 不是 `Python` 的 `int`,而是 `LLVM` 的 `i32` +2. **类型注解即编译指令**:Viper 的类型注解不是可选的提示,而是编译器生成 `LLVM IR` 的决定性依据。`x: t.CInt` 生成 `i32`,`x: t.CInt | t.CPtr` 生成 `i32*`,但无注解时会自动推导,详见下文。 +3. **两阶段编译**:先从源文件提取声明接口(`.pyi` + `.stub.ll`),再使用声明接口翻译源文件为含代码的 `.ll`。这是 `Viper` 区别于简单"`Python` 转 `C`"工具的关键架构 +4. **`SHA1` 命名空间**:每个源文件按内容 `SHA1` 哈希命名,非导出函数和结构体自动加上 `SHA1` 前缀,从根本上消除跨模块的符号冲突 +5. **万物皆数据**:为便于底层开发,实际上所有的变量和类型一般并不适用于鸭子类型,但在语义层面我们会尽可能贴近鸭子类型。 + +### 与 `Python` 的关系 + +| 特性 | `Python` | `Viper` | +|------|--------|-------| +| 类型系统 | 动态类型 | 静态类型(通过 `t` 模块注解,编译时确定) | +| 内存管理 | `GC` 自动回收 | 手动管理,无 `GC`,无运行时 | +| 运行时 | 解释执行 + 字节码 | 编译为原生机器码(通过 `LLVM`) | +| 对象模型 | 万物皆对象 | 仅 `@t.Object` 类有对象语义,`@t.CVTable` 为多态,普通 `class` 仅为结构体布局 | +| 标准库 | `Python` 标准库 | `Viper` 标准库(`includes/`)+ `ViperOS SDK` | +| 空值 | `None` | `None`(编译为 `NULL`/零值) | +| 模块系统 | 运行时 `import` | 编译时解析,插入 `.stub.ll`,`SHA1` 命名空间隔离 | + +### 与 C 的关系 + +Viper 编译后的代码在底层等价于 `C` 编译后的机器码(都经过 LLVM),但 Viper 在语言层面提供了 C 所没有的能力: + +- **SHA1 命名空间**:自动消除符号冲突,无需手动管理 `static`/命名前缀 +- **类型组合语法**:`t.CConst | t.CInt | t.CPtr` 比 `const int*` 更具组合性 +- **声明式内联汇编**:`c.Asm(f"mov {c.AsmOut(x, t.ASM_DESCR.OUTPUT_REG)}, rdi")` 比裸 `__asm__` 更安全,也更具可读性。 +- **结构化预处理**:`c.CIfdef`/`c.CEndif()` 替代 `#ifdef`/`#endif`。 +- **面向对象**:`@t.Object` + `@t.CVTable` 提供 `vtable` 支持的 `OOP` +- **存根驱动的模块系统**:`.pyi` 存根文件实现跨模块类型解析,无需头文件 +- **更多内容**:额外更多的不依赖操作系统的函数和语法 + +## 两阶段编译流程 + +Viper 的编译由 `Projectrans.py` 驱动,分为两个阶段。这是 Viper 编译模型的核心,理解两阶段编译是理解 `t.CDefine`、`t.CExport`、`t.CInline` 等关键概念的前提。 + +``` +源文件 (.py) ────────────────────────────────────────────────────── + │ │ + │ ┌─────────────── 阶段一:声明提取 ───────────────┐ │ + │ │ │ │ + │ │ 1. 计算源文件 SHA1 │ │ + │ │ 2. 生成 .pyi(签名存根) │ │ + │ │ 3. 构建结构体注册表 │ │ + │ │ 4. 生成 .stub.ll(LLVM IR 声明) │ │ + │ │ │ │ + │ └────────────────────────────────────────────────┘ │ + │ │ + │ ┌─────────────── 阶段二:代码翻译 ───────────────┐ │ + │ │ │ │ + │ │ 1. 加载所有 .pyi 和 .stub.ll │ │ + │ │ 2. 构建共享符号表 │ │ + │ │ 3. 收集内联函数符号 │ │ + │ │ 4. 翻译源文件 → .ll(含代码) │ │ + │ │ 5. llc 编译 → .o(目标文件) │ │ + │ │ 6. ld.lld 链接 → 可执行文件 │ │ + │ │ │ │ + │ └────────────────────────────────────────────────┘ │ + │ │ + └──────────────────────────────────────────────────────────────┘ +``` + +### 阶段一:声明提取(Phase1Generator) + +阶段一的目标是从每个源文件提取**声明接口**,使其他模块在阶段二翻译时能够正确解析跨模块引用。 + +#### 步骤 1:可达文件发现与拓扑排序 + +从入口文件(`main.py` 或 `project.json` 指定)出发,通过 `import` 语句递归遍历,找出所有可达的 `.py` 源文件。然后对文件进行拓扑排序,确保被依赖的模块先被处理。 + +``` +main.py → import serial → import drivers.serial.uart.serial → ... +``` + +#### 步骤 2:生成 `.pyi` 签名存根 + +对每个源文件,计算其内容的 SHA1 哈希(16位),然后调用 `PythonToStubConverter` 生成签名存根文件 `.pyi`。 + +**SHA1 计算方式**: +```python +sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16] +``` + +**存根文件的内容规则**: + +| 源文件元素 | 存根文件中的表示 | +|-----------|----------------| +| `t.CDefine` 常量 | 保留原样(含赋值):`MAX_SIZE: t.CDefine = 1024` | +| `t.CDefine` 函数(返回 `t.CDefine` 的函数) | 保留完整函数体(宏展开需要) | +| 普通函数 | 仅保留签名,添加 `| c.State`:`def foo(x: t.CInt) -> t.CVoid | c.State: pass` | +| 类定义 | 保留成员类型注解和方法签名 | +| 全局变量 | 添加 `t.CExtern`:`x: t.CExtern | t.CInt` | +| `c.CIfdef`/`c.CEndif()` 等预处理指令 | 保留原样 | +| `import` 语句 | 保留原样 | + +**关键设计**:`t.CDefine` 常量和 `t.CDefine` 函数在存根中**保留完整定义**(包括赋值和函数体),因为它们是编译时常量/宏,其他模块引用时需要完整展开。普通函数只保留签名并标记 `c.State`(仅声明),因为函数体在阶段二生成。 + + +#### 步骤 3:构建结构体注册表 + +扫描所有已生成的 `.pyi` 文件,提取: +- **结构体名称集合**:所有非枚举、非异常的 `class` 定义 +- **枚举名称集合**:继承 `t.CEnum` 的类 +- **异常名称集合**:继承 `Exception` 的类 +- **结构体→SHA1 映射**:记录每个结构体定义在哪个模块中 + +这个注册表用于阶段一的声明生成和阶段二的类型解析,确保跨模块的结构体引用使用正确的 SHA1 命名空间。 + +#### 步骤 4:生成 `.stub.ll` LLVM IR 声明 + +对每个 `.pyi` 文件,由 `DeclarationGenerator` 生成对应的 LLVM IR 声明文件 `.stub.ll`。 + +**声明生成规则**: + +| 元素 | 生成的 LLVM IR | +|------|---------------| +| 普通函数 | `declare void @".foo"(i32)` — 非 CExport 函数加 SHA1 前缀 | +| CExport 函数 | `declare i32 @main()` — CExport 函数不加前缀,全局可见 | +| 结构体 | `%".ClassName" = type { i32, i32* }` — 类型名加 SHA1 前缀 | +| 全局变量 | `@varname = external global i32` | +| `t.CDefine` 常量 | **不生成声明**(编译时常量,直接内联展开) | +| `t.CTypedef` 别名 | **不生成声明**(类型别名,在类型解析时展开) | +| 枚举 | 为每个枚举成员生成 `@__config_EnumName_member = external global i32` | + +**增量编译**:如果 `.pyi` 或 `.stub.ll` 已存在,则跳过生成(缓存命中)。只有源文件内容变化导致 SHA1 变化时才重新生成。 + +### 阶段二:代码翻译(Phase2Translator) + +阶段二使用阶段一生成的声明接口,将源文件翻译为包含完整代码的 LLVM IR。 + +#### 步骤 1:加载声明接口 + +加载 `temp/` 目录中所有的 `.pyi` 和 `.stub.ll` 文件,构建: +- `sig_files`:SHA1 → `.pyi` 文件路径映射 +- `stub_files`:SHA1 → `.stub.ll` 文件路径映射 +- `sha1_map`:SHA1 → 源文件相对路径映射 + +#### 步骤 2:构建共享符号表 + +一次性构建所有文件共享的符号表数据,避免每个文件重复加载。将所有 `.pyi` 存根和 `.stub.ll` 声明中的类型信息注册到统一的 `SymbolTable` 中。 + +#### 步骤 3:收集内联函数符号 + +扫描 `includes/` 目录中被引用的模块,收集标记为 `t.CInline` 的函数。内联函数的完整 AST body 被保存,在翻译调用点时直接展开。值得注意的是,和 C 语言不同,`t.CInline` 不是优化建议,而是强制性的。 + +#### 步骤 4:翻译源文件 + +对每个源文件,使用 `TransPyC` 翻译器将 Python AST 翻译为 LLVM IR: +1. 将对应模块的 `.stub.ll` 声明嵌入到生成的 `.ll` 文件头部 +2. 所有被引用模块的 `.stub.ll` 声明也被嵌入 +3. 翻译 AST 节点为 LLVM IR 指令 +4. 输出 `.ll` 文件 + +#### 步骤 5:编译与链接 + +- `llc`:将每个 `.ll` 编译为 `.o` 目标文件 +- `ld.lld`:将所有 `.o` 链接为最终可执行文件 + +## SHA1 命名空间机制 + +SHA1 命名空间是 Viper 解决跨模块符号冲突的核心机制。 + +### 问题 + +在 C 语言中,多个模块可能定义同名函数或结构体,导致链接时符号冲突。C 的解决方案是 `static`(限制可见性)和命名前缀(手动避免冲突)。 + +### Viper 的解决方案 + +Viper 使用源文件内容的 SHA1 哈希作为命名空间前缀: + +``` +源文件 A.py(SHA1 = a1b2c3d4e5f6g7h8)中定义: + class Point: x: t.CInt; y: t.CInt + def draw(p: Point) -> t.CVoid: ... + +源文件 B.py(SHA1 = i9j0k1l2m3n4o5p6)中也定义: + class Point: x: t.CFloat; y: t.CFloat # 不同的结构体! + def draw(p: Point) -> t.CVoid: ... + +编译后: + A.py 的结构体 → %"a1b2c3d4e5f6g7h8.Point" = type { i32, i32 } + A.py 的函数 → declare void @"a1b2c3d4e5f6g7h8.draw"(%"a1b2c3d4e5f6g7h8.Point"*) + + B.py 的结构体 → %"i9j0k1l2m3n4o5p6.Point" = type { float, float } + B.py 的函数 → declare void @"i9j0k1l2m3n4o5p6.draw"(%"i9j0k1l2m3n4o5p6.Point"*) +``` + +**没有符号冲突**——即使两个模块定义了同名类型和函数,它们的 LLVM IR 符号也是不同的。即便有两个内容完全相同的文件也不会冲突,他们会被识别为同一个文件,然后进行单次编译。 + +### SHA1 前缀的豁免:`t.CExport` + +标记为 `t.CExport` 的函数**不加 SHA1 前缀**,保持原始函数名。这是模块向外部暴露 API/ABI 的机制: + +```python +# main.py — 入口函数,必须全局可见 +def main() -> t.CInt | t.CExport: + return 0 + +# 编译后:define i32 @main() — 无 SHA1 前缀 +``` + +```python +# serial.py — 驱动接口,对外暴露 +def init() -> t.CVoid | t.CExport: + serial_puts("init\n") + +# 编译后:define void @init() — 无 SHA1 前缀,其他模块可直接调用 +``` + +### SHA1 与增量编译 + +SHA1 同时服务于增量编译: +- 源文件内容不变 → SHA1 不变 → `.pyi` 和 `.stub.ll` 缓存命中,跳过阶段一 +- 源文件内容变化 → SHA1 变化 → 重新生成声明接口 +- `temp/_sha1_map.txt` 记录 SHA1 → 源文件路径的映射,供阶段二加载 + +理论上,如果一个未经变动的模块的依赖路径上存在变动的模块,暂时的方法是将其简单做 SHA1 替换,暨将旧 SHA1 替换为新 SHA1,但不适用于签名改变的情况,不过签名改变这个未经变动的模块必然也需要改变,但是由于宏展开的存在,不能完全保证不会发生错误情况。且依赖路径的 SHA1 替换成功性未经完整测试,可能出现未定义行为。 + +## `t.CDefine` 深度解析 + +`t.CDefine` 是 Viper 中最特殊的类型——它不是数据类型,而是**编译时元指令**,控制编译器的代码生成行为。 + +### `t.CDefine` 常量 + +```python +MAX_SIZE: t.CDefine = 1024 +PAGE_SIZE: t.CDefine = 4096 +FA_READ: t.CDefine = 0x01 +CPU_FEATURE_FPU: t.CDefine = (1 << 0) +``` + +**编译行为**: +1. **阶段一**:存根生成器保留 `t.CDefine` 常量的完整定义(包括赋值值),因为其他模块可能引用此常量 +2. **阶段一**:`.stub.ll` 声明生成器**跳过** `t.CDefine` 常量,不生成 `external global` 声明 +3. **阶段二**:翻译器遇到 `t.CDefine` 常量的引用时,直接内联其值,不生成任何加载指令 + +这意味着 `t.CDefine` 常量在 LLVM IR 层面**不存在**——它们在编译时被完全展开,等价于 C 的 `#define` 宏。 + +### `t.CDefine` 函数 + +当函数的返回类型注解包含 `t.CDefine` 时,该函数被编译器视为**宏函数**: + +```python +def MAKE_FLAG(bit) -> t.CDefine: + return (1 << bit) + +def GET_PAGE_ORDER(size) -> t.CDefine: + return size // PAGE_SIZE +``` + +**编译行为**: +1. **阶段一**:存根生成器保留 `t.CDefine` 函数的**完整函数体**(不像普通函数那样只保留签名) +2. **阶段二**:翻译器遇到 `t.CDefine` 函数的调用时,将调用内联展开为函数体的计算结果 + +`t.CDefine` 函数在 LLVM IR 中**不生成函数定义**——它们是纯粹的编译时宏。 + +### `t.CDefine` 与类型组合 + +`t.CDefine` 可以与具体类型组合使用,为常量指定底层类型: + +```python +CODE_SEG: t.CDefine | t.CUInt16T = 0x08 +DATA_SEG: t.CDefine | t.CUInt16T = 0x10 +``` + +这表示常量在类型检查时被视为 `t.CUInt16T`(`uint16_t`),但在代码生成时仍然内联展开。 + +## `t.CExport` 深度解析 + +`t.CExport` 控制函数的符号可见性,是 SHA1 命名空间的豁免机制。 + +### 语义 + +| 修饰 | LLVM IR 函数名 | 链接可见性 | 用途 | +|------|---------------|-----------|------| +| 无修饰 | `@.funcname` | 模块内部 | 模块私有函数 | +| `t.CExport` | `@funcname` | 全局可见 | 对外暴露的 API | +| `t.CExtern` | `@funcname`(仅声明) | 外部定义 | 引用外部函数 | + +### 使用场景 + +```python +# 入口函数 — 必须全局可见,链接器需要找到它 +def _start() -> t.CInt | t.CExport: + return kernel_main() + +# 驱动接口 — 其他模块/应用需要调用 +def init() -> t.CVoid | t.CExport: + uart_init() + +# 内部辅助函数 — 不需要全局可见,自动加 SHA1 前缀 +def _helper() -> t.CInt: + return 42 +``` + +### `t.CExport` 在存根中的表现 + +在 `.pyi` 存根文件中,`t.CExport` 函数与普通函数一样只保留签名(添加 `c.State`,表示单纯声明),但 `t.CExport` 标记被保留在返回类型注解中。阶段一的 `DeclarationGenerator` 检查 `t.CExport` 标记来决定是否添加 SHA1 前缀。 + +## `t.CInline` 深度解析 + +`t.CInline` 标记函数为内联函数,编译器在调用点直接展开函数体。 + +### 语义 + +```python +def fast_add(a: t.CInt, b: t.CInt) -> t.CInt | t.CInline: + return a + b +``` + +**编译行为**: +1. **阶段二预收集**:`_collect_inline_symbols` 扫描 `includes/` 目录中被引用的模块,收集所有 `t.CInline` 函数的 AST body +2. **翻译时展开**:遇到内联函数调用时,将函数体的 AST 直接嵌入调用点,替换参数为实际值 +3. **不生成独立函数**:内联函数不生成 LLVM IR 函数定义(除非也被非内联调用) + +### `t.CInline` 与 `t.CDefine` 函数的区别 + +| 特性 | `t.CInline` | `t.CDefine` 函数 | +|------|------------|-----------------| +| 展开时机 | 阶段二翻译时 | 阶段二翻译时 | +| 类型检查 | 完整的参数和返回类型检查 | 返回类型为宏,类型检查较弱 | +| 存根表示 | 签名 + `c.State` | 完整函数体 | +| LLVM IR | 可能生成函数定义(如果有非内联调用) | 不生成函数定义 | +| 适用场景 | 性能关键的短函数 | 编译时常量和宏计算 | + +## 目标平台 + +默认目标三元组:`x86_64-none-elf` + +数据布局:`e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128` + +## Hello World 示例 + +```python +import t, c + +def main() -> t.CInt | t.CExport: + print("Hello, ViperOS!\n") + return 0 +``` + +编译流程: + +1. **阶段一**:计算 `main.py` 的 SHA1,生成 `.pyi` 存根(`def main() -> t.CInt | t.CExport | c.State: pass`),生成 `.stub.ll` 声明(`declare i32 @main()`,因为是 CExport 不加前缀) +2. **阶段二**:翻译 `main.py` 为 `.ll`,嵌入 `print` 对应的 `puts`/`printf` 声明,生成 `define i32 @main()` 函数体 +3. **编译**:`llc` 将 `.ll` 编译为 `.o` +4. **链接**:`ld.lld` 链接为可执行文件 + + +# 02 - 类型系统 + +Viper 的类型系统通过 `t` 模块提供,所有类型注解继承 `t.CType` 。大都以 `t.C{TypeName:PascalCase}`为类型,类型注解是 Viper 的核心机制,决定了变量的内存布局、大小和对齐方式。 + +## 基本类型 + +### 整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CChar` | `char` | 8 | 是 | +| `t.CShort` | `short` | 16 | 是 | +| `t.CInt` | `int` | 32 | 是 | +| `t.CLong` | `long` | 64 | 是 | +| `t.CUnsignedChar` | `unsigned char` | 8 | 否 | +| `t.CUnsignedShort` | `unsigned short` | 16 | 否 | +| `t.CUnsigned` / `t.CUnsignedInt` | `unsigned int` | 32 | 否 | +| `t.CUnsignedLong` | `unsigned long` | 64 | 否 | +| `t.CSignedChar` | `signed char` | 8 | 是 | + +值得注意的是,t.CUnsigned 一般不单独使用,而是配合其它有符号类型,替换表示无符号,比如 `t.CUnsigned | t.CInt`,由于为了便于使用一些常用类型,则将其组合为常用的 `t.CUnginedInt` 等。 + +### 固定宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CInt8T` | `int8_t` | 8 | 是 | +| `t.CInt16T` | `int16_t` | 16 | 是 | +| `t.CInt32T` | `int32_t` | 32 | 是 | +| `t.CInt64T` | `int64_t` | 64 | 是 | +| `t.CUInt8T` | `uint8_t` | 8 | 否 | +| `t.CUInt16T` | `uint16_t` | 16 | 否 | +| `t.CUInt32T` | `uint32_t` | 32 | 否 | +| `t.CUInt64T` | `uint64_t` | 64 | 否 | + +### 最小宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CIntLeast8T` | `int_least8_t` | ≥8 | 是 | +| `t.CIntLeast16T` | `int_least16_t` | ≥16 | 是 | +| `t.CIntLeast32T` | `int_least32_t` | ≥32 | 是 | +| `t.CIntLeast64T` | `int_least64_t` | ≥64 | 是 | +| `t.CUIntLeast8T` | `uint_least8_t` | ≥8 | 否 | +| `t.CUIntLeast16T` | `uint_least16_t` | ≥16 | 否 | +| `t.CUIntLeast32T` | `uint_least32_t` | ≥32 | 否 | +| `t.CUIntLeast64T` | `uint_least64_t` | ≥64 | 否 | + +### 最快最小宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CIntFast8T` | `int_fast8_t` | ≥8 | 是 | +| `t.CIntFast16T` | `int_fast16_t` | ≥16 | 是 | +| `t.CIntFast32T` | `int_fast32_t` | ≥32 | 是 | +| `t.CIntFast64T` | `int_fast64_t` | ≥64 | 是 | +| `t.CUIntFast8T` | `uint_fast8_t` | ≥8 | 否 | +| `t.CUIntFast16T` | `uint_fast16_t` | ≥16 | 否 | +| `t.CUIntFast32T` | `uint_fast32_t` | ≥32 | 否 | +| `t.CUIntFast64T` | `uint_fast64_t` | ≥64 | 否 | + +### 最大宽度整数类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CIntMaxT` | `intmax_t` | ≥64 | 是 | +| `t.CUIntMaxT` | `uintmax_t` | ≥64 | 否 | + +### stdint 短名别名 + +通过 `import stdint` 引入,这些短名是上述类型的 `t.CTypedef` 别名,便于在系统编程场景中快速使用。允许直接使用 `from stdint import *` 来直接使用。 + +#### 基本类型短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.INT` | `t.CInt` | - | +| `stdint.UINT` | `t.CUnsignedInt` | - | +| `stdint.BOOL` | `t.CInt` | 1 为真,0 为假 | +| `stdint.SHORT` | `t.CShort` | - | +| `stdint.USHORT` | `t.CUnsignedShort` | - | +| `stdint.LONG` | `t.CLong` | - | +| `stdint.ULONG` | `t.CUnsignedLong` | - | +| `stdint.LONGLONG` | `t.CLong \| t.CLong` | - | +| `stdint.ULONGLONG` | `t.CUnsignedLong \| t.CLong` | - | +| `stdint.FLOAT` | `t.CFloat` | - | +| `stdint.DOUBLE` | `t.CDouble` | - | + +#### 固定宽度短名 + +| 短名 | 等价展开 | +|------|---------| +| `stdint.INT8` | `t.CInt8T` | +| `stdint.INT16` | `t.CInt16T` | +| `stdint.INT32` | `t.CInt32T` | +| `stdint.INT64` | `t.CInt64T` | +| `stdint.UINT8` | `t.CUInt8T` | +| `stdint.UINT16` | `t.CUInt16T` | +| `stdint.UINT32` | `t.CUInt32T` | +| `stdint.UINT64` | `t.CUInt64T` | + +#### 字节与字短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.BYTE` | `t.CUnsignedChar` | 8 位无符号 | +| `stdint.WORD` | `t.CUInt16T` | 16 位无符号 | +| `stdint.DWORD` | `t.CUInt32T` | 32 位无符号 | +| `stdint.QWORD` | `t.CUInt64T` | 64 位无符号 | + +#### 字符类型短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.TCHAR` | `t.CChar` | - | +| `stdint.CHARLIST` | `str \| t.CPtr` / `list[str, None]` | - | +| `stdint.WCHAR` | `stdint.WORD` | UTF-16 代码单元 | +| `stdint.CHAR8` | `t.CChar8T` | - | +| `stdint.CHAR16` | `t.CChar16T` | - | +| `stdint.CHAR32` | `t.CChar32T` | - | + +#### 指针短名 + +| 短名 | 等价展开 | +|------|---------| +| `stdint.INTPTR` | `t.CInt \| t.CPtr` | +| `stdint.UINTPTR` | `t.CUnsignedInt \| t.CPtr` | +| `stdint.BYTEPTR` | `stdint.BYTE \| t.CPtr` | +| `stdint.SHORTPTR` | `t.CShort \| t.CPtr` | +| `stdint.USHORTPTR` | `t.CUnsignedShort \| t.CPtr` | +| `stdint.WCHARPTR` | `stdint.WORD \| t.CPtr` | +| `stdint.VOIDPTR` | `t.CVoid \| t.CPtr` | +| `stdint.INT8PTR` | `t.CInt8T \| t.CPtr` | +| `stdint.INT16PTR` | `t.CInt16T \| t.CPtr` | +| `stdint.INT32PTR` | `t.CInt32T \| t.CPtr` | +| `stdint.INT64PTR` | `t.CInt64T \| t.CPtr` | +| `stdint.UINT8PTR` | `t.CUInt8T \| t.CPtr` | +| `stdint.UINT16PTR` | `t.CUInt16T \| t.CPtr` | +| `stdint.UINT32PTR` | `t.CUInt32T \| t.CPtr` | +| `stdint.UINT64PTR` | `t.CUInt64T \| t.CPtr` | +| `stdint.CHAR8PTR` | `t.CChar8T \| t.CPtr` | +| `stdint.CHAR16PTR` | `t.CChar16T \| t.CPtr` | +| `stdint.CHAR32PTR` | `t.CChar32T \| t.CPtr` | + +#### 平台特定短名 + +| 短名 | 等价展开 | 说明 | +|------|---------|------| +| `stdint.FSIZE_t` | `stdint.DWORD` | 文件大小 | +| `stdint.LBA_t` | `stdint.DWORD` | 逻辑块地址 | + +### 平台相关类型 + +| Viper 类型 | C 等价 | 大小(位) | 有符号 | +|------------|--------|-----------|--------| +| `t.CSizeT` | `size_t` | 64 | 否 | +| `t.CIntPtrT` | `intptr_t` | 64 | 是 | +| `t.CUIntPtrT` | `uintptr_t` | 64 | 否 | +| `t.CPtrDiffT` | `ptrdiff_t` | 64 | 是 | + +### stdint 短名别名 + +Viper 在 `stdint` 模块中提供了更简短的类型别名,通过 `t.CTypedef` 映射到对应的完整类型。使用时需 `import stdint`。 + +#### 固定宽度整数短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `INT8` | `t.CInt8T` | `int8_t` | +| `INT16` | `t.CInt16T` | `int16_t` | +| `INT32` | `t.CInt32T` | `int32_t` | +| `INT64` | `t.CInt64T` | `int64_t` | +| `UINT8` | `t.CUInt8T` | `uint8_t` | +| `UINT16` | `t.CUInt16T` | `uint16_t` | +| `UINT32` | `t.CUInt32T` | `uint32_t` | +| `UINT64` | `t.CUInt64T` | `uint64_t` | + +#### 固定宽度指针短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `INT8PTR` | `t.CInt8T \| t.CPtr` | `int8_t*` | +| `INT16PTR` | `t.CInt16T \| t.CPtr` | `int16_t*` | +| `INT32PTR` | `t.CInt32T \| t.CPtr` | `int32_t*` | +| `INT64PTR` | `t.CInt64T \| t.CPtr` | `int64_t*` | +| `UINT8PTR` | `t.CUInt8T \| t.CPtr` | `uint8_t*` | +| `UINT16PTR` | `t.CUInt16T \| t.CPtr` | `uint16_t*` | +| `UINT32PTR` | `t.CUInt32T \| t.CPtr` | `uint32_t*` | +| `UINT64PTR` | `t.CUInt64T \| t.CPtr` | `uint64_t*` | + +#### 字符类型短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `CHAR8` | `t.CChar8T` | `char8_t` | +| `CHAR16` | `t.CChar16T` | `char16_t` | +| `CHAR32` | `t.CChar32T` | `char32_t` | +| `CHAR8PTR` | `t.CChar8T \| t.CPtr` | `char8_t*` | +| `CHAR16PTR` | `t.CChar16T \| t.CPtr` | `char16_t*` | +| `CHAR32PTR` | `t.CChar32T \| t.CPtr` | `char32_t*` | + +#### 基本类型短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `INT` | `t.CInt` | `int` | +| `UINT` | `t.CUnsignedInt` | `unsigned int` | +| `BOOL` | `t.CInt` | `int`(0/1) | +| `SHORT` | `t.CShort` | `short` | +| `USHORT` | `t.CUnsignedShort` | `unsigned short` | +| `LONG` | `t.CLong` | `long` | +| `ULONG` | `t.CUnsignedLong` | `unsigned long` | +| `LONGLONG` | `t.CLong \| t.CLong` | `long long` | +| `ULONGLONG` | `t.CUnsignedLong \| t.CLong` | `unsigned long long` | +| `FLOAT` | `t.CFloat` | `float` | +| `DOUBLE` | `t.CDouble` | `double` | + +#### 平台 / Windows 风格短名 + +| 短名 | 等价 Viper 类型 | C 等价 | +|------|----------------|--------| +| `BYTE` | `t.CUnsignedChar` | `unsigned char`(8位) | +| `BYTEPTR` | `BYTE \| t.CPtr` | `unsigned char*` | +| `WORD` | `t.CUInt16T` | `uint16_t`(16位) | +| `DWORD` | `t.CUInt32T` | `uint32_t`(32位) | +| `QWORD` | `t.CUInt64T` | `uint64_t`(64位) | +| `INTPTR` | `t.CInt \| t.CPtr` | `int*` | +| `UINTPTR` | `t.CUnsignedInt \| t.CPtr` | `unsigned int*` | +| `SHORTPTR` | `t.CShort \| t.CPtr` | `short*` | +| `USHORTPTR` | `t.CUnsignedShort \| t.CPtr` | `unsigned short*` | +| `VOIDPTR` | `t.CVoid \| t.CPtr` | `void*` | +| `TCHAR` | `t.CChar` | `char` | +| `WCHAR` | `WORD` | `uint16_t`(UTF-16) | +| `WCHARPTR` | `WORD \| t.CPtr` | `uint16_t*`(UTF-16) | +| `CHARLIST` | `str \| t.CPtr` | `char*` | +| `FSIZE_t` | `DWORD` | `uint32_t` | +| `LBA_t` | `DWORD` | `uint32_t` | + +### 浮点类型 + +| Viper 类型 | C 等价 | 大小(位) | +|------------|--------|-----------| +| `t.CFloat` | `float` | 32 | +| `t.CDouble` | `double` | 64 | + +### 其他基本类型 + +| Viper 类型 | C 等价 | 说明 | +|------------|--------|------| +| `t.CVoid` | `void` | 空类型 | +| `t.CBool` | `bool` | 布尔类型(8位) | +| `t.CWCharT` | `wchar_t` | 宽字符(32位) | +| `t.CChar8T` | `char8_t` | UTF-8 字符(8位,无符号) | +| `t.CChar16T` | `char16_t` | UTF-16 字符(16位,无符号) | +| `t.CChar32T` | `char32_t` | UTF-32 字符(32位,无符号) | + +### Python 便捷映射类型 +| Python 类型| Viper 类型 | Viper 值 | +|--------|--------|------| +|`int`|`t.CInt`|-| +|`float`|`t.CFloat`|-| +|`str`|`t.CChar \| t.CPtr`|-| +|`bool`|`t.CBool`|-| +|`True`|`t.CBool`|`1`| +|`False`|`t.CBool`|`0`| +|`None`|`NULL`|`t.CIntPtr(0)`| + +对于 t.py 和 c.py 的引用,必须使用 import,不能使用 from ... import ...,也不能使用 as,因为这两者都是直接以字符串识别的。 + +## 类型组合 + +Viper 使用 Python 的 `|` 来组合类型修饰符(TypeUnion),其仅在左值注解中有效,其它位置均为位或语义(至少重载前是这样的),这是 Viper 最重要的语法特性之一。 + +### 指针类型 + +使用 `t.CPtr` 表示指针,通过 `|` 与基本类型组合: + +```python +x: t.CInt | t.CPtr # int* — 指向 int 的指针 +p: t.CVoid | t.CPtr # void* — 通用指针 +s: t.CChar | t.CPtr # char* — 字符串指针 +pp: t.CInt | t.CPtr | t.CPtr # int** — 指向指针的指针 +``` + +等价 C 代码: +```c +int* x; +void* p; +char* s; +int** pp; +``` + +同时,指针也可以用 `Ptr[T]` 的方法表示,但在 Viper 中不太推荐,虽然说这样做一直是类似工程的常见用法,但是如果此处 `T` 是一个多态,同时我们想要复用 Python 的高亮插件,插件无法识别 `Ptr[T]` 的属性取值,但是对于 `T | Ptr` 可以。 + +```python +x: t.CPtr[t.CInt] +p: t.CPtr[t.CVoid] +s: t.CPtr[t.CChar] +pp: t.CPtr[t.CPtr[t.CInt]] +pp2: t.CInt | t.CPtr[t.CPtr] # 同时也可以这么表示 +``` + +等价 C 代码: + +```c +int* x; +void* p; +char* s: +int** pp; +int** pp2; +``` + +### 类型限定符组合 + +```python +x: t.CConst | t.CInt # const int +p: t.CConst | t.CChar | t.CPtr # const char* +``` + +### 存储类组合 + +```python +x: t.CStatic | t.CInt # static int +f: t.CExtern | t.CInt # extern int(声明其存在于外部) +``` + +### 函数返回类型组合 + +```python +def foo() -> t.CInt | t.CExport: # 导出函数,返回 int + return 0 + +def bar() -> t.CVoid | t.CExtern: # 外部函数声明 + pass +``` + +## 数组类型 + +使用 Python 的 `list[ElementType, Size]` 语法表示固定大小数组(通常的,这用于栈上和 BSS 段上): + +```python +buf: list[t.CChar, 64] # char buf[64] +ids: list[t.CInt, 10] # int ids[10] +matrix: list[t.CFloat, 16] # float matrix[16] +entries: list[idt_entry, 256] # struct idt_entry entries[256] +s: list[t.CChar, None] # 自推长度 char s[]; +``` + +等价 C 代码: +```c +char buf[64]; +int ids[10]; +float matrix[16]; +struct idt_entry entries[256]; +``` + +### 数组指针和函数指针 + +```python +lp: list[t.CInt, 12] | t.CPtr +vp: t.Callable[[t.CInt, t.CInt], t.CInt] = x # 其中 t.Callable = typing.Callable,可以直接使用。 +``` + +## 结构体类型 + +使用 `class` 定义结构体,成员通过类型注解声明: + +```python +class point: + x: t.CInt + y: t.CInt +``` + +等价 C 代码: +```c +struct point { + int x; + int y; +}; +``` + +### 带指针成员的结构体 + +```python +class node: + value: t.CInt + next: node | t.CPtr # struct node* next +``` + +### 匿名结构体成员(❌) + +> WARNING: **警告:** 由于 Viper 在声明结构体,枚举等处不需要关键字,因此 `t.Anonymouse` 是不需要使用的。此关键字不再维护,强行使用可能导致未定义行为。 + +继承 `t.Anonymous` 的类作为匿名成员嵌入: + +```python +class header(t.Anonymous): + magic: t.CUInt32T + version: t.CUInt32T + +class packet: + header # 匿名嵌入,可直接访问 packet.magic + length: t.CUInt32T +``` + +### 字节序标记 + +> WARNING: **警告:** 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。 + +使用 `t.BigEndian` 或 `t.LittleEndian` 标记成员的字节序: + +```python +class network_packet: + length: t.CUInt16T | t.BigEndian # 大端序存储 + type: t.CUInt8T # 默认小端序 +``` + +## 联合体类型 + +继承 `t.CUnion` 定义联合体: + +```python +class value(t.CUnion): + ival: t.CInt + fval: t.CFloat + pval: t.CVoid | t.CPtr +``` + +等价 C 代码: +```c +union value { + int ival; + float fval; + void* pval; +}; +``` + +## 枚举类型 + +继承 `t.CEnum` 定义枚举: + +```python +class color(t.CEnum): + RED: t.CEnum = 0 + GREEN: t.CEnum = 1 + BLUE: t.CEnum = 2 +``` + +等价 C 代码: +```c +enum color { + RED = 0, + GREEN = 1, + BLUE = 2 +}; +``` + +枚举成员可直接使用名称: + +```python +c: t.CInt = color.RED +``` + +## Typedef + +使用 `t.CTypedef` 创建类型别名: + +```python +irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] # typedef int (*irq_handler_t)(); +``` + +或通过注解赋值: + +```python +MyStruct: t.CTypedef = original_struct # typedef struct original_struct MyStruct; +``` + +更推荐通过后者的形式,前者编译器不一定能够识别到注解是对 t.CTypedef 的,还是后面的注解的,而且容易触发未定义行为。 + +## 强制类型转换 +在 Viper 中的强制类型转换有些不同,强制类型转换有两种方案,都是基于 `t.CType` 的用法,符合 Python 本意。 +对于原先就继承自 `t.CType` 的类型来说,可以直接使用 call 的方法进行类型转换,比如 +```python +x: t.CChar | t.CPtr = "Hello" +t.CVoid(x, t.CPtr) +``` +其中用法为 `t.CType(x, ...)`,此处...可以是其它的 `t.CType` 或将要提到的 `Typedef`,将其视为组合类型对 `x` 进行强制转换。 + +额外补充一句,对于比较,四则运算,可以不使用强制类型转换,而是依靠隐式类型转换,但如果是严谨数学计算,推荐还是强制类型转换避免判断出错。 + +此外,若将一个不同类型的右值赋值到左值,则会隐式将右值转换为左值的类型。 + +如果你不喜欢 `t.CVoid(x, t.CPtr)` 的方法,认为这将 `void` 和 `ptr` 分开,不便于理解,那么还有一个方法,即将二者打包为一个 Typedef,即: +```python +VOIDPTR: t.CTypedef = t.CVoid | t.CPtr +``` +则如果你想,你可以直接通过 call 的方式进行强转,比如: +```python +y: VOIDPTR = VOIDPTR(x) +``` + +## 宏定义常量 + +使用 `t.CDefine` 定义编译时常量。`t.CDefine` 不是数据类型,而是编译时元指令——标记为 `t.CDefine` 的常量在 LLVM IR 中不生成任何全局变量,而是在引用处直接内联展开。详见 [01-overview.md 中 t.CDefine 深度解析](01-overview.md#tcdefine-深度解析)。 + +```python +MAX_SIZE: t.CDefine = 1024 +PAGE_SIZE: t.CDefine = 4096 +FA_READ: t.CDefine = 0x01 +``` + +语义上等价于 C 的 `#define`: +```c +#define MAX_SIZE 1024 +#define PAGE_SIZE 4096 +#define FA_READ 0x01 +``` + +宏定义支持表达式: + +```python +CPU_FEATURE_FPU: t.CDefine = (1 << 0) +CPU_FEATURE_APIC: t.CDefine = (1 << 6) +``` + +宏定义也可与类型组合使用: + +```python +CODE_SEG: t.CDefine | t.CUInt16T = 0x08 +``` + +## 位域 + +使用 `t.Bit(n)` 定义位域成员: + +```python +class flags: + a: t.CUInt32T | t.Bit[1] # 此处,如果存入 2,二进制是 10,会被截断为 0 + b: t.CUInt32T | t.Bit[3] + c: t.CUInt32T | t.Bit[4] +``` + +## LLVM IR 类型简写 + +Viper 提供了 LLVM IR 级别的类型简写: + +```python +i1 # 1位整数 +i8 # 8位有符号整数 +i16 # 16位有符号整数 +i32 # 32位有符号整数 +i64 # 64位有符号整数 +u1 # 1位无符号整数 +u8 # 8位无符号整数 +u16 # 16位无符号整数 +u32 # 32位无符号整数 +u64 # 64位无符号整数 +``` + +仍然通过 `t.x` 的方式调用,比如 `t.i1` 等。 + +## `__attribute__` 属性 + +通过 `t.attr` 命名空间访问 GCC `__attribute__` 属性: + +```python +t.attr.packed() # __attribute__((packed)) +t.attr.aligned(16) # __attribute__((aligned(16))) +t.attr.section(".text.startup") # __attribute__((section(".text.startup"))) +t.attr.always_inline() # __attribute__((always_inline)) +t.attr.noinline() # __attribute__((noinline)) +t.attr.noreturn() # __attribute__((noreturn)) +t.attr.weak() # __attribute__((weak)) +t.attr.unused() # __attribute__((unused)) +t.attr.constructor() # __attribute__((constructor)) +t.attr.destructor() # __attribute__((destructor)) +t.attr.deprecated("use X") # __attribute__((deprecated("use X"))) +``` + +### LLVM 函数属性 + +通过 `t.attr.llvm` 命名空间指定 LLVM 函数属性: + +```python +t.attr.llvm.nobuiltin +t.attr.llvm.nounwind +t.attr.llvm.noredzone +t.attr.llvm.willreturn +t.attr.llvm.mustprogress +t.attr.llvm.optnone +t.attr.llvm.noinline +t.attr.llvm.alwaysinline +t.attr.llvm.readnone +t.attr.llvm.readonly +t.attr.llvm.writeonly +t.attr.llvm.inaccessiblememonly +t.attr.llvm.inaccessiblemem_or_argmemonly +``` + +在函数返回类型注解中使用: + +```python +def foo() -> t.CInt | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind: + return 0 +``` + + +# 03 - 变量声明与赋值 + +Viper 的变量声明使用 Python 的类型注解语法,通过 `t` 模块指定 C 级别的类型信息。 + +## 带注解的变量声明 + +使用 `变量名: 类型` 语法声明变量: + +```python +x: t.CInt = 0 # int x = 0; +y: t.CUInt32T = 42 # uint32_t y = 42; +name: list[t.CChar, 64] # char name[64]; +ptr: t.CVoid | t.CPtr # void* ptr; +msg: t.CConst | str = "Hello" # const char* msg = "Hello"; +``` + +### 无初始值的声明 + +未赋值的变量声明会生成零初始化的变量,此处是未定义行为,即在不同环境位置可能会覆盖0,也可能不会,最好预先复制或用 memset 清除: + +```python +count: t.CInt # int count = 0; +buffer: list[t.CChar, 256] # char buffer[256] = {0}; +``` + +### 延迟赋值 + +变量可以先声明后赋值: + +```python +x: t.CInt +x = 10 +``` + +### 指针变量 + +```python +p: t.CInt | t.CPtr # int* p; +fb: t.CVoid | t.CPtr # void* fb; +str_ptr: t.CConst | t.CChar | t.CPtr # const char* str_ptr; +``` + +### 指针变量的特殊初始化 + +使用 `t.CVoid(0, t.CPtr)` 初始化指针为 NULL: + +```python +p: t.CVoid | t.CPtr = t.CVoid(0, t.CPtr) # void* p = NULL; +``` + +或使用 `None`: + +```python +p: t.CVoid | t.CPtr = None # void* p = NULL; +``` + +## 普通赋值 + +```python +x: t.CInt = 0 +x = 42 # x = 42; +``` + +## 增强赋值 + +支持所有 Python 增强赋值运算符: + +```python +x: t.CInt = 0 +x += 1 # x += 1; +x -= 1 # x -= 1; +x *= 2 # x *= 2; +x //= 3 # x /= 3; (整数除法) +x %= 5 # x %= 5; +x <<= 1 # x <<= 1; +x >>= 1 # x >>= 1; +x |= 0xFF # x |= 0xFF; +x &= 0x0F # x &= 0x0F; +x ^= 0xAA # x ^= 0xAA; +``` + +## 全局变量 + +模块顶层声明的变量即为全局变量: + +```python +kbd_tid: t.CInt = -1 # 全局 int kbd_tid = -1; +BootInfo: bootinfo | t.CPtr # 全局 struct bootinfo* BootInfo; +``` + +### 静态全局变量 + +```python +x: t.CStatic | t.CInt = 0 # static int x = 0; +``` + +### 外部声明 + +```python +x: t.CExtern | t.CInt # extern int x; +``` + +## 常量定义 + +使用 `t.CDefine` 定义编译时常量。`t.CDefine` 不是数据类型,而是编译时元指令——常量在 LLVM IR 中不生成全局变量,而是在引用处直接内联展开。详见 [01-overview.md 中 t.CDefine 深度解析](01-overview.md#tcdefine-深度解析)。 + +```python +MAX_SIZE: t.CDefine = 1024 +PAGE_SIZE: t.CDefine = 4096 +FA_READ: t.CDefine = 0x01 +FA_WRITE: t.CDefine = 0x02 +``` + +## 字符串常量 + +Viper 中的字符串字面量编译为 C 的字符串常量(`const char*`): + +```python +msg: t.CConst | str = "Hello, World!" +serial.puts(msg) +``` + +也可以直接传递字符串字面量: + +```python +serial.puts("Hello, World!") +``` + +## 类型转换 + +使用类型构造函数进行显式类型转换: + +```python +x: t.CInt = 42 +y: t.CUInt32T = t.CUInt32T(x) # (uint32_t)x +f: t.CFloat = float(x) # (float)x +i: t.CInt = int(f) # (int)f +``` + +### 指针类型转换 + +```python +ptr: t.CVoid | t.CPtr = some_ptr +int_val: t.CUInt64T = t.CUInt64T(ptr) # (uint64_t)ptr — 指针转整数 +``` + +## 结构体实例化 + +> 此处不理解参见 ([05-classes.md 中 结构体实例化与初始化](05-classes.md#结构体实例化与初始化)) + +直接使用类名作为构造函数: + +```python +info: fat32_types.fat32_fileinfo +dp: fat32_types.fat32_dirobj +``` + +带初始化的实例化: + +```python +obj: MyClass = MyClass(arg1, arg2) +``` + +使用 `c.Addr` 获取实例指针: + +```python +obj_ptr: MyClass | t.CPtr = c.Addr(MyClass(arg1, arg2)) +``` + +## delete 语句 + +`del` 语句可用于调用对象的 `__del__` 或 `__delete__` 方法: + +```python +del obj # 调用 obj.__del__() 或 obj.__delete__() +del arr[idx] # 调用 arr.__delitem__(idx) +``` + + +# 04 - 函数定义与调用 + +Viper 的函数定义使用 Python 的 `def` 语法,通过类型注解指定参数类型和返回类型。 + +## 基本函数定义 + +```python +def add(a: t.CInt, b: t.CInt) -> t.CInt: + return a + b +``` + +等价 C 代码: +```c +int add(int a, int b) { + return a + b; +} +``` + +## 无返回值函数 + +```python +def print_msg(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid: + serial.puts(msg) +``` + +等价 C 代码: +```c +void print_msg(const char* msg) { + serial_puts(msg); +} +``` + +## 函数修饰符 + +通过返回类型的 `|` 组合添加函数修饰符: + +### 导出函数 + +` t.CExport` 控制函数的符号可见性——标记为 `t.CExport` 的函数不加 SHA1 前缀,全局可见。详见 [01-overview.md 中 t.CExport 深度解析](01-overview.md#tcexport-深度解析)。 + +```python +def _start() -> t.CInt | t.CExport: + return 0 +``` + +### 外部函数声明 + +使用 `c.State` 表示仅声明不定义: + +```python +def isr0() -> t.CExtern | t.CVoid | c.State: pass +def isr1() -> t.CExtern | t.CVoid | c.State: pass +``` + +等价 C 代码: +```c +extern void isr0(); +extern void isr1(); +``` + +### 内联函数 + +`t.CInline` 标记函数为内联函数,编译器在调用点直接展开函数体。详见 [01-overview.md 中 t.CInline 深度解析](01-overview.md#tcinline-深度解析)。 + +```python +def fast_add(a: t.CInt, b: t.CInt) -> t.CInt | t.CInline: + return a + b +``` + +### 宏函数 + +当函数返回类型注解包含 `t.CDefine` 时,该函数被视为宏函数,不生成 LLVM IR 函数定义,在调用处内联展开。详见 [01-overview.md 中 t.CDefine 函数](01-overview.md#tcdefine-函数)。 + +```python +def MAKE_FLAG(bit) -> t.CDefine: + return (1 << bit) +``` + +### 静态函数 + +```python +def helper() -> t.CVoid | t.CStatic: + pass +``` + +## 函数属性装饰器 + +使用 `@c.Attribute` 添加 GCC `__attribute__` 属性: + +```python +@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16)) +def _start() -> t.CInt: + return 0 +``` + +等价 C 代码: +```c +__attribute__((section(".text.startup"), aligned(16))) +int _start() { + return 0; +} +``` + +### 常用属性组合 + +```python +@c.Attribute(t.attr.always_inline()) +def hot_path() -> t.CInt: + return 0 + +@c.Attribute(t.attr.noreturn()) +def panic(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid: + serial.puts(msg) + while True: pass + +@c.Attribute(t.attr.packed) +``` + +## LLVM 函数属性 + +在返回类型注解中通过 `t.attr.llvm` 指定 LLVM 属性: + +```python +def foo() -> t.CInt | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind: + return 0 +``` + +## 多返回值(CReturn) + +使用 `@c.CReturn` 装饰器实现多返回值,通过指针参数实现: + +```python +@c.CReturn(t.CInt, t.CInt) +def divmod(a: t.CInt, b: t.CInt) -> t.CVoid: + q: t.CInt = a // b + r: t.CInt = a % b + return q, r +``` + +规则: +1. `CReturn` 中的类型数量决定返回值个数 +2. 自动为每个返回类型添加指针参数(`t.CInt` → `t.CInt | t.CPtr`) +3. 参数名自动生成为 `__ReturnValue0__`、`__ReturnValue1__` 等 +4. 调用处自动传参 `&xxx` +5. 必须使用左右值赋值,返回值不能直接传入函数。 + +## 指针参数 + +```python +def read_data(buf: t.CChar | t.CPtr, size: t.CSizeT) -> t.CInt: + pass +``` + +等价 C 代码: +```c +int read_data(char* buf, size_t size); +``` + +### 输出参数模式 + +使用 `c.Addr` 传递变量地址作为输出参数: + +```python +res: t.CInt = fat32.opendir("/", c.Addr(dp)) +``` + +等价 C 代码: +```c +int res = fat32_opendir("/", &dp); +``` + +后续会使用隐式左值获取来减少 `c.Addr` 的直接使用。 + +## 函数指针类型 + +使用 `t.Callable` 定义函数指针类型: + +```python +irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] +``` + +等价 C 代码: +```c +typedef int (*irq_handler_t)(); +``` + +带参数的函数指针: + +```python +callback_t: t.Callable[[t.CInt, t.CInt], t.CVoid] +``` + +## 默认参数 + +Viper 支持函数默认参数值: + +```python +def create_window(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | str = "Window") -> t.CInt: + pass +``` + +## return 语句 + +```python +return 0 # return 0; +return # return; (void 函数) +return a, b # 多返回值(需配合 @c.CReturn) +``` + +## 全局变量声明 + +函数外的变量声明为全局变量,函数内通过 `global` 关键字访问: + +```python +BootInfo: bootinfo | t.CPtr + +def init() -> t.CVoid: + global BootInfo + BootInfo = saved_rdi +``` + +支持 func-in-func 嵌套函数。 + +## Call Func 函数调用 + +函数调用最好通过顺序调用来实现 + +```python +def add(x: int, y: int, z: int): + return (x + y) * z + +def main() -> int | t.CExport: + print(add(1, 2, 3)) # (1 + 2) * 3 = 9 +``` + +这也是 C 中的标准实现方法,但实际上,你也可以乱序传参。 + +```python +# 仍然用刚才的 add 举例 +def main() -> int | t.CExport: + print(add(z=3, x=1, y=2)) # (1 + 2) * 3 = 9 +``` + +乱序传参或带参传参对于结构体的初始化同样有效。同时,你也能够给函数指定默认值。 + +```python +def add(x: int, y: int, z: int = 1): + return (x + y) * z + +def main() -> int | t.CExport: + print(add(1, 2)) # (1 + 2) * 1 = 3 +``` + +在结构体中也可以指定默认值。 + + +# 05 - 类与数据布局 + +Viper 中的 `class` 有两种用途:定义**数据布局**(结构体/联合体/枚举,本章内容),以及定义**面向对象的对象类型**(见 [06-oop.md](06-oop.md))。 + +## 结构体定义 + +普通 `class` 定义编译为 LLVM 结构体类型: + +```python +class point: + x: t.CInt + y: t.CInt +``` + +在 C 中: +```c +struct point { + int x; + int y; +}; +``` + +在 LLVM IR 中: +```llvm +%".point" = type { i32, i32 } +``` + +### 带属性的结构体 + +```python +@c.Attribute(t.attr.packed) +class idt_entry: + base_low: t.CUInt16T + selector: t.CUInt16T + ist_attr: t.CUInt8T + type_attr: t.CUInt8T + base_middle: t.CUInt16T + base_high: t.CUInt32T + reserved1: t.CUInt32T +``` + +### 结构体数组 + +```python +idt: list[idt_entry, 256] = [] +``` + +### 结构体指针成员 + +```python +class node: + value: t.CInt + next: node | t.CPtr # 指向自身的指针(有向图链表) +``` + +### 结构体实例化与初始化 + +```python +entry: idt_entry = idt_entry() +``` + +或带构造函数: + +```python +mousepoint = Point(x=1, y=2) +``` + +乱序传参或带参传参对于结构体的初始化同样有效: + +```python +entry: idt_entry = idt_entry(selector=0x08, base_low=0x0000) +``` + +也可以给结构体成员指定默认值: + +```python +class config: + width: t.CInt = 640 + height: t.CInt = 480 + depth: t.CInt = 32 +``` + +使用 `c.Addr` 获取实例指针: + +```python +obj_ptr: MyClass | t.CPtr = c.Addr(MyClass(arg1, arg2)) +``` + +### 匿名结构体成员(❌) + +> WARNING: **警告:** 由于 Viper 在声明结构体,枚举等处不需要关键字,因此 `t.Anonymouse` 是不需要使用的。此关键字不再维护,强行使用可能导致未定义行为。 + +继承 `t.Anonymous` 的类作为匿名成员嵌入: + +```python +class header(t.Anonymous): + magic: t.CUInt32T + version: t.CUInt32T + +class packet: + header # 匿名嵌入,可直接访问 packet.magic + length: t.CUInt32T +``` + +### 字节序标记 + +> WARNING: **警告:** 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。 + +使用 `t.BigEndian` 或 `t.LittleEndian` 标记成员的字节序: + +```python +class network_packet: + length: t.CUInt16T | t.BigEndian # 大端序存储 + type: t.CUInt8T # 默认小端序 +``` + +## 联合体 + +继承 `t.CUnion` 定义联合体: + +```python +class value(t.CUnion): + ival: t.CInt + fval: t.CFloat + pval: t.CVoid | t.CPtr +``` + +等价 C 代码: +```c +union value { + int ival; + float fval; + void* pval; +}; +``` + +## 枚举 + +继承 `t.CEnum` 定义枚举: + +```python +class color(t.CEnum): + RED: t.CEnum = 0 + GREEN: t.CEnum = 1 + BLUE: t.CEnum = 2 +``` + +等价 C 代码: +```c +enum color { + RED = 0, + GREEN = 1, + BLUE = 2 +}; +``` + +枚举成员可直接使用名称: + +```python +c: t.CInt = color.RED +``` + +## Typedef + +使用 `t.CTypedef` 创建类型别名: + +```python +irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] # typedef int (*irq_handler_t)(); +``` + +或通过注解赋值: + +```python +MyStruct: t.CTypedef = original_struct # typedef struct original_struct MyStruct; +``` + +更推荐通过后者的形式,前者编译器不一定能够识别到注解是对 `t.CTypedef` 的,还是后面的注解的,而且容易触发未定义行为。 + +## 位域 + +使用 `t.Bit(n)` 定义位域成员: + +```python +class flags: + a: t.CUInt32T | t.Bit[1] + b: t.CUInt32T | t.Bit[3] + c: t.CUInt32T | t.Bit[4] +``` + +注意:如果存入超出位域宽度的值,会被截断。例如 `a` 为 1 位,存入 2(二进制 `10`)会被截断为 0。 + + +# 06 - 面向对象与运算符重载 + +Viper 中的 `class` 有两种语义:**数据布局**(结构体/联合体/枚举,见 [05-classes.md](05-classes.md))和**面向对象**。面向对象特性通过 `@t.Object` 装饰器启用,多态通过 `@t.CVTable` 装饰器启用。 + +## `@t.Object` 面向对象 + +使用 `@t.Object` 装饰器启用面向对象特性,类将拥有成员方法、运算符重载、属性装饰器等 OOP 支持。编译器也会自动识别:如果结构体内有函数定义,会自动使用 `@t.Object` 行为,但在实际工程中更推荐明确标记,避免未定义行为。 + +### 基本定义 + +```python +@t.Object +class ViperKernel: + BootInfo: bootinfo | t.CPtr + VGA: vga._VGAScreenDriver | t.CPtr + VGA_drv: vga._VGAScreenDriver + + def __init__(self): + self.BootInfo = BootInfo + serial.init() + serial.puts("ViperOS VKernel Test\n") +``` + +避免在 `__init__` 中使用 `return`,虽然不会报错,但是你永远无法获得 `return` 的结果,如果需要提前结束代码流,`return` 也不失为一种办法。 + +### 成员方法 + +```python +@t.Object +class Sheet: + id: t.CInt + x: t.CInt + y: t.CInt + w: t.CInt + h: t.CInt + + def __init__(self, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt): + self.x = x + self.y = y + self.w = w + self.h = h + + def MoveTo(self, nx: t.CInt, ny: t.CInt): + self.x = nx + self.y = ny + + def Resize(self, nw: t.CInt, nh: t.CInt): + self.w = nw + self.h = nh +``` + +### 方法调用 + +```python +sheet: Sheet = Sheet(0, 0, 640, 480) +sheet.MoveTo(100, 100) +sheet.Resize(800, 600) +``` + +### 属性装饰器 + +支持 Python 的 `@property` 和 `@xxx.setter` 装饰器: + +> `@xxx.setter` 和自定义装饰器正在试验阶段,尽可能不要使用 + +```python +@t.Object +class MyObj: + _value: t.CInt + + @property + def value(self) -> t.CInt: + return self._value + + @value.setter + def value(self, v: t.CInt): + self._value = v +``` + +### 静态方法 + +```python +@staticmethod +def _yield(): + pass +``` + +### 对象指针与解引用 + +`@t.Object` 的实例通常通过指针操作: + +```python +obj_ptr: MyClass | t.CPtr = c.Addr(MyClass()) +obj: MyClass = c.Deref(obj_ptr) +``` + +### 成员访问 + +通过 `self` 访问成员变量和方法: + +```python +def method(self) -> t.CVoid: + self.x = 10 + y: t.CInt = self.y + self.DoSomething() +``` + +### 内嵌对象 + +`@t.Object` 可以包含其他对象实例(非指针): + +```python +@t.Object +class Sheet: + _surf_obj: gfx.Surface + _renderer_obj: gfx.Renderer + surf: gfx.Surface | t.CPtr + renderer: gfx.Renderer | t.CPtr + + def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: t.CInt, h: t.CInt): + self._surf_obj = gfx.Surface(fb, zb, w, h) + self.surf = c.Addr(self._surf_obj) + self._renderer_obj = gfx.Renderer(self.surf) + self.renderer = c.Addr(self._renderer_obj) +``` + +### `__before_init__` 方法 + +`@t.Object` 或 `@t.CVTable` 类自动生成 `__before_init__` 方法声明,用于在 `__init__` 之前初始化 vtable: + +```llvm +declare void @".ClassName.__before_init__"(%".ClassName"*) +``` + +值得注意的是,`__before_init__` 并不帮忙初始化内存,而只是初始化虚表等准备工作,实际上,我们更希望用户能自己管理内存。实验性阶段内容中,我们通过内存池机制来帮助用户管理碎片化内容,包括部分小的结构体,但较大内容最好还是由用户手动处理。 + +## `@t.CVTable` 虚函数表与多态 + +> 尽可能避免使用 `self[0]` 来获取结构体成员,其不稳定,也不要手动操作虚表。 + +使用 `@t.CVTable` 装饰器可以启用虚函数表支持。启用后,结构体第一个成员自动插入 `i8*` 类型的 vtable 指针。`@t.CVTable` 的操作必须配合 `@t.Object`,但有时编译器会自动识别是否存在结构体内函数,自动使用 `@t.Object`。 + +### 基本定义 + +```python +@t.Object +@t.CVTable +class Base: + def __init__(self): + pass + def method(self) -> t.CInt: + return 0 +``` + +在 LLVM IR 中,`@t.CVTable` 类的结构体类型自动插入 vtable 指针: + +```llvm +%".Base" = type { i8*, i32 } +``` + +同时,编译器会生成全局 vtable 变量: + +```llvm +@".Base_Vtable" = internal global [1 x i8*] zeroinitializer +``` + +### 继承与多态 + +虚表可以帮你使用继承语法实现多态: + +```python +@t.Object +@t.CVTable +class Base: + def __init__(self): + pass + def method(self) -> t.CInt: + return 0 + +@t.Object +@t.CVTable +class M1(Base): + def method(self) -> t.CInt: + return 1 +``` + +当 M1 被初始化时,VTable 会自动修改,这点和 C++ 相同,尽可能避免直接操作 VTable。 + +继承时,子类会自动继承父类的成员变量和方法。子类重写父类方法时,vtable 中对应位置的函数指针被子类方法替换,实现多态。 + +### VTable 运行时修改 + +如果需要临时构造一个额外的 `Base` 结构体,还可以使用: + +```python +@t.Object +@t.CVTable +class Base: + def __init__(self): + pass + def method(self) -> t.CInt: + return 0 + +def __method1(self) -> t.CInt: + return 1 + +def main() -> t.CInt | t.CExport: + b: Base = Base() + b.method = __method1 +``` + +启用 `VTable` 的表中,允许直接修改内部函数为函数指针,在底层,此操作将修改 `VTable` 来达成目的。编译器会检测当前 vtable 是否与类 vtable 相同,如果相同则创建 vtable 的副本(memcpy),避免修改影响所有实例。 + +### 虚方法调度机制 + +虚方法调用的核心流程: + +1. 从对象指针 GEP 获取第 0 个 slot(vtable 指针) +2. 加载 vtable 指针 +3. bitcast 为 `[N x i8*]*` 类型 +4. GEP 获取方法索引处的函数指针 +5. 加载函数指针 +6. bitcast 为正确的函数指针类型 +7. 间接调用 + +## 继承 + +### 结构体成员继承 + +当子类继承父类(父类在 `Gen.class_members` 中存在)时: + +- 父类的成员变量被继承到子类(排在子类成员之前) +- 父类的默认值也被继承 +- 父类的方法被继承(重命名为 `子类名.方法名`) + +```python +@t.Object +class Animal: + name: list[t.CChar, 32] + age: t.CInt + + def __init__(self, age: t.CInt): + self.age = age + + def Speak(self) -> t.CVoid: + serial.puts("...\n") + +@t.Object +class Dog(Animal): + breed: t.CInt + + def Speak(self) -> t.CVoid: + serial.puts("Woof!\n") +``` + +### 异常类继承 + +Viper 支持异常类的继承,用于 `try/except` 的异常匹配: + +```python +class MyError(Exception): + pass + +class SpecificError(MyError): + pass +``` + +详见 [09-exceptions.md](09-exceptions.md)。 + +## 运算符重载 + +`@t.Object` 类支持运算符重载。编译器在遇到运算符时,首先检查左操作数是否为结构体类型,如果是则尝试调用对应的 dunder 方法。 + +### 算术运算符重载 + +| 运算符 | Dunder 方法 | 示例 | +|--------|------------|------| +| `+` | `__add__` | `a + b` → `a.__add__(b)` | +| `-` | `__sub__` | `a - b` → `a.__sub__(b)` | +| `*` | `__mul__` | `a * b` → `a.__mul__(b)` | +| `/` | `__truediv__` | `a / b` → `a.__truediv__(b)` | +| `//` | `__floordiv__` | `a // b` → `a.__floordiv__(b)` | +| `%` | `__mod__` | `a % b` → `a.__mod__(b)` | +| `**` | `__pow__` | `a ** b` → `a.__pow__(b)` | + +示例: + +```python +@t.Object +class Vec2: + x: t.CFloat + y: t.CFloat + + def __init__(self, x: t.CFloat, y: t.CFloat): + self.x = x + self.y = y + + def __add__(self, other: Vec2) -> Vec2: + result: Vec2 = Vec2(0.0, 0.0) + result.x = self.x + other.x + result.y = self.y + other.y + return result + + def __mul__(self, scalar: t.CFloat) -> Vec2: + result: Vec2 = Vec2(0.0, 0.0) + result.x = self.x * scalar + result.y = self.y * scalar + return result +``` + +### 增量赋值运算符重载 + +| 增量运算符 | 优先 Dunder | 回退 Dunder | +|-----------|------------|------------| +| `+=` | `__iadd__` | `__add__` | +| `-=` | `__isub__` | `__sub__` | +| `*=` | `__imul__` | `__mul__` | +| `/=` | `__itruediv__` | `__truediv__` | +| `//=` | `__ifloordiv__` | `__floordiv__` | +| `%=` | `__imod__` | `__mod__` | +| `**=` | `__ipow__` | `__pow__` | + +如果未定义增量版本(如 `__iadd__`),编译器自动回退到普通版本(如 `__add__`)。 + +### 比较运算符重载 + +> **实验性**:以下比较运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。定义它们不会报错,但比较运算不会自动调用。 + +| 运算符 | Dunder 方法 | 状态 | +|--------|------------|------| +| `==` | `__eq__` | 仅类型推断识别 | +| `!=` | `__ne__` | 仅类型推断识别 | +| `<` | `__lt__` | 仅类型推断识别 | +| `<=` | `__le__` | 仅类型推断识别 | +| `>` | `__gt__` | 仅类型推断识别 | +| `>=` | `__ge__` | 仅类型推断识别 | + +### 反向运算符重载 + +> **实验性**:以下反向运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。 + +| Dunder 方法 | 预期语义 | 状态 | +|------------|---------|------| +| `__radd__` | 右操作数加法 | 仅类型推断识别 | +| `__rsub__` | 右操作数减法 | 仅类型推断识别 | +| `__rmul__` | 右操作数乘法 | 仅类型推断识别 | +| `__rtruediv__` | 右操作数真除法 | 仅类型推断识别 | +| `__rfloordiv__` | 右操作数整除 | 仅类型推断识别 | +| `__rmod__` | 右操作数取模 | 仅类型推断识别 | +| `__rpow__` | 右操作数幂运算 | 仅类型推断识别 | + +### 一元运算符重载 + +> **实验性**:以下一元运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。 + +| 运算符 | Dunder 方法 | 状态 | +|--------|------------|------| +| `-a` | `__neg__` | 仅类型推断识别 | +| `+a` | `__pos__` | 仅类型推断识别 | + +## 可调用对象 `__call__` + +```python +@t.Object +class Counter: + value: t.CInt + + def __init__(self): + self.value = 0 + + def __call__(self) -> t.CInt: + self.value += 1 + return self.value + +c: Counter = Counter() +x: t.CInt = c() # x = 1 +y: t.CInt = c() # y = 2 +``` + +## 下标访问重载 + +### `__getitem__` — 读取 + +```python +def __getitem__(self, key: t.CInt) -> t.CInt: + return self.data[key] +``` + +### `__setitem__` — 赋值 + +```python +def __setitem__(self, key: t.CInt, value: t.CInt): + self.data[key] = value +``` + +### `__delitem__` — 删除 + +```python +def __delitem__(self, key: t.CInt): + pass +``` + +## 布尔转换 `__bool__` + +```python +@t.Object +class Buffer: + size: t.CInt + data: list[t.CChar, 1024] + + def __bool__(self) -> t.CBool: + return self.size > 0 + +buf: Buffer = Buffer() +if buf: + serial.puts("buffer is not empty\n") +``` + +## 长度 `__len__` + +```python +@t.Object +class Buffer: + size: t.CInt + + def __len__(self) -> t.CInt: + return self.size + +buf: Buffer = Buffer() +n: t.CInt = len(buf) +``` + +## 字符串转换 `__str__` + +```python +@t.Object +class Point: + x: t.CInt + y: t.CInt + + def __str__(self) -> t.CConst | t.CChar | t.CPtr: + return "Point" + +p: Point = Point(1, 2) +print(p) # 调用 p.__str__() +``` + +## 绝对值 `__abs__` + +```python +@t.Object +class SignedValue: + val: t.CInt + + def __abs__(self) -> t.CInt: + if self.val < 0: + return -self.val + return self.val + +sv: SignedValue = SignedValue() +sv.val = -42 +a: t.CInt = abs(sv) # a = 42 +``` + +## 迭代器协议 + +Viper 的迭代器协议与 Python 相同,但 `__next__` 的实现方式通过 `StopIteration` 异常来结束。 + +### `__iter__` 和 `__next__` + +```python +@t.Object +class IntRange: + start: t.CInt + end: t.CInt + current: t.CInt + + def __init__(self, start: t.CInt, end: t.CInt): + self.start = start + self.end = end + self.current = start + + def __iter__(self): + return self + + def __next__(self, stop_flag) -> t.CInt: + if self.current >= self.end: + raise StopIteration + val: t.CInt = self.current + self.current += 1 + return val +``` + +### for 循环使用 + +```python +r: IntRange = IntRange(0, 10) +for i in r: + print(i) +``` + +编译器生成的等价逻辑: + +```c +IntRange r = IntRange_init(0, 10); +IntRange iter = IntRange___iter__(&r); +while (1) { + i1 stop_flag = 0; + int i = IntRange___next__(&iter, &stop_flag); + if (stop_flag == 1) break; + print_int(i); +} +``` + +## 上下文管理器 + +`with` 语句要求对象实现 `__enter__` 和 `__exit__` 方法: + +```python +@t.Object +class File: + fd: t.CInt + path: t.CConst | t.CChar | t.CPtr + + def __init__(self, path: t.CConst | t.CChar | t.CPtr, flags: t.CInt): + self.path = path + self.fd = -1 + + def __enter__(self): + self.fd = fat32.open(self.path, self.flags) + return self + + def __exit__(self): + fat32.close(self.fd) + + def read(self) -> t.CInt: + return fat32.read(self.fd) +``` + +使用方式: + +```python +with File("/test.txt", FA_READ) as f: + data: t.CInt = f.read() +``` + +编译器会自动保证 `__exit__` 在 `with` 块结束时被调用。 + +## 析构 `__del__` / `__delete__` + +```python +del obj # 调用 obj.__del__() 或 obj.__delete__() +del arr[idx] # 调用 arr.__delitem__(idx) +``` + +## Dunder 方法完整参考 + +### 已实现自动调度的 Dunder 方法 + +| Dunder 方法 | 触发语法 | 说明 | +|------------|---------|------| +| `__init__` | `ClassName(args)` | 构造函数 | +| `__before_init__` | 自动生成 | vtable 初始化,在 `__init__` 之前执行 | +| `__add__` | `a + b` | 加法 | +| `__sub__` | `a - b` | 减法 | +| `__mul__` | `a * b` | 乘法 | +| `__truediv__` | `a / b` | 真除法 | +| `__floordiv__` | `a // b` | 整除 | +| `__mod__` | `a % b` | 取模 | +| `__pow__` | `a ** b` | 幂运算 | +| `__iadd__` | `a += b` | 增量加法(回退到 `__add__`) | +| `__isub__` | `a -= b` | 增量减法(回退到 `__sub__`) | +| `__imul__` | `a *= b` | 增量乘法(回退到 `__mul__`) | +| `__itruediv__` | `a /= b` | 增量真除法(回退到 `__truediv__`) | +| `__ifloordiv__` | `a //= b` | 增量整除(回退到 `__floordiv__`) | +| `__imod__` | `a %= b` | 增量取模(回退到 `__mod__`) | +| `__ipow__` | `a **= b` | 增量幂运算(回退到 `__pow__`) | +| `__call__` | `obj(args)` | 可调用对象 | +| `__getitem__` | `obj[key]` | 下标读取 | +| `__setitem__` | `obj[key] = value` | 下标赋值 | +| `__delitem__` | `del obj[key]` | 下标删除 | +| `__bool__` | `if obj:` | 布尔转换 | +| `__len__` | `len(obj)` | 长度 | +| `__str__` | `str(obj)` / `print(obj)` | 字符串转换 | +| `__abs__` | `abs(obj)` | 绝对值 | +| `__iter__` | `for x in obj:` | 获取迭代器 | +| `__next__` | `for x in obj:` | 获取下一个值(标志位方式) | +| `__enter__` | `with obj as x:` | 上下文管理器进入 | +| `__exit__` | `with obj as x:` | 上下文管理器退出 | +| `__del__` | `del obj` | 析构 | +| `__delete__` | `del obj` | 删除描述符 | + +### 声明支持但未实现自动调度的 Dunder 方法 + +| Dunder 方法 | 预期触发语法 | 当前状态 | +|------------|------------|---------| +| `__eq__` | `a == b` | 仅类型推断识别,比较运算不自动调度 | +| `__ne__` | `a != b` | 同上 | +| `__lt__` | `a < b` | 同上 | +| `__le__` | `a <= b` | 同上 | +| `__gt__` | `a > b` | 同上 | +| `__ge__` | `a >= b` | 同上 | +| `__neg__` | `-a` | 仅类型推断识别,一元负号不自动调度 | +| `__pos__` | `+a` | 同上 | +| `__radd__` | 右操作数加法 | 仅类型推断识别 | +| `__rsub__` | 右操作数减法 | 同上 | +| `__rmul__` | 右操作数乘法 | 同上 | +| `__rtruediv__` | 右操作数真除法 | 同上 | +| `__rfloordiv__` | 右操作数整除 | 同上 | +| `__rmod__` | 右操作数取模 | 同上 | +| `__rpow__` | 右操作数幂运算 | 同上 | + + +# 07 - 控制流 + +Viper 支持 Python 风格的控制流语句,编译为 LLVM IR 的基本块和分支指令。 + +## 条件语句 + +### if / elif / else + +```python +if x > 0: + y = 1 +elif x == 0: + y = 0 +else: + y = -1 +``` + +等价 C 代码: +```c +if (x > 0) { + y = 1; +} else if (x == 0) { + y = 0; +} else { + y = -1; +} +``` + +### 条件表达式 + +```python +is_dir: t.CInt = 1 if (info.attr & AM_DIR) else 0 +``` + +等价 C 代码: +```c +int is_dir = (info.attr & AM_DIR) ? 1 : 0; +``` + +### None 检查 + +```python +if BootInfo != None: + paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize) +else: + paging.init(0, 0) +``` + +等价 C 代码: +```c +if (BootInfo != NULL) { + paging_init(BootInfo->MemmapAddr, BootInfo->MemmapSize); +} else { + paging_init(0, 0); +} +``` + +## 循环语句 + +### while 循环 + +```python +i: t.CInt = 0 +while i < 10: + buf[i] = 0 + i += 1 +``` + +等价 C 代码: +```c +int i = 0; +while (i < 10) { + buf[i] = 0; + i += 1; +} +``` + +### while-else + +```python +while i < count: + if found: + break +else: + serial.puts("not found\n") +``` + +`else` 块在循环正常结束(非 `break` 退出)时执行。 + +### 无限循环 + +```python +while True: + sched.Scheduler._yield() +``` + +等价 C 代码: +```c +while (1) { + scheduler_yield(); +} +``` + +### for 循环(range)(关于 for 循环的更多用法,参见 [06-oop.md 中 迭代器协议](06-oop.md#迭代器协议)) + +```python +for i in range(10): + buf[i] = 0 + +for i in range(5, 10): + buf[i] = 0 + +for i in range(0, 100, 2): + buf[i] = 0 +``` + +等价 C 代码: +```c +for (int i = 0; i < 10; i++) { + buf[i] = 0; +} + +for (int i = 5; i < 10; i++) { + buf[i] = 0; +} + +for (int i = 0; i < 100; i += 2) { + buf[i] = 0; +} +``` + +### for 循环(迭代器) + +如果类实现了 `__iter__` 和 `__next__` 方法,可以使用 `for ... in` 迭代: + +```python +container: Iter = Iter() +for item in container: + process(item) +``` + +### for-else + +```python +for i in range(count): + if items[i] == target: + break +else: + serial.puts("not found\n") +``` + +### break 和 continue + +```python +while True: + if done: + break + if skip: + continue + process() +``` + +## match 语句 + +Viper 支持 Python 3.10+ 的 `match` 语句,编译为 C 的 `switch` 语句: + +```python +match self.ctype: + case UI_LABEL: + self.RenderLabel(sh) + case UI_BUTTON: + self.RenderButton(sh) + case UI_PANEL: + self.RenderPanel(sh) + case _: + pass +``` + +等价 C 代码: +```c +switch (self->ctype) { + case UI_LABEL: + self_RenderLabel(self, sh); + break; + case UI_BUTTON: + self_RenderButton(self, sh); + break; + case UI_PANEL: + self_RenderPanel(self, sh); + break; + default: + break; +} +``` + +### match 值匹配 + +```python +match code: + case 0: + serial.puts("OK\n") + case 1: + serial.puts("Error\n") + case _: + serial.puts("Unknown\n") +``` + +### match 或模式 + +```python +match value: + case 1 | 2 | 3: + serial.puts("small\n") + case _: + serial.puts("other\n") +``` + +### fallthrough(无 break) + +默认每个 `case` 自动添加 `break`。如需 fallthrough,使用 `c.NoBreak`: + +```python +match value: + case 1: + do_one() + c.NoBreak + case 2: + do_two() +``` + +如果需要提前 break,需要使用 `c.Break()`,因为在 `Python` 中,`match` 分支不支持直接使用 `break`。 + +## assert 语句 +> 避免使用此语句,此语句未经充分测试,在多数情况下,编译器不知道如何展示结果。 + +```python +assert ptr != None, "null pointer" +``` + +编译为条件检查和错误报告。 + +## with 语句 + +`with` 语句用于资源管理,要求对象实现 `__enter__` 和 `__exit__` 方法: + +```python +with File("/test.txt", FA_READ) as f: + data: t.CInt = f.read() +``` + +等价 C 代码: +```c +File* f = File_enter(File_new("/test.txt", FA_READ)); +int data = File_read(f); +File_exit(f); +``` + + +# 08 - C 语言操作 + +Viper 通过 `c` 模块提供对 C 语言底层特性的访问,包括指针操作、内联汇编、预处理指令等。 + +## 指针操作 + +### 取地址 `c.Addr` + +```python +x: t.CInt = 42 +p: t.CInt | t.CPtr = c.Addr(x) # int* p = &x; +``` + +对结构体成员取地址: + +```python +res: t.CInt = fat32.opendir("/", c.Addr(dp)) # res = fat32_opendir("/", &dp); +``` + +对数组取地址: + +```python +viperlib.snprintf(c.Addr(buf), 64, "hello %d", 42) # snprintf(&buf, 64, "hello %d", 42); +``` + +### 解引用 `c.Deref` + +```python +ptr: Sheet | t.CPtr = c.Addr(sheet_obj) +obj: Sheet = c.Deref(ptr) # struct Sheet obj = *ptr; +``` + +### 内存拷贝 `c.Load` + +```python +c.Load(ptr, value) # *ptr = *value; +``` + +### 解引用赋值 `c.DerefAs` + +```python +c.DerefAs(ptr, value) # *ptr = value; +``` + + +### 赋值 `c.Set` + +```python +c.Set(target, value) # target = value; +``` + +## 内联汇编 `c.Asm` + +Viper 提供了声明式的内联汇编语法,编译为 GCC 风格的 `__asm__ __volatile__` 语句。 + +### 基本用法 + +```python +c.Asm("nop") # __asm__ __volatile__("nop"); +``` + +### 带操作数的汇编 + +使用 f-string 和 `c.AsmInp` / `c.AsmOut` 标记操作数: + +```python +saved_rdi: t.CUnsignedLong +c.Asm(f"mov {c.AsmOut(saved_rdi, t.ASM_DESCR.OUTPUT_REG)}, rdi", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RDI]) +``` + +等价 C 代码: +```c +__asm__ __volatile__( + "mov %0, rdi" + : "=r"(saved_rdi) + : + : "memory", "rdi" +); +``` + +### 输入操作数 `c.AsmInp` + +```python +msg: t.CConst | t.CChar | t.CPtr = "Hello" +c.Asm(f"mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)}", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) +``` + +### 完整示例 + +```python +c.Asm(f"""mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)} + call {c.AsmInp(log_info_fn, t.ASM_DESCR.REG_ANY)}""", + op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, + t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, + t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI, + t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, + t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) +``` + +### ASM_DESCR 约束字符 + +#### 操作数修饰符 + +| 常量 | 值 | 说明 | +|------|----|------| +| `MODIFIER_OUTPUT` | `=` | 输出操作数 | +| `MODIFIER_READWRITE` | `+` | 读写操作数 | +| `MODIFIER_INPUT` | `` | 输入操作数(默认) | +| `MODIFIER_GLOBAL` | `&` | 全局操作数 | + +#### 寄存器约束 + +| 常量 | 值 | 说明 | +|------|----|------| +| `REG_ANY` | `r` | 任何通用寄存器 | +| `REG_EAX` / `REG_RAX` | `a` | EAX/RAX | +| `REG_EBX` / `REG_RBX` | `b` | EBX/RBX | +| `REG_ECX` / `REG_RCX` | `c` | ECX/RCX | +| `REG_EDX` / `REG_RDX` | `d` | EDX/RDX | +| `REG_ESI` / `REG_RSI` | `S` | ESI/RSI | +| `REG_EDI` / `REG_RDI` | `D` | EDI/RDI | +| `REG_XMM` | `x` | XMM 寄存器 | + +#### 内存与立即数 + +| 常量 | 值 | 说明 | +|------|----|------| +| `MEMORY` | `m` | 内存操作数 | +| `IMMEDIATE` | `i` | 立即数 | +| `ANY` | `g` | 通用寄存器/内存/立即数 | + +#### 预定义组合约束 + +| 常量 | 值 | 说明 | +|------|----|------| +| `OUTPUT_REG` | `=r` | 输出,通用寄存器 | +| `OUTPUT_MEM` | `=m` | 输出,内存 | +| `OUTPUT_EAX` | `=a` | 输出,EAX/RAX | +| `INPUT_REG` | `r` | 输入,通用寄存器 | +| `INPUT_MEM` | `m` | 输入,内存 | +| `INPUT_IMM` | `i` | 输入,立即数 | + +#### 破坏描述符 + +| 常量 | 说明 | +|------|------| +| `CLOBBER_EAX` / `CLOBBER_RAX` | 破坏 EAX/RAX | +| `CLOBBER_EBX` / `CLOBBER_RBX` | 破坏 EBX/RBX | +| `CLOBBER_ECX` / `CLOBBER_RCX` | 破坏 ECX/RCX | +| `CLOBBER_EDX` / `CLOBBER_RDX` | 破坏 EDX/RDX | +| `CLOBBER_ESI` / `CLOBBER_RSI` | 破坏 ESI/RSI | +| `CLOBBER_EDI` / `CLOBBER_RDI` | 破坏 EDI/RDI | +| `CLOBBER_CC` | 破坏条件码(标志寄存器) | +| `CLOBBER_MEMORY` | 破坏内存 | +| `CLOBBER_R8` ~ `CLOBBER_R15` | 破坏 R8~R15 | + +## 预处理指令 + +Viper 通过 `c` 模块的函数调用实现 C 预处理指令。 + +### #define + +```python +c.CDefine("MAX_SIZE", 1024) # #define MAX_SIZE 1024 +``` + +上述方法容易引起未定义行为,至少是不便于理解,更常用的方式是使用类型注解: + +```python +MAX_SIZE: t.CDefine = 1024 # #define MAX_SIZE 1024 +``` + +### 条件编译 + +```python +c.CIfndef(HEADER_H) # #ifndef HEADER_H +c.CDefine(HEADER_H) # #define HEADER_H +c.CEndif() # #endif +``` + +```python +c.CIfdef(DEBUG) # #ifdef DEBUG +c.CEndif() # #endif +``` + +```python +c.CIf(VERSION > 2) # #if VERSION > 2 +c.CElif(VERSION > 1) # #elif VERSION > 1 +c.CElse() # #else +c.CEndif() # #endif +``` + +### #undef + +```python +c.Undef(MACRO_NAME) # #undef MACRO_NAME +``` + +### #error + +```python +c.CError("Platform not supported") # #error "Platform not supported" +``` + +### #pragma +> 此关键字已不再支持 +```python +c.CPragma("GCC diagnostic push") # #pragma GCC diagnostic push +``` + +### ## 连接符 +> 此方法可能已经不再支持 +```python +c.TokenPast("PREFIX_", "NAME") # PREFIX_ ## NAME +``` + +由于编译器设计,以及不需要像 `C` 一样做大量字符替换,对于上述所有的条件宏都远比 `C` 差,实际工程中不推荐使用条件宏,后期将完善宏的设计,引入模板等。 + +## 仅声明 `c.State` + +`c.State` 用于声明但不定义函数或变量: + +```python +def isr0() -> t.CExtern | t.CVoid | c.State: pass # extern void isr0(); +``` + +## LLVM IR 内联 + +### c.LLVMIR + +直接嵌入 LLVM IR 指令来实现高效且跨平台的汇编操作: + +```python +c.LLVMIR(f"add i32 {c.LInp(a)}, {c.LInp(b)}", t.CInt) +``` + +### c.LInp / c.LOut + +标记 LLVM IR 的输入/输出操作数: + +```python +c.LInp(expr) # 输入操作数 +c.LOut(expr) # 输出操作数 +``` + +## 运算符重载 + +`@t.Object` 类支持运算符重载,详见 [06-oop.md 中 运算符重载](06-oop.md#运算符重载)。 + + +# 09 - 异常处理 + +Viper 支持 Python 风格的 `try/except/finally` 异常处理,编译为基于 jmp_buf 和错误码的 C 级别异常处理机制。 + +## 异常处理机制 + +Viper 的异常处理不使用 C++ 风格的零开销异常,而是通过函数参数传递错误码和错误消息实现。编译器自动为可能抛出异常的函数添加 `__eh_msg_out__` 和 `__eh_code_out__` 参数。 + +## try / except + +```python +try: + result = risky_operation() +except ValueError: + serial.puts("ValueError occurred\n") +except OSError: + serial.puts("OSError occurred\n") +except Exception: + serial.puts("Unknown error\n") +``` + +### 内置异常类型 + +Viper 预定义了以下异常类型及其错误码: + +| 异常类型 | 错误码 | +|---------|--------| +| `ValueError` | 1 | +| `TypeError` | 2 | +| `RuntimeError` | 3 | +| `ZeroDivisionError` | 4 | +| `IndexError` | 5 | +| `KeyError` | 6 | +| `IOError` | 7 | +| `OSError` | 8 | +| `AssertionError` | 9 | +| `Exception` | 99 | + +### 自定义异常 + +```python +class DiskFullError(Exception): + pass + +class ReadOnlyError(DiskFullError): + pass +``` + +自定义异常会自动分配递增的错误码(从 100 开始),并支持继承匹配。 + +### 多异常捕获 + +```python +try: + result = operation() +except (ValueError, TypeError): + serial.puts("Value or Type error\n") +``` + +### 获取异常信息 + +```python +try: + result = operation() +except Exception as e: + serial.puts("Error occurred\n") +``` + +## try / finally + +```python +try: + result = operation() +finally: + cleanup() +``` + +`finally` 块无论是否发生异常都会执行。 + +## try / except / finally + +```python +try: + result = operation() +except ValueError: + handle_value_error() +finally: + cleanup() +``` + +## raise 语句 + +### 抛出异常 + +```python +raise ValueError("invalid value") +``` + +### 抛出自定义异常 + +```python +raise MyCustomError("something went wrong") +``` + +### 重新抛出异常 + +```python +raise +``` + +### StopIteration + +`StopIteration` 有特殊处理,用于迭代器协议: + +```python +raise StopIteration +``` + +## 异常处理的编译实现 + +### 函数签名变换 + +可能抛出异常的函数会自动添加错误输出参数: + +```python +def risky_operation() -> t.CInt: + if error: + raise ValueError("bad value") + return 0 +``` + +编译后等价于: + +```c +int risky_operation(char** __eh_msg_out__, int* __eh_code_out__) { + if (error) { + *__eh_code_out__ = 1; // ValueError code + *__eh_msg_out__ = "bad value"; + return 0; + } + return 0; +} +``` + +### 调用点变换 + +```python +try: + result = risky_operation() +except ValueError: + handle_error() +``` + +编译后等价于: + +```c +char* __eh_msg = NULL; +int __eh_code = 0; +int result = risky_operation(&__eh_msg, &__eh_code); +if (__eh_msg != NULL) { + if (__eh_code == 1) { // ValueError + handle_error(); + } +} +``` + +## 异常继承匹配 + +异常匹配支持继承体系。捕获父类异常时,子类异常也会被匹配: + +```python +class FileSystemError(Exception): + pass + +class FileNotFoundError(FileSystemError): + pass + +class PermissionError(FileSystemError): + pass + +try: + operation() +except FileSystemError: + # 也会捕获 FileNotFoundError 和 PermissionError + handle_fs_error() +``` + + +# 10 - 模块与导入系统 + +Viper 的模块系统基于 Python 的 `import` 语法,但编译时通过 TransPyC 的符号表和类型系统实现跨模块的类型解析。 + +## 导入语法 + +### 标准导入 + +```python +import t, c # Viper 内置类型和操作模块 +import asm # 汇编辅助模块 +import string # 字符串操作模块 +``` + +### 模块导入 + +```python +import bootinfo +import intr +import platform.pch as pch +import drivers.serial.uart.serial as serial +``` + +### from 导入 + +```python +from stdint import * # 导入所有 stdint 类型别名 +from drivers.video.vesafb.vga import _VGAScreenDriver +``` + +### 相对导入 + +```python +from . import submodule +from .. import parent_module +``` + +## 特殊模块 + +### `t` 模块 + +类型定义模块,提供所有 C 类型对应的 Viper 类型。每个 Viper 文件通常都需要导入: + +```python +import t +``` + +### `c` 模块 + +C 语言操作模块,提供指针操作、内联汇编、预处理指令等: + +```python +import c +``` + +### `asm` 模块 + +汇编辅助模块,提供常用汇编指令的封装: + +```python +import asm + +asm.sti() # __asm__ __volatile__("sti"); +asm.hlt() # __asm__ __volatile__("hlt"); +asm.cli() # __asm__ __volatile__("cli"); +asm.nop() # __asm__ __volatile__("nop"); +asm.BSSClean() # 清零 BSS 段 +``` + +### `string` 模块 + +字符串/内存操作模块,对应 C 标准库的 `string.h`: +> string 部分使用 x86_64 专有汇编进行加速,因此某些函数在其它架构可能需要手动重新编写。 + +```python +import string + +string.memset(c.Addr(buf), 0, 64) # memset(&buf, 0, 64); +string.memcpy(dst, src, size) # memcpy(dst, src, size); +string.memmove(dst, src, size) # memmove(dst, src, size); +string.memcmp(a, b, size) # memcmp(a, b, size); +string.strcmp(a, b) # strcmp(a, b); +string.strcpy(dst, src) # strcpy(dst, src); +``` + +### `stdint` 模块 + +提供 C `stdint.h` 中的类型别名: + +```python +from stdint import * + +# 提供以下类型别名: +# UINT8PTR, UINT16PTR, UINT32PTR, UINT64PTR +# INT8PTR, INT16PTR, INT32PTR, INT64PTR +# 等等 +``` + +### `viperlib` 模块 + +ViperOS 标准库,提供常用 C 库函数: + +```python +import viperlib + +viperlib.snprintf(c.Addr(buf), 64, "value=%d", 42) # snprintf(&buf, 64, "value=%d", 42); +``` + +### `vpsdk` 模块 + +> vpsdk 模块 专供于基于 Viper 开发的示例操作系统 ViperOS,不可用于其它平台,且需要特殊配置。 + +ViperOS 应用 SDK,提供系统调用和窗口管理: + +```python +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.dynlib as dynlib +import vpsdk.syscall as syscall +``` + +## 模块解析流程 + +### 编译时模块加载 + +Viper 的 `import` 是编译时操作,不涉及运行时模块加载。完整的模块解析流程与两阶段编译紧密耦合: + +1. **阶段一**:从入口文件出发,通过 `import` 语句递归发现所有可达的源文件 +2. **阶段一**:对每个源文件生成 `.pyi` 签名存根和 `.stub.ll` LLVM IR 声明 +3. **阶段二**:加载所有 `.pyi` 和 `.stub.ll`,构建共享符号表 +4. **阶段二**:翻译源文件时,通过符号表解析跨模块的类型和函数引用 +5. **阶段二**:将被引用模块的 `.stub.ll` 声明嵌入到生成的 `.ll` 文件头部 + +### 类型存根文件(.pyi) + +`.pyi` 文件是模块的声明接口,由 `PythonToStubConverter` 在阶段一生成。存根文件的内容规则: + +| 元素 | 存根表示 | +|------|---------| +| `t.CDefine` 常量 | 保留完整定义(含赋值) | +| `t.CDefine` 函数 | 保留完整函数体 | +| 普通函数 | 仅签名 + `c.State` | +| 全局变量 | 添加 `t.CExtern` | +| 类定义 | 保留成员类型注解和方法签名 | + +存根文件还自动添加宏守卫(`c.CIfndef`/`c.CEndif()`),防止重复包含。 + +### SHA1 命名空间与导入 + +每个模块的函数和结构体在 LLVM IR 中自动加上 SHA1 前缀,消除跨模块符号冲突。只有标记为 `t.CExport` 的函数保持原始名称。详见 [01-overview.md 中 SHA1 命名空间机制](01-overview.md#sha1-命名空间机制)。 + +### 符号表 + +所有模块的类型信息统一存储在 `SymbolTable` 中,包含: +- 结构体/联合体/枚举定义 +- 函数签名 +- 全局变量 +- typedef 别名 +- `t.CDefine` 常量 + +### 跨模块类型引用 + +```python +# 在 fat32_types.py 中定义 +class fat32_fileinfo: + fname: list[t.CChar, 13] + attr: t.CUInt8T + file_size: t.CUInt32T + +# 在其他模块中引用 +import fat32_types +info: fat32_types.fat32_fileinfo +``` + +跨模块引用的结构体在 LLVM IR 中使用 SHA1 前缀的类型名: +```llvm +%".fat32_fileinfo" = type { [13 x i8], i8, i32 } +``` + +## 项目 includes 配置 + +在 `project.json` 中配置模块搜索路径: + +```json +{ + "includes": ["../.."], + "source_dir": "./Kernel" +} +``` + +编译器会在 `includes` 指定的目录中搜索导入的模块。 + + +# 11 - 内置函数与运算符 + +Viper 支持 Python 内置函数和运算符,编译为对应的 C/LLVM 操作。 + +## 内置函数 + +### print + +```python +print("Hello, World!") # 调用 puts 或 printf +print(x, y) # 多参数输出 +``` + +### len + +```python +n: t.CInt = len(array) # 获取数组长度(编译时常量) +``` + +### sizeof + +```python +s: t.CSizeT = sizeof(t.CInt) # sizeof(int) +s: t.CSizeT = obj.__sizeof__() # sizeof(obj) +``` + +### abs + +```python +a: t.CInt = abs(x) # abs(x) +``` + +### int / float / bool + +```python +i: t.CInt = int(3.14) # (int)3.14 +f: t.CFloat = float(42) # (float)42 +b: t.CBool = bool(x) # x != 0 +``` + +### min / max + +```python +m: t.CInt = min(a, b) # a < b ? a : b +m: t.CInt = max(a, b) # a > b ? a : b +``` + +### chr / ord + +```python +c: t.CChar = chr(65) # 'A' (整数转字符) +n: t.CInt = ord('A') # 65 (字符转整数) +``` + +### range + +用于 `for` 循环: + +```python +for i in range(10): pass # for (int i = 0; i < 10; i++) +for i in range(5, 10): pass # for (int i = 5; i < 10; i++) +for i in range(0, 10, 2): pass # for (int i = 0; i < 10; i += 2) +``` + +## 运算符 + +### 算术运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a + b` | `a + b` | 加法 | +| `a - b` | `a - b` | 减法 | +| `a * b` | `a * b` | 乘法 | +| `a / b` | `a / b` | 浮点除法 | +| `a // b` | `a / b` | 整数除法 | +| `a % b` | `a % b` | 取模 | +| `a ** b` | `pow(a, b)` | 幂运算 | +| `-a` | `-a` | 取负 | +| `+a` | `+a` | 正号 | + +### 位运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a & b` | `a & b` | 按位与 | +| `a \| b` | `a \| b` | 按位或 | +| `a ^ b` | `a ^ b` | 按位异或 | +| `~a` | `~a` | 按位取反 | +| `a << n` | `a << n` | 左移 | +| `a >> n` | `a >> n` | 右移 | + +> **注意**:`|` 运算符在类型注解中表示类型组合,在表达式中表示按位或。编译器根据上下文区分。 + +### 比较运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a == b` | `a == b` | 等于 | +| `a != b` | `a != b` | 不等于 | +| `a < b` | `a < b` | 小于 | +| `a > b` | `a > b` | 大于 | +| `a <= b` | `a <= b` | 小于等于 | +| `a >= b` | `a >= b` | 大于等于 | + +### 逻辑运算符 + +| Viper | C 等价 | 说明 | +|-------|--------|------| +| `a and b` | `a && b` | 逻辑与 | +| `a or b` | `a \|\| b` | 逻辑或 | +| `not a` | `!a` | 逻辑非 | + +### 增强赋值运算符 + +| Viper | C 等价 | +|-------|--------| +| `a += b` | `a += b` | +| `a -= b` | `a -= b` | +| `a *= b` | `a *= b` | +| `a //= b` | `a /= b` | +| `a %= b` | `a %= b` | +| `a <<= b` | `a <<= b` | +| `a >>= b` | `a >>= b` | +| `a &= b` | `a &= b` | +| `a \|= b` | `a \|= b` | +| `a ^= b` | `a ^= b` | + +## 类型自动转换 + +Viper 在算术运算中自动进行类型提升: + +1. **整数宽度提升**:较窄的整数类型自动扩展为较宽的类型 + - 有符号类型使用符号扩展(`sext`) + - 无符号类型使用零扩展(`zext`) + +2. **整数到浮点**:整数与浮点数运算时,整数自动转换为浮点 + +3. **浮点精度提升**:`float` 与 `double` 运算时,`float` 提升为 `double` + +## 指针算术 + +指针与整数的加法支持自动计算偏移: + +```python +buf: t.CUInt32T | t.CPtr +buf[i] = value # buf[i] = value; (自动计算偏移) +``` + +## 数组下标 + +```python +buf: list[t.CChar, 64] +buf[0] = 'H' # buf[0] = 'H'; +buf[i] = value # buf[i] = value; + +info.fname[0] # info.fname[0] (结构体数组成员访问) +``` + +## 成员访问 + +```python +obj.x # obj.x (直接成员) +obj.method() # obj.method() (方法调用) +ptr.x # ptr->x (指针自动解引用) +``` + +## 字符串字面量 + +字符串字面量编译为 C 字符串常量(`const char*`),其为只读: + +```python +serial.puts("Hello") # 传递 const char* +``` + +### 字符串索引 + +```python +msg: t.CConst | str = "Hello" +serial.puts(c.Addr(msg[4])) # 访问 msg[4] 的地址,输出 'o' +``` + +## C 标准库函数 + +Viper 内置支持以下 C 标准库函数的直接调用: + +| 函数 | 签名 | +|------|------| +| `strlen(s)` | `size_t strlen(const char* s)` | +| `memset(s, c, n)` | `void* memset(void* s, int c, size_t n)` | +| `memcpy(d, s, n)` | `void* memcpy(void* d, const void* s, size_t n)` | +| `memmove(d, s, n)` | `void* memmove(void* d, const void* s, size_t n)` | +| `memcmp(a, b, n)` | `int memcmp(const void* a, const void* b, size_t n)` | +| `strcmp(a, b)` | `int strcmp(const char* a, const char* b)` | +| `strncmp(a, b, n)` | `int strncmp(const char* a, const char* b, size_t n)` | +| `strcpy(d, s)` | `char* strcpy(char* d, const char* s)` | +| `strncpy(d, s, n)` | `char* strncpy(char* d, const char* s, size_t n)` | +| `strcat(d, s)` | `char* strcat(char* d, const char* s)` | +| `puts(s)` | `int puts(const char* s)` | +| `atoi(s)` | `int atoi(const char* s)` | +| `atol(s)` | `long atol(const char* s)` | +| `abs(x)` | `int abs(int x)` | +| `labs(x)` | `long labs(long x)` | + +部分模式比如裸机没有 libc,则不能使用这些函数,即使环境满足,Viper 标准库也可能和 C 标准库冲突。 + + +# 12 - 项目配置与构建 + +Viper 项目使用 `project.json` 配置文件管理编译和链接参数。 + +## project.json 结构 + +```json +{ + "name": "Kernel", + "version": "1.0.0", + "source_dir": "./Kernel", + "temp_dir": "./temp", + "output_dir": "./output", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "--strip-all" + ], + "output": "kernel.bin" + }, + "includes": ["../../includes"], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} +``` + +## 配置项说明 + +### 基本信息字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `name` | string | 项目名称 | +| `version` | string | 项目版本 | +| `source_dir` | string | 源代码目录(相对于 project.json 所在目录) | +| `temp_dir` | string | 临时文件目录(存放 .pyi 存根等) | +| `output_dir` | string | 输出文件目录 | + +### 编译器配置 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `compiler.cmd` | string | 编译器命令(通常为 `llc`) | +| `compiler.flags` | string[] | 编译器标志 | + +常用 `llc` 标志: +- `-filetype=obj`:输出目标文件 +- `-mtriple=x86_64-none-elf`:目标三元组 +- `-relocation-model=static`:静态重定位 +- `-O2`:优化级别 + +### 链接器配置 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `linker.cmd` | string | 链接器命令(通常为 `ld.lld.exe`) | +| `linker.flags` | string[] | 链接器标志 | +| `linker.output` | string | 输出文件名 | + +常用 `ld.lld` 标志: +- `-m elf_x86_64`:ELF x86_64 格式 +- `-T linker.ld`:链接脚本 +- `--oformat elf64-x86-64`:输出 ELF 格式 +- `--oformat binary`:输出原始二进制(用于内核) +- `--strip-all`:去除所有符号信息 + +### 目标平台配置 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `target.triple` | string | LLVM 目标三元组 | +| `target.datalayout` | string | LLVM 数据布局字符串 | + +默认值: +- triple: `x86_64-none-elf` +- datalayout: `e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128` + +### 包含路径 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `includes` | string[] | 模块搜索路径列表 | + +### 编译选项 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `options.slice_level` | int | 切片优化级别(0-3) | +| `options.target` | string | 编译目标(目前仅支持 `llvm`) | +| `options.strict_mode` | bool | 严格模式 | + +## 切片优化级别 + +| 级别 | 名称 | 说明 | +|------|------|------| +| `0` | `no_optimize` | 无优化 | +| `1` | `conservative` | 保守优化 | +| `2` | `moderate` | 适度优化 | +| `3` | `aggressive` | 激进优化(默认) | +| `s` | `size` | 体积优化 | + +## 项目结构示例 + +### 内核项目 + +``` +VKernel/ +├── project.json # 项目配置 +├── linker.ld # 链接脚本 +├── Kernel/ # 源代码目录 +│ ├── main.py # 内核入口 +│ ├── bootinfo.py +│ ├── intr/ +│ │ ├── gdt.py +│ │ ├── idt.py +│ │ └── syscall.py +│ ├── drivers/ +│ │ ├── serial/ +│ │ ├── video/ +│ │ ├── storage/ +│ │ └── ... +│ └── ... +├── temp/ # 临时文件(自动生成) +│ ├── .pyi # 签名存根(阶段一生成) +│ ├── .stub.ll # LLVM IR 声明(阶段一生成) +│ └── _sha1_map.txt # SHA1→源文件路径映射 +└── output/ # 输出文件(自动生成) + ├── kernel.bin # 内核二进制 + ├── *.deps.json # 依赖信息 + └── linker.ld # 生成的链接脚本 +``` + +### 应用项目 + +``` +HelloWorld/ +├── project.json # 项目配置 +├── linker.ld # 链接脚本 +├── main.py # 应用入口 +├── temp/ # 临时文件 +└── output/ # 输出文件 + └── helloworld.elf # ELF 可执行文件 +``` + +### 库项目 + +``` +SerialLogger/ +├── project.json # 项目配置 +├── linker.ld # 链接脚本 +├── serial_logger.py # 库源码 +├── temp/ # 临时文件 +└── output/ # 输出文件 +``` + +## 链接脚本 + +ViperOS 使用自定义链接脚本控制内存布局: + +```ld +ENTRY(_start) + +SECTIONS +{ + . = 0x100000; /* 加载地址 */ + + .text : { + *(.text.startup) + *(.text*) + } + + .rodata : { + *(.rodata*) + } + + .data : { + *(.data*) + } + + .bss : { + *(.bss*) + *(COMMON) + } +} +``` + +## 编译命令 + +TransPyC 编译器的基本用法(注:适用于单个文件,而不适用于工程): + +```bash +python TransPyC.py -f InputFile -o OutputFile [options] +``` + +### 命令行参数 + +| 参数 | 说明 | +|------|------| +| `-f` | 输入文件路径 | +| `-o` | 输出文件路径 | +| `-wh` | 头文件列表 | +| `-debug` | 调试输出文件 | +| `-cc` | 编译命令 | +| `-cflags` | 编译标志 | +| `-run` | 编译后运行 | +| `-args` | 运行参数 | +| `-h` | 辅助文件(C 或 Python) | + +工程编译使用: +```bash +python Projectrans.py xxx.json +``` +即可 + +## 增量编译 + +TransPyC 支持基于 SHA1 的增量编译,与两阶段编译模型紧密耦合: + +### 阶段一增量 + +- 每个源文件按内容计算 SHA1 哈希(16位) +- 如果 `.pyi` 和 `.stub.ll` 已存在,则跳过生成(缓存命中) +- 只有源文件内容变化导致 SHA1 变化时才重新生成声明接口 +- `temp/_sha1_map.txt` 记录 SHA1 → 源文件路径的映射,格式为 `:` + +### 阶段二增量 + +- 阶段二加载 `temp/_sha1_map.txt` 重建 SHA1 映射 +- 过滤掉不在当前 SHA1 映射中的旧 `.pyi` 和 `.stub.ll` 文件 +- 共享符号表一次性构建,避免每个文件重复加载 + +### SHA1 命名空间与增量编译的协同 + +SHA1 同时服务于命名空间隔离和增量编译: +- **命名空间**:非 `t.CExport` 函数和结构体使用 SHA1 前缀,消除符号冲突 +- **增量编译**:SHA1 不变则缓存命中,SHA1 变化则重新生成 + +详见 [01-overview.md 中 SHA1 命名空间机制](01-overview.md#sha1-命名空间机制)。 + + diff --git a/vpsdk/window.py b/vpsdk/window.py new file mode 100644 index 0000000..453dd1e --- /dev/null +++ b/vpsdk/window.py @@ -0,0 +1,93 @@ +import t, c +from . import syscall as syscall +from stdint import * + +STYLE_CLASSIC: t.CDefine = 0 +STYLE_WIN10: t.CDefine = 1 +STYLE_WIN11: t.CDefine = 2 +STYLE_WIN7: t.CDefine = 3 +STYLE_WINXP: t.CDefine = 4 + +EVENT_TYPE_NONE: t.CDefine = 0 +EVENT_TYPE_CLICK: t.CDefine = 1 +EVENT_TYPE_HOVER: t.CDefine = 2 +EVENT_TYPE_RELEASE: t.CDefine = 3 +EVENT_TYPE_HOVER_LEAVE: t.CDefine = 4 +EVENT_TYPE_KEY: t.CDefine = 5 +EVENT_TYPE_KEY_RELEASE: t.CDefine = 6 +EVENT_TYPE_PING: t.CDefine = 7 +EVENT_TYPE_RESIZE: t.CDefine = 8 + +KEY_STATE_PRESS: t.CDefine = 1 +KEY_STATE_RELEASE: t.CDefine = 2 +KEY_STATE_REPEAT: t.CDefine = 3 + +CURSOR_DEFAULT: t.CDefine = 0 +CURSOR_IBEAM: t.CDefine = 1 +CURSOR_RESIZE_EW: t.CDefine = 2 +CURSOR_RESIZE_NS: t.CDefine = 3 +CURSOR_RESIZE_NWSE: t.CDefine = 4 + +VK_LEFT: t.CDefine = -1 +VK_RIGHT: t.CDefine = -2 +VK_UP: t.CDefine = -3 +VK_DOWN: t.CDefine = -4 +VK_HOME: t.CDefine = -5 +VK_END: t.CDefine = -6 +VK_DELETE: t.CDefine = -7 +VK_PRIOR: t.CDefine = -8 +VK_NEXT: t.CDefine = -9 + +@c.Attribute(t.attr.packed) +@t.Object +class InputEvent: + type: t.CUInt32T + win_id: t.CInt + x: t.CInt + y: t.CInt + button: t.CUInt8T + _pad0: t.CUInt8T + _pad1: t.CUInt8T + _pad2: t.CUInt8T + +def create_window(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | t.CChar | t.CPtr, style: t.CInt) -> t.CInt: + return t.CInt(syscall._syscall6(t.CUInt64T(syscall.CREATE_WINDOW), t.CUInt64T(x), t.CUInt64T(y), t.CUInt64T(w), t.CUInt64T(h), t.CUInt64T(title), t.CUInt64T(style))) + +def draw_text(win_id: t.CInt, x: t.CInt, y: t.CInt, text: t.CConst | t.CChar | t.CPtr, color: t.CUInt32T) -> t.CInt: + return t.CInt(syscall._syscall5(t.CUInt64T(syscall.DRAW_TEXT), t.CUInt64T(win_id), t.CUInt64T(x), t.CUInt64T(y), t.CUInt64T(text), t.CUInt64T(color))) + +def get_winbuf(win_id: t.CInt) -> UINT32PTR: + return t.CVoid(syscall._syscall2(t.CUInt64T(syscall.GET_WINBUF), t.CUInt64T(win_id), 0), t.CPtr) + +def get_win_size(win_id: t.CInt) -> t.CInt: + return t.CInt(syscall._syscall2(t.CUInt64T(syscall.GET_WIN_SIZE), t.CUInt64T(win_id), 0)) + +def flush(win_id: t.CInt) -> t.CVoid: + syscall._syscall2(t.CUInt64T(syscall.WIN_FLUSH), t.CUInt64T(win_id), 0) + +def mainloop(win_id: t.CInt) -> t.CVoid: + syscall._syscall1(t.CUInt64T(syscall.WAIT_WINDOW), t.CUInt64T(win_id)) + +def poll_event(evt: InputEvent | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall1(t.CUInt64T(syscall.POLL_EVENT), t.CUInt64T(evt))) + +def is_active(win_id: t.CInt) -> t.CInt: + return t.CInt(syscall._syscall2(t.CUInt64T(syscall.WAIT_WINDOW), t.CUInt64T(win_id), 1)) + +def set_cursor_type(win_id: t.CInt, cursor_t: t.CInt) -> t.CVoid: + syscall._syscall2(t.CUInt64T(syscall.SET_CURSOR), t.CUInt64T(win_id), t.CUInt64T(cursor_t)) + +def set_title(win_id: t.CInt, title: t.CConst | t.CChar | t.CPtr) -> t.CInt: + return t.CInt(syscall._syscall2(t.CUInt64T(syscall.SET_TITLE), t.CUInt64T(win_id), t.CUInt64T(title))) + +def set_geometry(win_id: t.CInt, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt) -> t.CInt: + return t.CInt(syscall._syscall5(t.CUInt64T(syscall.SET_GEOMETRY), t.CUInt64T(win_id), t.CUInt64T(x), t.CUInt64T(y), t.CUInt64T(w), t.CUInt64T(h))) + +def set_resizable(win_id: t.CInt, rw: t.CInt, rh: t.CInt) -> t.CInt: + return t.CInt(syscall._syscall3(t.CUInt64T(syscall.SET_RESIZABLE), t.CUInt64T(win_id), t.CUInt64T(rw), t.CUInt64T(rh))) + +#class Window: +# HWND: t.CInt +# def __init__(self): +# self.HWND = create_window(100, 100, 100, 100) +