diff --git a/.gitignore b/.gitignore index e02fde13..faafcedb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /lib/*.ppu /lib/*.a /lib/*.o +/lib/x86_64/ +/bin/x86_64/ /release/*.svg /release/*.iso /bin/*.bin diff --git a/Dockerfile b/Dockerfile index 66febdd2..24fdaea4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN dpkg --add-architecture i386 RUN apt-get update && apt-get install -y \ curl dos2unix wget git make nasm binutils xorriso grub-pc-bin gcc gcc-multilib \ - python3 python3-pip python3-pil && \ + python3 python3-pip python3-pil fp-compiler && \ apt-get clean my room RUN pip3 install --no-cache-dir --break-system-packages "mkdocs>=1.6,<2" mkdocs-material @@ -20,6 +20,6 @@ RUN curl -sL https://sourceforge.net/projects/freepascal/files/Linux/$FPC_VERSIO ADD https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver /usr/bin/semver RUN chmod +x /usr/bin/semver WORKDIR /code -RUN find . -type f -print0 | xargs -0 dos2unix +# Sources are mounted at runtime; the CMD below normalizes build scripts. ENTRYPOINT ["/bin/bash", "-c"] -CMD ["find toolchain -name '*.sh' -exec dos2unix {} + 2>/dev/null; find toolchain -name '*.sh' -exec chmod +x {} +; bash toolchain/compile.sh"] \ No newline at end of file +CMD ["find toolchain -name '*.sh' -exec dos2unix {} + 2>/dev/null; find toolchain -name '*.sh' -exec chmod +x {} +; bash toolchain/compile.sh"] diff --git a/compat/lmemorymanager.pas b/compat/lmemorymanager.pas index fda09ed4..b49e6e6d 100644 --- a/compat/lmemorymanager.pas +++ b/compat/lmemorymanager.pas @@ -22,12 +22,12 @@ type THeapPageHeader = memory.heap.THeapPageHeader; procedure init; -function kalloc(size : uint32) : void; -function klalloc(size : uint32) : void; -procedure klfree(address : uint32); -function kpalloc(address : uint32) : void; +function kalloc(size : SizeUInt) : void; +function klalloc(size : SizeUInt) : void; +procedure klfree(address : PtrUInt); +function kpalloc(address : PtrUInt) : void; procedure kfree(area : void); -function lmm_total_free : uint32; +function lmm_total_free : SizeUInt; function lmm_page_count : uint32; implementation @@ -37,22 +37,22 @@ begin memory.heap.init; end; -function kalloc(size : uint32) : void; inline; +function kalloc(size : SizeUInt) : void; inline; begin kalloc := memory.heap.kalloc(size); end; -function klalloc(size : uint32) : void; inline; +function klalloc(size : SizeUInt) : void; inline; begin klalloc := memory.heap.klalloc(size); end; -procedure klfree(address : uint32); inline; +procedure klfree(address : PtrUInt); inline; begin memory.heap.klfree(address); end; -function kpalloc(address : uint32) : void; inline; +function kpalloc(address : PtrUInt) : void; inline; begin kpalloc := memory.heap.kpalloc(address); end; @@ -62,7 +62,7 @@ begin memory.heap.kfree(area); end; -function lmm_total_free : uint32; inline; +function lmm_total_free : SizeUInt; inline; begin lmm_total_free := memory.heap.lmm_total_free; end; diff --git a/doc/planning/memoryallocator.md b/doc/planning/memoryallocator.md index 84cbd4dc..7463733f 100644 --- a/doc/planning/memoryallocator.md +++ b/doc/planning/memoryallocator.md @@ -205,7 +205,7 @@ Key constants: - `DATA_OFFSET = $10000` — data area starts at 64 KB into each page - `LARGE_ALLOC_MAGIC = $FFFFFFFE` — sentinel for klalloc'd multi-page allocations -Interrupt safety: `pushfd; cli; pop saved_flags` (stack-balanced) + `restore_if(saved_flags)` +Interrupt safety: `pushfd; cli; pop saved_flags` (stack-balanced) + `arch.platform.cpu.unlock(saved_flags)` inline procedure that only restores IF via `sti`, avoiding dangerous EFLAGS restoration (IOPL, NT, VM, AC bits that caused Bad-TSS on IRET). @@ -256,7 +256,7 @@ list, fixes Search_Page, calls `free_page_at_address`). Skips Root_Page. | Bug | Root Cause | Fix | |-----|-----------|-----| -| Bad TSS exception during network processing | `pushfd`/`popfd` across separate asm blocks: FPC doesn't track ESP changes in asm, causing EFLAGS corruption (NT=1, IOPL wrong) → Bad TSS on IRET | Stack-balanced `pushfd; cli; pop local_var` + `restore_if()` that only does `sti` if IF was set | +| Bad TSS exception during network processing | `pushfd`/`popfd` across separate asm blocks: FPC doesn't track ESP changes in asm, causing EFLAGS corruption (NT=1, IOPL wrong) → Bad TSS on IRET | Stack-balanced `pushfd; cli; pop local_var` + `arch.platform.cpu.unlock()` that only does `sti` if IF was set | | GPF from SSE MOVAPS in doublebuffer flush | `SIZE_PREFIX=4`→8 gave 8-byte alignment; `MOVAPS` needs 16-byte. `klalloc` returned `page+8` (not 16-aligned) | `SIZE_PREFIX=16` (4B size + 12B pad), klalloc header expanded to 16 bytes | | Pages never freed (historic) | `free_page` extracted wrong block index from PDE Address field | Fixed extraction to `Address SHR 10` — page freeing works for the first time | diff --git a/doc/planning/x86_64.md b/doc/planning/x86_64.md new file mode 100644 index 00000000..a46ec125 --- /dev/null +++ b/doc/planning/x86_64.md @@ -0,0 +1,579 @@ +# x86-64 migration + +## Status + +The x86-64 target boots into Pascal with exception handling, physical/virtual +allocation, the shared heap, PIC/PIT interrupts and tested kernel-context +preemption. Fatal faults report over COM1. +The native desktop, terminal, storage, E1000 networking and WASM runtime now run. +The existing x86 target remains the default. + +## Architecture boundaries and reuse + +Follow the namespace convention in [namespace.md](namespace.md): architecture +units live under `src/arch//` and use `arch..*` names. +The compiler-required `system.pas` retains its special name. + +- `src/arch/x86_64/boot/stub.asm`: 32-bit Multiboot entry and long-mode transition. +- `src/arch/x86_64/arch.x86_64.boot.pas`: bootstrap entry and serial self-checks. +- `src/arch/x86_64/system.pas`: minimal compiler runtime definitions. +- `src/arch/x86_64/arch.x86_64.isr.pas`: IDT/TSS setup, frame validation, and + allocation-free exception reporting. +- `src/arch/x86_64/isr/entry.asm`: generated entry stubs, register/FX save and + restore, `iretq`, emergency stacks, and controlled fault probes. +- `src/debug/debug.early.pas`: reusable architecture-independent diagnostic + formatting, supplied with a character sink by `arch.x86_64.debug`. +- `src/arch/common/system.types.inc`: shared scalar/compiler definitions extracted + from the existing system unit; native widths follow the selected CPU target. +- `toolchain/target.sh`: target selection shared by the existing build steps. +- `toolchain/x86_64/linker.script`: bootstrap address layout. + +Do not copy the i386 kernel, drivers, heap, scheduler policy, or runtime wholesale. +As each subsystem is enabled, retain portable logic in its existing namespace +and put architecture-specific operations behind the appropriate architecture +interface. External `lvgl/` and `wasuro/` sources must remain unchanged. + +## Build and boot + +Rebuild the builder image when first adding the native x86-64 FPC compiler: + +```powershell +docker-compose down --remove-orphans +docker-compose build builder +``` + +Build x86-64 using the normal build command with an explicit target: + +```powershell +$env:ASURO_ARCH = 'x86_64' +docker-compose run builder | Tee-Object -FilePath .\build.log +Remove-Item Env:ASURO_ARCH +Select-String -Path .\build.log -Pattern "Error|Fatal|Success|No errors|Failed" -CaseSensitive +``` + +Outputs are `Asuro-x86_64.iso`, `bin/x86_64/kernel.bin`, and `lib/x86_64/`. +Without `ASURO_ARCH`, the existing x86 build still produces `Asuro.iso`, +`bin/kernel.bin`, and `lib/`. Cleaning and linking only consider the selected +target's objects. The x86 compiler search path excludes `src/arch/x86_64`. + +Use VirtualBox for runtime validation. With the configured Asuro VM stopped, +select `Asuro-x86_64.iso` as its optical medium, then start it through the normal +VM workflow and read `C:\Temp\asuro.log`. Restore the original optical medium +after testing. The normal image enumerates storage and mounts attached filesystems; boot checks read attached disks without writing them. + +Expected serial output: + +```text +ASURO x86_64: IDT/TSS ready; DF/NMI/MC emergency stacks installed +ASURO x86_64: entered Pascal +PASS: long mode, Multiboot handoff, 64-bit Pascal types +PASS: INT3 frame, GPRs, RFLAGS, x87/SSE and IRETQ +PASS: page-fault error code, CR2 and IRETQ recovery +EXCEPTION_TESTS_PASSED=0000000000000002 +EXCEPTION_TESTS_FAILED=0000000000000000 +ASURO x86_64: desktop ready +``` + +An early single `C` means CPUID/long-mode support is unavailable; `B` means an +invalid Multiboot magic value. The Pascal checks validate the handoff magic, +a non-null info pointer, native/fixed type widths, wide arithmetic, and EFER.LMA. + +### Repeatable VirtualBox fault checks + +The default build runs two returning self-tests. `INT3` verifies the saved frame, +all 15 general registers including high halves, CF/DF, x87 and XMM0/XMM15 state, +and `iretq`. The handler intentionally destroys FX state before the return path +restores it. A write to unmapped `0x100000000` verifies hardware error code `2`, +CR2, and recovery by changing RIP only for the exact expected probe. + +Use the bounded test harness with the configured VM powered off: + +```powershell +.\toolchain\x86_64\test_boot.ps1 -Test none +``` + +For deliberately fatal tests, select `ud`, `pf`, or `df` when building, then use +the matching harness option. For example: + +```powershell +$env:ASURO_ARCH = 'x86_64' +$env:ASURO_BOOT_TEST = 'df' +docker-compose run builder | Tee-Object -FilePath .\build.log +Remove-Item Env:ASURO_BOOT_TEST +Remove-Item Env:ASURO_ARCH +.\toolchain\x86_64\test_boot.ps1 -Test df +``` + +The harness uses `localenv.json`, temporarily attaches the ISO, starts the VM +through `virtualbox-wrapper.ps1 -command up -Headless -NoMonitor`, validates +serial output, captures registers, stops the VM, and restores the original +optical medium in a `finally` block. The new wrapper switches are optional; +the existing interactive workflow is unchanged. Storage disks are not changed. + +Rebuild without `ASURO_BOOT_TEST` after testing to restore the normal image. + +## Bootstrap layout and limitations + +- GRUB loads the kernel at physical/virtual 1 MiB using explicit Multiboot v1 + load-address fields; the payload is ELF64 with a 32-bit assembly entry. +- Six reserved 4 KiB pages hold the PML4, PDPT, and four page directories. + They identity-map the first 4 GiB with 2 MiB leaves. +- A reserved 16 KiB stack is aligned for the SysV AMD64 calling convention. + These bounded early allocations exist before any heap and must remain + reserved when the physical allocator is introduced. +- Paging, PAE, long mode, x87, and SSE2 support are enabled before calling Pascal. +- A 256-entry IDT uses normalized 64-bit frames. TSS IST1 supplies a 16 KiB + double-fault stack; IST2 and IST3 supply separate 8 KiB NMI and machine-check + stacks. The 4 KiB IDT, 104-byte TSS, and emergency stacks are bounded early + reservations before the allocator exists. Stack guard pages remain future work. +- Entry preserves GPRs and captures CR2 before Pascal code. It clears DF, aligns + the call stack, and saves/restores x87/SSE state using FXSAVE64/FXRSTOR64. + AVX/OSXSAVE and lazy-FPU switching are not enabled. Future changes must extend + this state policy before enabling those features. +- IRQ0 is enabled during timer and context tests, then masked before the final + halt. PIC acknowledgement and spurious IRQ7/15 handling are implemented. + General driver hooks and APIC/SMP remain future work. +- The low identity map is temporary. It is not a final process address-space + design and does not enforce page-level W^X. New heap mappings use 4 KiB pages + at `0xFFFF800000000000`, spanning up to 16 GiB with NX when supported. + Their backing frames can reside above 4 GiB. +- Unsupported runtime operations intentionally fail to link rather than silently + delegating to a hosted runtime or pretending to succeed. + +## Remaining migration work + +| Area | Existing assumptions / next work | +|---|---| +| Runtime | Keep compiler metadata shared; initialization dispatch is shared; implement further x86-64 helpers as consumers are ported. | +| Common kernel entry | `src/asuro.pas` currently imports x86 units; separate common boot sequencing from architecture setup before sharing full initialization. | +| Exceptions | IDT/TSS, IST stacks, normalized frames, diagnostics, and tested `iretq` paths are implemented. NMI/MC injection, user transitions, guard pages, and general IRQ dispatch remain. | +| ABI | Bootstrap interrupt alignment and x87/SSE preservation are tested. Audit red-zone assumptions for each added compiler/library and extend preservation before enabling AVX or lazy-FPU switching. | +| Memory | PMM, VMM and shared heap implemented. Final process layout, guards, MMIO and DMA APIs remain. | +| Scheduler/loader | Shared scheduling, kernel preemption and process lifecycle tested. Process address spaces and executable loading remain. | +| Drivers/storage | Audit `uint32(pointer)` conversions, MMIO and DMA translations, and hardware address limits. Keep disk/USB/controller structures fixed-width. | +| Graphics | VESA calls into V86, which cannot operate in long mode. Initially use a bootloader framebuffer; rebuild LVGL and verify Pascal/C bindings later. | +| Validation | Add deliberate exception tests, allocator checks, preemption checks, and storage tests as their implementations become available. | + +Next milestone: port filesystem/console services and complete driver address/DMA +audits. Kernel process lifecycle now uses the shared manager. Do not enable the +desktop/storage stack before its dependencies are ported and tested. + +## Validation performed — 2026-11-07 + +- x86-64 compile, link, GRUB header validation, and ISO generation passed. +- The existing Asuro VirtualBox VM printed all three expected serial lines. +- First-milestone VirtualBox registers showed `CS=0x18`, `CR0=0x80000013`, + `CR3=0x103000`, `CR4=0x620`, and `EFER=0x500` (LME and LMA set). +- Disassembly confirmed 64-bit Pascal code, wide arithmetic, and aligned calls + in the bootstrap. This does not establish interrupt safety for future code. +- The full default x86 build passed, including compilation, linking, ISO, and + documentation generation, with the x86-64 output directory still present. +- The VM was stopped and its original optical medium restored after testing. +- VirtualBox passed the normal boot tests and deliberate invalid-opcode, + page-fault, and double-fault builds. The final write-fault test preserved + error code `2` and CR2 `0x100000000`. +- The exception milestone's normal boot captured `TR=0x20`, confirming the + installed TSS selector, with long mode still active (`EFER=0x500`). +- Double-fault injection set RSP to unmapped `0x100001000`. Diagnostics reported + vector `8`, error `0`, the original RSP, and a frame inside the IST1 stack. +- Fatal exception reports include all 15 GPRs, RIP/CS/RFLAGS/RSP/SS, error code, + CR2, and frame address. CR2 is the last page-fault address and may be stale + for other exception types. + +Runtime evidence is saved locally in the ignored `bin/x86_64/boot.log` and +`bin/x86_64/registers.log` files for the first milestone, and `boot-.log` / +`registers-.log` for the exception milestone. Unsupported-CPU boot checks and NMI/MC injection have not yet been performed. + +The interrupt-frame and IST layout follows the +[AMD64 System Programming manual, chapter 8](https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf). + +## Memory, timer and shared scheduling progress + +The PMM uses 4 KiB frames and dynamically placed bitmaps, with a 64 GiB management +cap. Boot metadata, modules, kernel ranges, low bootstrap memory and allocator +metadata stay reserved. VMM allocates high virtual arenas and rolls back both +page tables and backing frames on failure. Page tables remain below 4 GiB; +backing frames may reside above it. + +`memory.heap` is shared between targets, with native-width pointer/size APIs, +16-byte alignment, overflow rejection and large-allocation rollback. +`memory.bitmap` supplies the common bitmap operations. `boot.multiboot` owns +fixed-width protocol records, with aliases retained in the x86 boot unit. +`arch.common.legacyirq` shares PIC programming and PIT constants. + +`io.types` separates data contracts from console initialization. `proc.types` +uses native saved-stack addresses. The existing round-robin/quantum policy now +lives in `proc.scheduler`, called by both the i386 process manager and x86-64 +preemption tests. Process creation, messaging, return/exit, resource cleanup and reaping now use +`proc.mgr` on x86-64. Descriptor tables are now active; console commands, executable loading and +full filesystem/controller integration remain unavailable in the bootstrap. + +VirtualBox validation has passed at 64 MiB, 2 GiB and 6 GiB. The 6 GiB run mapped, +wrote, read and released RAM above 4 GiB. Tests cover PMM accounting and invalid +frees, VMM rollback under injected allocation failures, heap alignment/zeroing/ +large allocations, timer delivery, spurious IRQ7/15, IF preservation and CLI. +Two heap-backed kernel contexts preserve distinct 64-bit registers, x87 and XMM +state across timer preemption, then release their stacks after returning to the +bootstrap context. Only the BSP is active; IRQ exclusion is not an SMP lock. + +```powershell +.\toolchain\x86_64\test_boot.ps1 -Test none -MemoryMB 64 +.\toolchain\x86_64\test_boot.ps1 -Test none -MemoryMB 6144 +``` + +The harness restores original VM RAM and optical media. Logs use +`bin/x86_64/boot--.log` and matching register filenames; zero means +use the configured RAM. All subsystem `*_TESTS_FAILED` fields must be zero. +PIC handling follows the [Intel 8259A datasheet](https://www.cs.umb.edu/cs341/Intel8259/I8259APIC.pdf). + +The final i386 regression build passed compilation, linking, ISO and documentation +steps. VirtualBox reached `Boot complete - halting into IDLE`; reported boot test +suites had zero failures. Evidence: `bin/x86_64/i386-regression.log`. +Fatal UD, write-PF and broken-stack DF tests also passed after the context-return +path was extended. Final normal images are built without fault injection. + +## Shared process lifecycle + +`proc.mgr` now runs on both targets with native process-pointer slots, stable +monotonic IDs, suspended creation, messaging, exit and resource ownership. +Initial stack layout lives in the architecture context units. x86-64's scheduler +adapter saves the current stack and calls the same scheduler/manager as i386. +A shared reaper process handles destruction on both targets outside IRQ context. +Timer selection and kill requests coalesce worker wakeups; cleanup runs after +registry/resource detachment. The reaper sleeps while there is no pending work. + +`core.ds.dynamic` contains the common dynamic-list implementation; existing +`core.ds.lists` exports compatible aliases/wrappers. Native addresses, checked +capacity arithmetic, overlap-safe insertion and empty-list regrowth replace +32-bit arithmetic. Deletion retains capacity; explicit `DL_ShrinkToFit` frees +excess storage without allocating inside every scheduler deletion. +`memory.raw` supplies allocation-free scalar fill/move operations. + +Process tests cover suspended publication, high-address lookup, killed tasks +never running, normal return, explicit exit, child notification waking an +awaiting parent, callback reentry without double cleanup, command argument/ +stream publication, sleep using the PIT divisor, and restored heap accounting. +The full i386 build and VirtualBox boot passed after these shared changes; +evidence is in `bin/x86_64/i386-lifecycle-regression.log`. + +The dedicated reaper revision passed all 15 process/container checks at 64 MiB +and 6 GiB, including the corrected check-before-await notification protocol. +The final i386 build and VirtualBox boot also passed with the shared reaper. +VM RAM and original optical media were restored and the VM was powered off. + +## Descriptor tables + +The shared `driver.storage.fdtable` now uses native memory operations and balanced +IRQ locking. Every x86-64 process, including system tasks, owns its normal FD +table. Storage protocol types depend on shared record definitions rather than +bringing in the linked-list implementation and its console dependencies. + +Close reserves an entry until callbacks and owned-string cleanup finish, rejects +active async references and reference-count overflow, and prevents callback +reentry from freeing it twice. Table teardown atomically rejects new users before +process removal. An exited process with active FD references remains alive; final +completion only wakes the shared reaper, which performs cleanup in process +context. Waiting does not repeatedly scan descriptors on every timer tick. + +VirtualBox passes 15 descriptor checks plus 16 lifecycle/container checks, +including an exited child held alive by an async FD reference until another task +releases it. No disk I/O is performed by these bootstrap tests. This validates +FD ownership, not full VFS operation or controller DMA support in long mode. + +The descriptor-table revision also passes the complete i386 build and VirtualBox +boot, including the existing storage/VFS suites and normal volume discovery. +Evidence: `bin/x86_64/i386-fd-regression.log`. Original VM settings are restored. + +## Shared strings and console command infrastructure + +`core.util` memory helpers accept native addresses and sizes, reusing scalar +`memory.raw` operations while retaining i386's existing large-copy REP path. +`core.strings` no longer imports the x86 utility or linked-list implementation; +all string copy addresses are native. Its common tests cover signed 64-bit +minimum formatting, overflowing substring/match bounds, and terminator size +rejection. The x86-64 system unit supplies Double-to-int64 truncation using +CVTTSD2SI without changing rounding control state. + +`io.stdio` now builds on both targets: growable buffers, bounded command parsing, +case-insensitive allocation-free lookup, registry growth, ANSI output, and +ECHO/CLEAR/HELP share one implementation. Buffer appends reserve space in bulk +and support self-append across relocation. Allocation failures retain the old +buffer; parser failures release all partially built parameters. Completion +detaches the halt owner before invoking callbacks, allowing safe reentry. + +VFS working-directory delegation and VERSION/TIME/REBOOT registration remain +gated until their platform dependencies are integrated. This is command +infrastructure, not yet an interactive keyboard/display/serial shell. No VFS or +storage-controller support is implied by compiling the console unit. + +The 64-bit bootstrap runs 162 shared string tests and 17 stdio tests. The process +suite also launches the registered ECHO through `runCommand` with real parsed +arguments and a growable output buffer, bringing it to 17 lifecycle/container +checks. The 6 GiB VirtualBox run verifies scheduled ECHO and physical RAM above +4 GiB. The complete i386 build and boot also pass, including 162 string tests, +stdio tests, normal mounts, and the existing storage/VFS suites. Evidence: +`bin/x86_64/i386-console-regression.log` and the normal x86-64 boot logs. + +## Build-selected platform adapters + +Shared Pascal units no longer select CPUs with `CPU64`/`CPU386` conditionals. +`ASURO_ARCH` selects a single `src/arch//platform` directory in the +compiler search path. Both targets implement the same `arch.platform.cpu`, +`arch.platform.memory`, `arch.platform.process`, and +`arch.platform.diagnostics` unit names. Their declarations are shared through +`src/arch/common/platform.*.inc`; implementations delegate to the existing +architecture units. Calls are resolved at compile/link time, with no runtime +architecture selection or function-pointer dispatch. + +The bitmap heap, allocation headers, process registry, scheduling policy, +strings, console buffers/parser, and descriptor lifecycle remain single shared +implementations. Native scalar widths live in each selected system unit. +`memory.raw` builds its fill pattern from native word width without a CPU branch. + +`arch.x86.startup` now registers heap/process/console startup with the boot +manager. `arch.x86.console` owns VERSION/TIME/REBOOT commands and their driver +dependencies. The x86-64 bootstrap retains its explicit startup sequence. +Unused working-directory wrappers were removed; directory operations belong to VFS. + +Existing `DEBUG_LMM` allocator guards and architecture-local fault-injection +build flags remain; these are feature/test options, not architecture selection. + +Validation: complete i386 build and VirtualBox boot, including 17 stdio checks, +162 string checks, storage/VFS suites and normal mounts. Evidence: +`bin/x86_64/i386-platform-regression.log`. x86-64 boot validation covers heap, +IRQs, contexts, descriptors, strings, console and scheduled command execution. + +The final adapter build also passes the 6 GiB VirtualBox bootstrap, including +physical memory above 4 GiB and all 162 string, 17 console, 15 descriptor, +and 17 lifecycle/container checks. VM RAM and optical media were restored; +the VM is powered off. + +Source search paths are discovered recursively from shared roots and the selected +architecture tree, with other architectures excluded. Both targets use the same +compiler invocation; `target.sh` loads compiler options and entry points from +`toolchain/targets/.sh`. +Directory arguments are NUL-delimited, sorted, and passed as an array so new +subdirectories and spaces in names do not require compile-script edits. Full +i386 and x86-64 builds pass using this discovery path. + +All shared build steps now consume the selected profile: pipeline stages, +assembler format and source roots, C/Pascal compiler options, linker format, +entry points, output locations, optional compiler runtime, and image validation. +`target.sh` loads the profile by filename instead of keeping an architecture +switch. Assembly files, object files, and static libraries are discovered; +the configured assembly entry is linked first to preserve the Multiboot header. +Initialization-table, icon, LVGL, version, and checksum generation use target +settings too. Full i386 and x86-64 builds and VirtualBox boot checks validate +this pipeline; the i386 boot includes normal mounts and existing storage tests. + +## Shared unit initialization + +Both system units now use `arch/common/system.init.inc` to dispatch the +build-generated INITFINAL table. The x86-64 profile enables the existing +`initcalls` stage, whose packed records use native procedure-pointer widths. +An initialization guard prevents repeat or reentrant dispatch. This also removes +the duplicated table walks from the i386 runtime. + +Initializers may set globals or register boot work; they must not require heap, +hardware setup, or dependency ordering. The existing generator enumerates symbols +in object-file order, so actual subsystem startup remains explicitly sequenced +on x86-64 and dependency-ordered by boot.mgr on i386. The full boot manager remains i386-only; the x86-64 composition now starts +the shared storage services explicitly (see storage integration below). + +Validation: x86-64 build and 64 MiB VirtualBox boot passed, including a real +Pascal initialization section checked after two calls to system.init. The full +i386 build and VirtualBox boot also passed, with 54 initialization entries, +normal volume mounts, and zero failures in the reported regression suites. + +## Shared filesystem foundations + +The shared FNV-1a hashes, hash map, managed/string lists, path syntax, and +filesystem registry now compile and run in long mode. String-list slots hold +native pointers; hash inputs and memory-copy addresses no longer truncate to +32 bits. Hash replacement preserves the entry count, rehashing links each item +in constant time, and container allocation failures leave published state intact. +Linked-list insertion supports the end position and string splitting shares one +implementation with rollback on allocation failure. + +`driver.storage.path` provides caller-owned absolute and normalized paths without +volume or driver dependencies. VFS delegates to this implementation on i386. +Normalization scans into one allocated output buffer, handling repeated slashes, +dot segments, and parent traversal without climbing above root. It is lexical; +symlink resolution remains VFS work. + +The filesystem registry uses native record copies, handles empty registries, +and traverses linked records directly for linear searches/probes. Its boot +registration lives in arch.x86.startup; x86-64 initializes it explicitly. +Registry tests invoke an identify callback using heap-backed volume/driver +records and restore the existing registry after the test. + +New shared checks: 9 FNV-1a, 47 hash-map, 8 list, 15 path, and 9 registry checks. +These foundation checks preceded the storage integration described below. + +The shared contiguous FIFO and I/O request pool also run on x86-64. Queue +addresses and byte-size calculations use native widths, capacity growth checks +for overflow/allocation failure, and zero-sized elements are rejected. Request +pool initialization and free-list changes use arch.platform.cpu IRQ exclusion. +Pool node recovery uses the actual request-field offset instead of assuming a +pointer-sized prefix. The bounded, heap-backed pool retains its existing +512-request capacity and lifetime. + +The 6 GiB VirtualBox run passes 22 CFIFO and 4 request-pool checks in addition +to the filesystem-foundation suites. Tests cover queue compaction/growth, +native pointer payloads, request-pool exhaustion/reuse, and callback/data copies. +Hardware storage validation is covered by the subsequent integration below. + +Final validation for this batch: both target builds pass. The x86-64 VirtualBox +bootstrap passes at 64 MiB; the 6 GiB run also passes the above-4-GiB physical +memory check. i386 reaches idle with all 114 shared foundation checks passing, +all 130 VFS checks passing, normal ISO9660/FAT32/EXT2 mounts, and zero failures +in the reported storage/controller suites. VM RAM and optical media are restored +and the VM is powered off. Logs remain in bin/x86_64/ (ignored). + +## Shared storage integration + +The x86-64 boot now starts PCI, AHCI, the shared storage request manager, volume +manager, VFS, ISO9660, FAT32 and EXT2. Boot registration belongs to target startup +units. Shared code uses build-selected CPU, memory, timer and interrupt adapters; +there are no CPU conditionals or duplicated filesystem implementations. + +AHCI command lists and FIS buffers use native virtual addresses and full physical +addresses. Each command table fits within one physical page. PRDs translate every +4 KiB page, coalesce contiguous physical runs and retain upper address bits. +Controllers lacking 64-bit DMA reject inaccessible structures and buffers rather +than truncating addresses. Unsupported 32-bit-only controllers do not yet have +bounce-buffer support. The descriptor count remains bounded at 32. + +PCI driver callbacks receive persistent device records rather than stack locals. +Multifunction probing visits functions 1 through 7. IRQ/timer callbacks use the +existing i386 interrupt infrastructure or the x86-64 PIC dispatch implementation. +MMIO currently uses the low 4 GiB mapping; this is not general high-BAR support. + +VFS, filesystem buffers, volume slots and controller references preserve native +pointer widths. EXT2's worker starts before blocking filesystem operations, with +scheduler interrupts enabled. Filesystem registration follows the i386 probe +order so stale FAT signatures do not take precedence over EXT2 identification. +FAT32 now falls back to the space required by a write when optional preallocation +cannot fit, instead of incorrectly returning disk-full. + +VirtualBox validation at 64 MiB and 6 GiB passes: + +- 130 shared VFS checks and 34 AHCI checks. +- 123 storage metadata/filesystem checks, including heap-backed FAT32 and EXT2 + format, file creation, cross-block write, close/reopen, unaligned read-back, + persisted size and volume cleanup. No attached disk is written by these tests. +- Actual ISO9660, FAT32 and EXT2 mounts through the AHCI controller. +- A 4096-byte VFS read of the ISO's ELF64 kernel with header validation. +- At 6 GiB, sector reads into physical RAM above 4 GiB compared byte-for-byte + against ordinary-buffer reads from every attached storage device. + +The boot harness requires the new failure counters and content-read marker; a +high-memory run must also complete the high-DMA comparison. Evidence is saved in +`bin/x86_64/boot-none-64.log` and `bin/x86_64/boot-none-6144.log`. + +This completes the tested AHCI/filesystem path, not the entire OS port. The +x86-64 image now continues into the desktop and shared application startup. Remaining USB controllers still require porting and validation. Disk LBAs +and file sizes retain the existing storage API's 32-bit limits. + +Final i386 regression also passes: the complete image reaches idle with 123 +metadata/filesystem checks, 130 VFS checks, 34 AHCI checks and the existing USB, +controller, storage and core suites reporting zero failures. Early-boot tests +use private filesystem descriptors with synchronous transfer hooks; scheduled +x86-64 tests keep the normal async hooks and exercise the EXT2 worker. The final +6 GiB x86-64 image passes the same 123 checks and the high-DMA comparison. +VirtualBox is powered off with its original 2048 MiB RAM and Asuro.iso restored. + +## USB core and OHCI bring-up + +The shared USB types, descriptor parsing, controller/device registry and hub +logic now use native pointers and platform adapters. Aligned allocations store +a native-width allocation header and reject invalid alignment and size overflow. +Device-list slots retain full pointers, empty-list loops are bounded, and failed +controller registration rolls back its allocation. Tests isolate registry state +under IRQ exclusion and verify that their allocations are returned. + +OHCI runs in the x86-64 VirtualBox image. Its hardware ED/TD prefix stays 16 bytes; +software links and cleanup pointers use native widths. Controller MMIO and IRQ +routing use the existing platform adapters. DMA buffers and descriptor pages +must fit below 4 GiB; inaccessible buffers fail submission without truncation. +There is no OHCI high-memory bounce-buffer support yet. Schedule allocation must +succeed before the controller enters the operational state. + +A 6 GiB VirtualBox boot starts OHCI, resets its port and enumerates the configured +USB device (VID 80EE, PID 0030). At that stage, USB class-driver input/storage, UHCI, EHCI and xHCI +were not part of the long-mode boot. That established real control transfers, +not full USB feature parity. The boot harness requires successful enumeration as +well as the USB type/core/hub/OHCI test failure counters. + +Validation after the OHCI changes: both target builds and VirtualBox boots pass. +The x86-64 runs at 64 MiB and 6 GiB report 55 USB type checks, 55 core checks, +44 hub checks and 91 OHCI checks with zero failures, plus real device enumeration. +The i386 boot reaches idle with the existing storage, USB/HID and core suites +passing. Original VM RAM and optical media are restored and the VM is off. + +## USB class drivers and stable storage registration + +The shared mass-storage, boot keyboard and boot mouse drivers now compile for +both targets through the platform adapters. The mouse receives bounds from the +video subsystem; i386 startup supplies the optional PS/2 keyboard-disable hook. +USB class registration stays in architecture startup composition. + +Boot work after scheduler initialization runs in a normal process. Running it on +the idle context starved initialization once the persistent USB worker started. +Storage device records now have stable heap addresses behind native pointer +slots: registering a fifth disk previously moved the by-value registry and left +mounted volumes pointing at freed records. Registry publication and lookup exclude +interrupts. ISO9660 now stops on failed sector reads instead of parsing empty data. + +VirtualBox checks pass at 64 MiB and 6 GiB: 40 mass-storage protocol tests, 62 +keyboard tests and 33 mouse tests, plus the existing USB/core/OHCI and storage +suites. The existing USB disk serves a read-only sector-zero request through OHCI, +and the AHCI kernel-file read passes before and after USB registration. The 6 GiB +run also compares high-physical-memory reads against normal buffers. No USB writes +are used. The full i386 image builds and reaches idle with its suites passing. + +Temporarily enabling VirtualBox USB keyboard and mouse binds both class drivers +alongside the disk. This validates attachment, not injected input: boot-protocol +requests returned failure and the existing drivers continued. Report delivery, +hot unplug, USB writes and UHCI/EHCI/xHCI remain unvalidated in long mode. The image now continues into the graphical desktop. +## Native desktop, networking and applications + +The normal image runs the shared LVGL desktop and application manager. Both +architectures use the same application registrations; architecture startup retains +CPU/BIOS-specific commands. PS/2 mouse and keyboard drive the terminal, including +paced and burst input. Live BGA switching to 1280x800 and subsequent terminal input +pass in VirtualBox. The LVGL lock hands ownership to queued waiters, and framebuffer +flush stays inside that lock so mode changes cannot release a buffer during use. + +E1000 uses native MMIO mappings, platform IRQ routing and owned DMA buffers. +VirtualBox NAT tests obtained a DHCP lease and received ping replies at 6 GiB; +DHCP also passed at 64 MiB. This does not establish complete TCP protocol coverage. + +The WASM bridge resolves its context from the current process, initializes a +suspended process before publishing it, and releases partially loaded contexts on +failure. Native WASI proc_exit tests pass, and /disk/ram/hello prints Hello, World! +in the terminal. The external runtime's fixed VM stacks exceed the available RAM +in the 64 MiB configuration; launch is rejected before allocating those stacks. +MEMINFO uses platform physical-byte counters and reports RAM above 4 GiB. + +Both target builds pass. The i386 image reaches idle with its suites reporting +zero failures. Native boots at 64 MiB and 6 GiB reach the interactive desktop; +the high-memory run also passes AHCI DMA comparisons above 4 GiB. MD5SUM of abc +matches 900150983CD24FB0D6963F7D28E17F72. VM test settings are restored afterward. + +Full feature parity is still outstanding: UHCI/EHCI/xHCI, USB report delivery and +hot unplug, broader network workloads, and full interactive application coverage. +OHCI still rejects DMA addresses above its 32-bit hardware limit rather than +providing bounce buffers. These limits must not be described as a finished port. + +Follow-up interactive checks pass with VirtualBox USB keyboard and mouse: the +mouse opens Terminal and paced/burst keyboard commands produce the expected +output. SET_PROTOCOL warnings remain, but report delivery is now demonstrated. +Files, Notepad and Disk Utility also open successfully; Files lists root entries, +and Disk Utility lists AHCI and USB devices. This checks startup and display, +not destructive disk operations or every editor/file-management workflow. + +The application check exposed a duplicate core.strings.helpers unit shadowing +the native implementation. Removing the stale copy fixes the Files sorting fault +without changing compiler search rules. Low-memory WASM launch now displays the +handler's memory error, and a subsequent terminal command succeeds. diff --git a/doc/src/memory/memory.heap.md b/doc/src/memory/memory.heap.md index 95cda1f6..0bf8c1ee 100644 --- a/doc/src/memory/memory.heap.md +++ b/doc/src/memory/memory.heap.md @@ -19,7 +19,7 @@ Every allocation is preceded by a 16-byte prefix: 4 bytes for the unit count, 12 Allocations larger than a single heap page's capacity are handled by `klalloc`, which maps contiguous virtual pages and stores a magic sentinel (`$FFFFFFFE`) in the header so `kfree` can identify them and call `klfree` instead of the normal bitmap path. -`kalloc` and `kfree` disable interrupts for the duration of their critical sections using a stack-balanced `pushfd`/`cli`/`pop` sequence and restore the interrupt flag via `restore_if` on all exit paths. +`kalloc` and `kfree` disable interrupts for the duration of their critical sections using a stack-balanced `pushfd`/`cli`/`pop` sequence and restore the interrupt flag via `arch.platform.cpu.unlock` on all exit paths. ## Dependencies @@ -132,7 +132,7 @@ Returns the number of heap pages currently in the linked list. ## Notes -The `restore_if` helper checks bit 9 (IF) of the saved EFLAGS word and issues `STI` only if interrupts were enabled on entry. It uses `STI` rather than `POPFD` to avoid restoring potentially dangerous EFLAGS bits (IOPL, NT, VM, AC) that could trigger a General Protection Fault or Bad-TSS on the next `IRET`. +The `arch.platform.cpu.unlock` helper checks bit 9 (IF) of the saved EFLAGS word and issues `STI` only if interrupts were enabled on entry. It uses `STI` rather than `POPFD` to avoid restoring potentially dangerous EFLAGS bits (IOPL, NT, VM, AC) that could trigger a General Protection Fault or Bad-TSS on the next `IRET`. When `DEBUG_LMM` is defined, `kalloc` allocates one additional unit and writes `GUARD_MAGIC` at the end of the allocation. `kfree` verifies this sentinel before freeing; a mismatch triggers a GPF with a syslog error, catching buffer overruns at free time. diff --git a/docker-compose.yml b/docker-compose.yml index 845189bd..3524a579 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,8 @@ services: builder: build: . + environment: + ASURO_ARCH: ${ASURO_ARCH:-x86} + ASURO_BOOT_TEST: ${ASURO_BOOT_TEST:-none} volumes: - - .:/code \ No newline at end of file + - .:/code diff --git a/mkdocs.yml b/mkdocs.yml index cba7f458..4c920e6f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -295,6 +295,7 @@ nav: - Planning: - Architecture: planning/architecture.md + - x86-64 Migration: planning/x86_64.md - USB: planning/USB.md - XHCI: planning/XHCI.md - FlatFS: planning/flatfs.md diff --git a/src/app/app.base64.pas b/src/app/app.base64.pas index 17fa8419..8e396e6a 100644 --- a/src/app/app.base64.pas +++ b/src/app/app.base64.pas @@ -22,7 +22,7 @@ unit app.base64; interface uses - io.stdio, core.util, arch.x86.util, core.strings, debug.tracer, core.enc.base64, memory.heap; + io.stdio, core.util, arch.platform.cpu, core.strings, debug.tracer, core.enc.base64, memory.heap; procedure init(); diff --git a/src/app/app.dhclient.pas b/src/app/app.dhclient.pas index e7d59bc9..03972429 100644 --- a/src/app/app.dhclient.pas +++ b/src/app/app.dhclient.pas @@ -22,7 +22,7 @@ unit app.dhclient; interface uses - io.stdio, core.util, arch.x86.util, core.strings, debug.tracer, driver.net.proto.dhcp; + io.stdio, core.util, arch.platform.cpu, core.strings, debug.tracer, driver.net.proto.dhcp; procedure init(); diff --git a/src/app/app.diskcmd.pas b/src/app/app.diskcmd.pas index ec73b5b4..bb50d8cf 100644 --- a/src/app/app.diskcmd.pas +++ b/src/app/app.diskcmd.pas @@ -25,7 +25,7 @@ uses core.strings, io.stdio, debug.tracer, - core.util, arch.x86.util, + core.util, arch.platform.cpu, driver.storage.vol.mgr; var @@ -254,7 +254,7 @@ begin { Allocate a zero buffer - 64 sectors at a time } bufSects := 64; buf := puint32(kalloc(bufSects * device^.sectorSize)); - memset(uint32(buf), 0, bufSects * device^.sectorSize); + memset(PtrUInt(buf), 0, bufSects * device^.sectorSize); print('Wiping device '); printint(idx); diff --git a/src/app/app.diskutil.pas b/src/app/app.diskutil.pas index df6d4202..fcbbc3fb 100644 --- a/src/app/app.diskutil.pas +++ b/src/app/app.diskutil.pas @@ -34,7 +34,7 @@ uses driver.video, driver.storage.vol.mgr, driver.video.windows, - core.util, arch.x86.util; + core.util, arch.platform.cpu; const WIN_W = 740; @@ -129,9 +129,9 @@ begin item := PUIText(kalloc(sizeof(TUIText)+n+m+1)); if item = nil then exit; item^.Next := TextTemps; TextTemps := item; - dest := pchar(uint32(item)+sizeof(TUIText)); - if n > 0 then memcpy(uint32(a), uint32(dest), n); - if m > 0 then memcpy(uint32(b), uint32(dest)+n, m); + dest := pchar(PtrUInt(item)+sizeof(TUIText)); + if n > 0 then memcpy(PtrUInt(a), PtrUInt(dest), n); + if m > 0 then memcpy(PtrUInt(b), PtrUInt(dest)+n, m); dest[n+m] := #0; uiConcat := dest; end; @@ -606,7 +606,7 @@ begin end; end; - memset(uint32(@part), 0, sizeof(TPartition_table)); + memset(PtrUInt(@part), 0, sizeof(TPartition_table)); driver.storage.vol.mbr.setup_partition(@part, lba, sectors); cbCtx := begin_partition_ui(1); if cbCtx = nil then exit; @@ -702,18 +702,19 @@ end; procedure operation_done(error : TError; userdata : pointer); var ctx : PFmtUICallbackCtx; - flags : uint32; + flags : PtrUInt; begin ctx := PFmtUICallbackCtx(userdata); if ctx <> nil then begin - asm pushfd; cli; pop flags end; + flags := arch.platform.cpu.lock; if ctx^.SessionID = ui_session_id then begin fmt_done_error := error; fmt_done_vol_idx := ctx^.VolumeIdx; - done_kind := ctx^.Kind; done_device := ctx^.DeviceIdx; + done_kind := ctx^.Kind; + done_device := ctx^.DeviceIdx; puint32(@fmt_done_flag)^ := 1; end; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(flags); kfree(void(ctx)); end; end; @@ -824,7 +825,7 @@ begin fs := driver.storage.fs.mgr.get_filesystem(selIdx); if fs = nil then begin closeMsgBox; exit; end; - memset(uint32(@params), 0, sizeof(TFSFormatParams)); + memset(PtrUInt(@params), 0, sizeof(TFSFormatParams)); params.Version := FS_FORMAT_PARAMS_VERSION_1; paramPtr := nil; @@ -874,7 +875,7 @@ begin if fs^.validateLabelCallback(text,normalized) <> eNone then begin set_fmt_param_error('Use up to 11 ASCII characters without punctuation such as / or :'); exit; end; - memcpy(uint32(@normalized[0]),uint32(@params.LabelText[0]),12); + memcpy(PtrUInt(@normalized[0]),PtrUInt(@params.LabelText[0]),12); params.Flags := params.Flags or FS_FORMAT_PARAM_LABEL; paramPtr := @params; end; diff --git a/src/app/app.filebrowser.pas b/src/app/app.filebrowser.pas index 32a01157..3c0cdfe4 100644 --- a/src/app/app.filebrowser.pas +++ b/src/app/app.filebrowser.pas @@ -32,7 +32,7 @@ uses core.gfx.fileicons, debug.tracer, core.util, - arch.x86.util; + arch.platform.cpu; const WIN_W = 800; @@ -785,9 +785,9 @@ begin dir_names := PNameBuf(kalloc(sizeof(TNameBuf))); file_names := PNameBuf(kalloc(sizeof(TNameBuf))); file_sizes := PSizeBuf(kalloc(sizeof(TSizeBuf))); - memset(uint32(dir_names), 0, sizeof(TNameBuf)); - memset(uint32(file_names), 0, sizeof(TNameBuf)); - memset(uint32(file_sizes), 0, sizeof(TSizeBuf)); + memset(PtrUInt(dir_names), 0, sizeof(TNameBuf)); + memset(PtrUInt(file_names), 0, sizeof(TNameBuf)); + memset(PtrUInt(file_sizes), 0, sizeof(TSizeBuf)); { Load directory from VFS — this may do disk I/O } Map := driver.storage.vfs.GetDirectoryListingFrom(state^.cur_path, '/'); @@ -1330,7 +1330,7 @@ begin parent := stringCopy('/') else begin parent := pchar(kalloc(uint32(i) + 1)); - memcpy(uint32(cur), uint32(parent), uint32(i)); + memcpy(PtrUInt(cur), PtrUInt(parent), uint32(i)); parent[i] := char(0); end; navigate_to(state, parent); @@ -1440,7 +1440,7 @@ begin { Segment name label } segBuf := pchar(kalloc(segEnd - segStart + 1)); - memcpy(uint32(@path[segStart]), uint32(segBuf), segEnd - segStart); + memcpy(PtrUInt(@path[segStart]), uint32(segBuf), segEnd - segStart); segBuf[segEnd - segStart] := char(0); lbl := lv_label_create(btn); lv_label_set_text(lbl, segBuf); @@ -1979,7 +1979,7 @@ begin if state^.tabs[idx] <> nil then free_tab_snapshot(state^.tabs[idx]); snap := PTabSnapshot(kalloc(sizeof(TTabSnapshot))); - memset(uint32(snap), 0, sizeof(TTabSnapshot)); + memset(PtrUInt(snap), 0, sizeof(TTabSnapshot)); if state^.cur_path <> nil then snap^.cur_path := stringCopy(state^.cur_path) else @@ -2090,7 +2090,7 @@ begin save_tab(state, state^.active_tab); { Create snapshot for new tab } snap := PTabSnapshot(kalloc(sizeof(TTabSnapshot))); - memset(uint32(snap), 0, sizeof(TTabSnapshot)); + memset(PtrUInt(snap), 0, sizeof(TTabSnapshot)); snap^.cur_path := stringCopy(path); snap^.hist_len := 0; snap^.hist_pos := 0; @@ -3053,7 +3053,7 @@ begin { Allocate and zero-initialise state } state := PFileBrowserState(kalloc(sizeof(TFileBrowserState))); - memset(uint32(state), 0, sizeof(TFileBrowserState)); + memset(PtrUInt(state), 0, sizeof(TFileBrowserState)); state^.win_id := g_win_id; state^.cur_path := stringCopy('/'); state^.view_mode := vmDetail; diff --git a/src/app/app.filepicker.pas b/src/app/app.filepicker.pas index 40f7dd7c..be242f60 100644 --- a/src/app/app.filepicker.pas +++ b/src/app/app.filepicker.pas @@ -64,7 +64,7 @@ uses core.strings.helpers, io.syslog, debug.tracer, - core.util, arch.x86.util, + core.util, arch.platform.cpu, driver.storage.vfs; const @@ -325,9 +325,9 @@ begin dir_names := PNameBuf(kalloc(sizeof(TNameBuf))); file_names := PNameBuf(kalloc(sizeof(TNameBuf))); file_sizes := PSizeBuf(kalloc(sizeof(TSizeBuf))); - memset(uint32(dir_names), 0, sizeof(TNameBuf)); - memset(uint32(file_names), 0, sizeof(TNameBuf)); - memset(uint32(file_sizes), 0, sizeof(TSizeBuf)); + memset(PtrUInt(dir_names), 0, sizeof(TNameBuf)); + memset(PtrUInt(file_names), 0, sizeof(TNameBuf)); + memset(PtrUInt(file_sizes), 0, sizeof(TSizeBuf)); { Sync path bar } if p^.path_bar <> nil then @@ -898,7 +898,7 @@ begin io.syslog.logln('FPCIK', 'make_picker: enter'); p := PPicker(kalloc(sizeof(TPicker))); - memset(uint32(p), 0, sizeof(TPicker)); + memset(PtrUInt(p), 0, sizeof(TPicker)); p^.cb := cb; p^.userdata := userdata; p^.is_save := is_save; diff --git a/src/app/app.md5sum.pas b/src/app/app.md5sum.pas index a16950bc..39879c91 100644 --- a/src/app/app.md5sum.pas +++ b/src/app/app.md5sum.pas @@ -22,7 +22,7 @@ unit app.md5sum; interface uses - io.stdio, core.util, arch.x86.util, core.strings, debug.tracer, core.enc.md5; + memory.heap, io.stdio, core.util, arch.platform.cpu, core.strings, debug.tracer, core.enc.md5; procedure init(); @@ -36,6 +36,10 @@ var i : uint32; begin + if paramCount(Params) = 0 then begin + io.stdio.bufWriteStrLn(stderr_buf, 'Usage: MD5SUM '); + exit; + end; md5word:= getParam(0, Params); wordlen:= stringSize(md5word); MD5_Hash := MD5Buffer(puint8(md5word), wordlen); @@ -43,6 +47,7 @@ begin io.stdio.bufWriteHexPair(stdout_buf, MD5_Hash^[i]); end; io.stdio.bufWriteStrLn(stdout_buf, ' '); + memory.heap.kfree(void(MD5_Hash)); end; procedure init(); @@ -51,4 +56,4 @@ begin io.stdio.registerCommand('MD5SUM', @Run, 'Perform MD5SUM on a word.'); end; -end. \ No newline at end of file +end. diff --git a/src/app/app.meminfo.pas b/src/app/app.meminfo.pas index c747a820..7c11ab50 100644 --- a/src/app/app.meminfo.pas +++ b/src/app/app.meminfo.pas @@ -22,7 +22,7 @@ unit app.meminfo; interface uses - io.stdio, arch.x86.multiboot, arch.x86.memory.physical, memory.heap, debug.tracer; + io.stdio, arch.platform.memory, memory.heap, debug.tracer; procedure init(); @@ -30,50 +30,30 @@ implementation procedure run(Params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); var - pmm_total, pmm_free, pmm_used : uint32; - lmm_pages, lmm_free : uint32; + total, available: uint64; + heapPages: uint32; begin - { Multiboot memory info } - io.stdio.bufWriteStrLn(stdout_buf, '--- Multiboot ---'); - io.stdio.bufWriteStr(stdout_buf, 'Lower Memory : '); - io.stdio.bufWriteInt(stdout_buf, multibootinfo^.mem_lower); - io.stdio.bufWriteStrLn(stdout_buf, ' KB'); - io.stdio.bufWriteStr(stdout_buf, 'Upper Memory : '); - io.stdio.bufWriteInt(stdout_buf, multibootinfo^.mem_upper); - io.stdio.bufWriteStrLn(stdout_buf, ' KB'); - io.stdio.bufWriteStr(stdout_buf, 'Total Memory : '); - io.stdio.bufWriteInt(stdout_buf, ((multibootinfo^.mem_upper + 1000) div 1024) + 1); - io.stdio.bufWriteStrLn(stdout_buf, ' MB'); - - { PMM stats } - io.stdio.bufWriteStrLn(stdout_buf, '--- PMM (4 MB blocks) ---'); - pmm_total := arch.x86.memory.physical.pmm_total_blocks; - pmm_free := arch.x86.memory.physical.pmm_free_blocks; - pmm_used := pmm_total - pmm_free; - io.stdio.bufWriteStr(stdout_buf, 'Total Blocks : '); - io.stdio.bufWriteIntLn(stdout_buf, pmm_total); - io.stdio.bufWriteStr(stdout_buf, 'Free Blocks : '); - io.stdio.bufWriteIntLn(stdout_buf, pmm_free); - io.stdio.bufWriteStr(stdout_buf, 'Used Blocks : '); - io.stdio.bufWriteIntLn(stdout_buf, pmm_used); - io.stdio.bufWriteStr(stdout_buf, 'Free Physical : '); - io.stdio.bufWriteInt(stdout_buf, pmm_free * 4); - io.stdio.bufWriteStrLn(stdout_buf, ' MB'); - - { LMM heap stats } - io.stdio.bufWriteStrLn(stdout_buf, '--- LMM (Heap) ---'); - lmm_pages := memory.heap.lmm_page_count; - lmm_free := memory.heap.lmm_total_free; - io.stdio.bufWriteStr(stdout_buf, 'Heap Pages : '); - io.stdio.bufWriteIntLn(stdout_buf, lmm_pages); - io.stdio.bufWriteStr(stdout_buf, 'Heap Size : '); - io.stdio.bufWriteInt(stdout_buf, lmm_pages * 4); - io.stdio.bufWriteStrLn(stdout_buf, ' MB'); - io.stdio.bufWriteStr(stdout_buf, 'Heap Free : '); - io.stdio.bufWriteInt(stdout_buf, lmm_free div 1024); - io.stdio.bufWriteStrLn(stdout_buf, ' KB'); + total := arch.platform.memory.totalPhysicalBytes; + available := arch.platform.memory.freePhysicalBytes; + io.stdio.bufWriteStr(stdout_buf, 'Usable Physical: '); + io.stdio.bufWriteInt(stdout_buf, total div 1024); + io.stdio.bufWriteStrLn(stdout_buf, ' KiB'); + io.stdio.bufWriteStr(stdout_buf, 'Free Physical : '); + io.stdio.bufWriteInt(stdout_buf, available div 1024); + io.stdio.bufWriteStrLn(stdout_buf, ' KiB'); + io.stdio.bufWriteStr(stdout_buf, 'Used Physical : '); + io.stdio.bufWriteInt(stdout_buf, (total - available) div 1024); + io.stdio.bufWriteStrLn(stdout_buf, ' KiB'); + heapPages := memory.heap.lmm_page_count; + io.stdio.bufWriteStr(stdout_buf, 'Heap Regions : '); + io.stdio.bufWriteIntLn(stdout_buf, heapPages); + io.stdio.bufWriteStr(stdout_buf, 'Heap Size : '); + io.stdio.bufWriteInt(stdout_buf, uint64(heapPages) * PAGE_SIZE_LMM div 1024); + io.stdio.bufWriteStrLn(stdout_buf, ' KiB'); + io.stdio.bufWriteStr(stdout_buf, 'Heap Free : '); + io.stdio.bufWriteInt(stdout_buf, memory.heap.lmm_total_free div 1024); + io.stdio.bufWriteStrLn(stdout_buf, ' KiB'); end; - procedure init(); begin debug.tracer.push_trace('meminfo.init'); diff --git a/src/app/app.mgr.pas b/src/app/app.mgr.pas index 348dbdd6..fa78125c 100644 --- a/src/app/app.mgr.pas +++ b/src/app/app.mgr.pas @@ -23,10 +23,9 @@ unit app.mgr; interface uses - boot.mgr, debug.tracer, io.stdio, proc.mgr, //progs - app.base64, app.md5sum, app.dhclient, app.vbeinfo, app.testcmd, app.ping, app.meminfo, app.setres, app.iotest, + app.base64, app.md5sum, app.dhclient, app.testcmd, app.ping, app.meminfo, app.setres, //drivers driver.storage.ctl.ram, driver.mgr, //network @@ -35,8 +34,6 @@ uses driver.storage.filedispatch, //wasm app.wasm.runner, - app.divzero, - app.bsod, app.vterminal; { Initialize all baked-in programs } @@ -47,7 +44,6 @@ implementation uses core.version, //command provider units - arch.x86.cpu, app.diskcmd, driver.bus.usb.core, app.diskutil, app.notepad, app.partcmd, app.volcmd, app.filebrowser; @@ -56,16 +52,10 @@ begin debug.tracer.push_trace('app.mgr.init'); { Register commands from provider units } - io.stdio.registerCommand('CPU', @arch.x86.cpu.Terminal_Command_CPU, 'CPU Info.'); io.stdio.registerCommand('DEV', @driver.mgr.terminal_command_dev, 'Driver Management Interface.'); io.stdio.registerCommand('PS', @proc.mgr.terminal_command_ps, 'List running processes.'); io.stdio.registerCommand('KILL', @proc.mgr.terminal_command_kill, 'Force-kill a process by PID.'); io.stdio.registerCommand('TERMINATE', @proc.mgr.terminal_command_terminate, 'Gracefully terminate a process by PID.'); - io.stdio.registerCommand('ARP', @driver.net.proto.arp.terminal_command_arp, 'Get ARP Table.'); - io.stdio.registerCommand('IFCONFIG', @driver.net.proto.ipv4.terminal_command_ifconfig, 'Configure Network Settings.'); - io.stdio.registerCommand('TCPCONNECT', @driver.net.proto.tcp.terminal_command_tcpconnect, 'Connect to a TCP host and send Hello World.'); - io.stdio.registerCommand('TCPLISTEN', @driver.net.proto.tcp.terminal_command_tcplisten, 'Listen on a TCP port and log received data.'); - io.stdio.registerCommand('TCPHTTP', @driver.net.proto.tcp.terminal_command_tcphttp, 'Send HTTP GET to a host IP (port 80 default).'); io.stdio.registerCommand('USB', @driver.bus.usb.core.terminal_command_usb, 'driver.bus.usb subsystem information.'); { File dispatch — must init before apps that register handlers } @@ -81,21 +71,15 @@ begin app.md5sum.init(); app.base64.init(); app.dhclient.init(); - app.vbeinfo.init(); app.testcmd.init(); app.ping.init(); app.meminfo.init(); { WASM & remaining apps } app.wasm.runner.init(); app.setres.init(); - app.divzero.init(); - app.bsod.init(); app.vterminal.init; app.filebrowser.init(); - app.iotest.init(); end; -Initialization - boot.mgr.registerBoot('app.mgr', @init, 'Program Manager', BOOT_MGR_BARRIER_LATE); end. \ No newline at end of file diff --git a/src/app/app.notepad.pas b/src/app/app.notepad.pas index 0a524c56..455a0462 100644 --- a/src/app/app.notepad.pas +++ b/src/app/app.notepad.pas @@ -11,8 +11,7 @@ - Status bar showing Line, Col, filename, and dirty flag Key design notes: - - lv_event_t.stop_processing (bit 1 at struct byte offset 24) is set - directly to prevent LVGL's default textarea key handler from also + - lv_event_stop_processing prevents LVGL's default textarea handler from also acting on keys we have fully handled ourselves. - Shift state is read from driver.hid.keyboard.is_shift / driver.hid.keyboard.is_ctrl. - selection anchor (sel_anchor) is stored as a sint32 byte offset; @@ -43,7 +42,7 @@ uses io.syslog, io.stdio, debug.tracer, - core.util, arch.x86.util, + core.util, arch.platform.cpu, driver.storage.vfs, driver.video, driver.video.windows; @@ -62,19 +61,6 @@ const FILE_BUF_SIZE = 65536; { 64 KB read / write buffer } - { - Byte offset and bitmask for lv_event_t.stop_processing. - struct lv_event_t (32-bit ABI): - offset 0: current_target (ptr) - offset 4: original_target (ptr) - offset 8: code (uint32 — lv_event_code_t) - offset 12: user_data (ptr) - offset 16: param (ptr) - offset 20: prev (ptr) - offset 24: flags byte [bit0=deleted, bit1=stop_processing, bit2=stop_bubbling] - } - LV_EVT_STOP_OFF = 24; - LV_EVT_STOP_BIT = 2; { bit 1 = stop_processing } type PNotepadState = ^TNotepadState; @@ -130,8 +116,7 @@ procedure removeInstance(state: PNotepadState); forward; ============================================================ } procedure stopEvent(e: Plv_event); begin - puint8(uint32(e) + LV_EVT_STOP_OFF)^ := - puint8(uint32(e) + LV_EVT_STOP_OFF)^ or LV_EVT_STOP_BIT; + lv_event_stop_processing(e); end; { ============================================================ @@ -157,18 +142,18 @@ begin if to_idx < from_idx then to_idx := from_idx; result_len := from_idx + ins_len + (src_len - to_idx); buf := pchar(kalloc(result_len + 1)); - memset(uint32(buf), 0, result_len + 1); + memset(PtrUInt(buf), 0, result_len + 1); pos := 0; if from_idx > 0 then begin - memcpy(uint32(src), uint32(buf), from_idx); + memcpy(PtrUInt(src), PtrUInt(buf), from_idx); pos := from_idx; end; if ins_len > 0 then begin - memcpy(uint32(insert), uint32(@buf[pos]), ins_len); + memcpy(PtrUInt(insert), PtrUInt(@buf[pos]), ins_len); pos := pos + ins_len; end; if to_idx < src_len then - memcpy(uint32(@src[to_idx]), uint32(@buf[pos]), src_len - to_idx); + memcpy(PtrUInt(@src[to_idx]), PtrUInt(@buf[pos]), src_len - to_idx); buildSplicedString := buf; end; @@ -198,12 +183,12 @@ begin { Generous output buffer: original + 4 bytes per line (worst case ~512 lines) } out_buf := pchar(kalloc(src_len + 2049)); - memset(uint32(out_buf), 0, src_len + 2049); + memset(PtrUInt(out_buf), 0, src_len + 2049); out_pos := 0; { Copy everything before the affected region verbatim } if first_ls > 0 then begin - memcpy(uint32(src), uint32(out_buf), first_ls); + memcpy(PtrUInt(src), PtrUInt(out_buf), first_ls); out_pos := first_ls; end; @@ -215,7 +200,7 @@ begin if i >= sel_end then begin { Past the affected region: copy the remainder verbatim } if i < src_len then begin - memcpy(uint32(@src[i]), uint32(@out_buf[out_pos]), src_len - i); + memcpy(PtrUInt(@src[i]), PtrUInt(@out_buf[out_pos]), src_len - i); out_pos := out_pos + (src_len - i); end; break; @@ -426,8 +411,8 @@ begin len := stringSize(txt); if len > FILE_BUF_SIZE - 1 then len := FILE_BUF_SIZE - 1; buf := pchar(kalloc(FILE_BUF_SIZE)); - memset(uint32(buf), 0, FILE_BUF_SIZE); - memcpy(uint32(txt), uint32(buf), len); + memset(PtrUInt(buf), 0, FILE_BUF_SIZE); + memcpy(PtrUInt(txt), PtrUInt(buf), len); fHandle := driver.storage.vfs.OpenFile(state^.currentPath, omReadWrite, @fError); if fHandle = 0 then fHandle := driver.storage.vfs.OpenFile(state^.currentPath, omCreate, @fError); @@ -477,7 +462,7 @@ begin exit; end; buf := pchar(kalloc(FILE_BUF_SIZE)); - memset(uint32(buf), 0, FILE_BUF_SIZE); + memset(PtrUInt(buf), 0, FILE_BUF_SIZE); driver.storage.vfs.ReadFile(fHandle, 0, puint8(buf), FILE_BUF_SIZE - 1); driver.storage.vfs.CloseFile(fHandle); if state^.currentPath <> nil then kfree(void(state^.currentPath)); @@ -943,7 +928,7 @@ begin { Allocate and zero-initialise state } state := PNotepadState(kalloc(sizeof(TNotepadState))); - memset(uint32(state), 0, sizeof(TNotepadState)); + memset(PtrUInt(state), 0, sizeof(TNotepadState)); state^.win_id := wid; state^.sel_anchor := -1; addInstance(state); diff --git a/src/app/app.ping.pas b/src/app/app.ping.pas index 76d72624..ae841cd4 100644 --- a/src/app/app.ping.pas +++ b/src/app/app.ping.pas @@ -20,8 +20,8 @@ procedure init(); implementation uses - arch.x86.bda, driver.net.types, driver.net.proto.icmp, driver.net.util, core.strings, - proc.mgr, core.util, arch.x86.util, memory.heap; + proc.types, driver.net.types, driver.net.proto.icmp, driver.net.util, core.strings, + proc.mgr, core.util, arch.platform.cpu, memory.heap; const PING_TIMEOUT_MS = 5000; { 5-second timeout per app.ping } @@ -31,22 +31,34 @@ type PPingState = ^TPingState; TPingState = record Result : TPingResult; - ReplyTimeMS : uint64; + ReplyTimeMS : uint32; ErrorReason : TARPErrorCode; - SendTime : uint64; + SendTime : uint32; + Address : puint8; end; +procedure cleanup(handle: void); +var + st: PPingState; +begin + st := PPingState(handle); + if st = nil then exit; + driver.net.proto.icmp.cancelRequests(handle); + kfree(void(st^.Address)); + kfree(handle); +end; + { ---- ICMP callbacks (called from network recv context) ---- } procedure on_reply(hdr : PICMPHeader; userData : void); var st : PPingState; - t2 : uint64; + t2 : uint32; begin st := PPingState(userData); if st = nil then exit; - t2 := Counters.c64; - st^.ReplyTimeMS := t2 - st^.SendTime; + t2 := networkTicks; + st^.ReplyTimeMS := (uint64(uint32(t2 - st^.SendTime)) * 1000) div 1024; st^.Result := prGotReply; end; @@ -87,24 +99,31 @@ begin { Allocate per-invocation state so concurrent pings are safe } st := PPingState(kalloc(sizeof(TPingState))); + if st = nil then begin + kfree(void(ip)); + exit; + end; + st^.Address := ip; + proc.mgr.bindResource(proc.mgr.CurrentProcess, rkCustom, void(st), @cleanup); timeoutTicks := (PING_TIMEOUT_MS * 1024) div 1000; for i := 0 to 9 do begin { Send ICMP echo request } st^.Result := prWaiting; - st^.SendTime := Counters.c64; + st^.SendTime := networkTicks; driver.net.proto.icmp.sendICMPRequest(ip, i, 128, @on_reply, @on_error, void(st)); { Spin-yield until callback fires or timeout } - tStart := Counters.c32; + tStart := networkTicks; while st^.Result = prWaiting do begin proc.mgr.proc_yield; - tNow := Counters.c32; + tNow := networkTicks; if tNow >= tStart then elapsed := tNow - tStart else elapsed := ($FFFFFFFF - tStart) + tNow + 1; if elapsed >= timeoutTicks then begin + driver.net.proto.icmp.cancelRequests(void(st)); st^.Result := prGotError; st^.ErrorReason := aecTimeout; break; @@ -132,8 +151,7 @@ begin end; { Clean up } - kfree(void(st)); - kfree(void(ip)); + proc.mgr.unbindResource(proc.mgr.CurrentProcess, void(st)); end; procedure init(); @@ -142,4 +160,4 @@ begin io.stdio.registerCommand('PING', @run, 'Ping a host.'); end; -end. \ No newline at end of file +end. diff --git a/src/app/app.setres.pas b/src/app/app.setres.pas index 7b0af214..88878662 100644 --- a/src/app/app.setres.pas +++ b/src/app/app.setres.pas @@ -14,7 +14,7 @@ unit app.setres; interface uses - io.stdio, driver.video.gpu, core.util, arch.x86.util, core.strings, debug.tracer, io.syslog; + io.stdio, driver.video.lvgl, driver.video.gpu, core.util, arch.platform.cpu, core.strings, debug.tracer, io.syslog; procedure init(); @@ -25,6 +25,7 @@ var w, h : uint32; wStr, hStr : pchar; info : TGPUModeInfo; + changed: boolean; begin debug.tracer.push_trace('setres.run'); @@ -55,7 +56,10 @@ begin io.stdio.bufWriteStrLn(stdout_buf, 'x32...'); { Step 1: Set display mode via GPU framework (tries BGA first, then VBE) } - if not driver.video.gpu.setMode(w, h, 32, info) then begin + driver.video.lvgl.lvgl_lock; + changed := driver.video.gpu.setMode(w, h, 32, info); + driver.video.lvgl.lvgl_unlock; + if not changed then begin io.stdio.bufWriteStrLn(stderr_buf, 'Error: Mode switch failed. Mode may not be supported.'); debug.tracer.pop_trace; exit; diff --git a/src/app/app.uidebug.pas b/src/app/app.uidebug.pas index 04293c24..61834154 100644 --- a/src/app/app.uidebug.pas +++ b/src/app/app.uidebug.pas @@ -19,7 +19,7 @@ unit app.uidebug; interface uses - driver.video.lvgl, driver.video.windows, driver.hid.mouse, driver.video, arch.x86.multiboot, debug.tracer; + driver.video.lvgl, driver.video.windows, driver.hid.mouse, driver.video, boot.multiboot, debug.tracer; { Call once per frame from the main loop (before lvgl_handler). } procedure update; diff --git a/src/app/app.vterminal.pas b/src/app/app.vterminal.pas index 2a7da820..d7d1c3f9 100644 --- a/src/app/app.vterminal.pas +++ b/src/app/app.vterminal.pas @@ -17,7 +17,7 @@ interface uses driver.video.lvgl, driver.video, driver.video.windows, driver.video.desktop, driver.hid.keyboard, debug.tracer, - core.strings, core.util, arch.x86.util, memory.heap, core.version, io.stdio, driver.storage.vfs, + core.strings, core.util, arch.platform.cpu, memory.heap, core.version, io.stdio, driver.storage.vfs, proc.mgr, proc.types, core.ds.lists, core.ds.hashmap, driver.storage.filedispatch; procedure init; @@ -114,10 +114,10 @@ begin if slen = 0 then exit; if state^.text_len + slen >= MAX_TEXT - 1 then begin i := MAX_TEXT div 2; - memcpy(uint32(@state^.text_buf[i]), uint32(@state^.text_buf[0]), state^.text_len - i); + memcpy(PtrUInt(@state^.text_buf[i]), PtrUInt(@state^.text_buf[0]), state^.text_len - i); state^.text_len := state^.text_len - i; end; - memcpy(uint32(s), uint32(@state^.text_buf[state^.text_len]), slen); + memcpy(PtrUInt(s), PtrUInt(@state^.text_buf[state^.text_len]), slen); state^.text_len := state^.text_len + slen; state^.text_buf[state^.text_len] := #0; end; @@ -144,6 +144,17 @@ begin end; end; +procedure showLaunchError(state: PVTermState; errors: POutBuf); +begin + if (errors <> nil) and (errors^.len > 0) then begin + errors^.buf[errors^.len] := #0; + appendText(state, errors^.buf); + end else begin + appendText(state, 'Unknown command. Type HELP for a list.'); + appendChar(state, #10); + end; +end; + procedure showPrompt(state : PVTermState); begin appendText(state, 'asuro:'); @@ -223,7 +234,7 @@ end; procedure drainBackgroundJobs(state : PVTermState); var i : sint32; - slotPtr : ^uint32; + slotPtr : ^PtrUInt; job : PBackgroundJob; ctx : PProcessContext; finished: boolean; @@ -233,7 +244,7 @@ begin if state^.bg_jobs = nil then exit; dirty := false; for i := sint32(DL_Size(state^.bg_jobs)) - 1 downto 0 do begin - slotPtr := DL_Get(state^.bg_jobs, uint32(i)); + slotPtr := pointer(DL_Get(state^.bg_jobs, uint32(i))); if slotPtr = nil then continue; job := PBackgroundJob(slotPtr^); if job = nil then continue; @@ -312,7 +323,7 @@ begin showPrompt(state); { Clear any partial input line } state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); end; { ============================================================ @@ -444,7 +455,7 @@ var pcount : uint32; lastParam : pchar; job : PBackgroundJob; - slotPtr : ^uint32; + slotPtr : ^PtrUInt; absPath : pchar; dispatchPID : uint32; begin @@ -456,8 +467,8 @@ begin { Push non-empty lines into history } if state^.line_len > 0 then begin - memcpy(uint32(@state^.line_buf[0]), - uint32(@state^.hist[state^.hist_head][0]), + memcpy(PtrUInt(@state^.line_buf[0]), + PtrUInt(@state^.hist[state^.hist_head][0]), state^.line_len + 1); state^.hist_head := (state^.hist_head + 1) mod HIST_SIZE; if state^.hist_count < HIST_SIZE then inc(state^.hist_count); @@ -467,7 +478,7 @@ begin if state^.line_len = 0 then begin showPrompt(state); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); exit; end; @@ -484,7 +495,7 @@ begin kfree(void(uppera)); io.stdio.freeParams(params); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); showPrompt(state); exit; end; @@ -495,7 +506,7 @@ begin kfree(void(uppera)); io.stdio.freeParams(params); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); showPrompt(state); exit; end; @@ -504,7 +515,7 @@ begin kfree(void(uppera)); io.stdio.freeParams(params); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); showPrompt(state); exit; end; @@ -513,7 +524,7 @@ begin kfree(void(uppera)); io.stdio.freeParams(params); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); showPrompt(state); exit; end; @@ -522,7 +533,7 @@ begin kfree(void(uppera)); io.stdio.freeParams(params); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); showPrompt(state); exit; end; @@ -531,7 +542,7 @@ begin if stringEquals(uppera, 'JOBS') then begin if (state^.bg_jobs <> nil) and (DL_Size(state^.bg_jobs) > 0) then begin for pcount := 0 to DL_Size(state^.bg_jobs) - 1 do begin - slotPtr := DL_Get(state^.bg_jobs, pcount); + slotPtr := pointer(DL_Get(state^.bg_jobs, pcount)); if (slotPtr <> nil) then begin job := PBackgroundJob(slotPtr^); if job <> nil then begin @@ -564,7 +575,7 @@ begin kfree(void(uppera)); io.stdio.freeParams(params); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); showPrompt(state); exit; end; @@ -573,7 +584,7 @@ begin if stringEquals(uppera, 'FG') then begin if (state^.bg_jobs <> nil) and (DL_Size(state^.bg_jobs) > 0) then begin { Use first bg job if no arg given } - slotPtr := DL_Get(state^.bg_jobs, 0); + slotPtr := pointer(DL_Get(state^.bg_jobs, 0)); if (slotPtr <> nil) then begin job := PBackgroundJob(slotPtr^); if job <> nil then begin @@ -600,7 +611,7 @@ begin kfree(void(uppera)); io.stdio.freeParams(params); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); exit; end; @@ -621,7 +632,7 @@ begin { --- Background launch --- } job := PBackgroundJob(kalloc(SizeOf(TBackgroundJob))); if job <> nil then begin - memset(uint32(job), 0, SizeOf(TBackgroundJob)); + memset(PtrUInt(job), 0, SizeOf(TBackgroundJob)); job^.StdOut := io.stdio.createOutBuf(1024); job^.StdErr := io.stdio.createOutBuf(1024); job^.LastDrain := 0; @@ -632,7 +643,7 @@ begin { Copy command name } pcount := stringSize(params^.Param); if pcount > 31 then pcount := 31; - memcpy(uint32(params^.Param), uint32(@job^.CmdName[0]), pcount); + memcpy(PtrUInt(params^.Param), PtrUInt(@job^.CmdName[0]), pcount); job^.CmdName[pcount] := #0; { Launch but don't set ForegroundPID } @@ -644,9 +655,9 @@ begin if ctx <> nil then begin job^.PID := ctx^.ProcessID; if state^.bg_jobs = nil then - state^.bg_jobs := DL_New(SizeOf(uint32)); - slotPtr := DL_Add(state^.bg_jobs); - slotPtr^ := uint32(job); + state^.bg_jobs := DL_New(SizeOf(PtrUInt)); + slotPtr := pointer(DL_Add(state^.bg_jobs)); + slotPtr^ := PtrUInt(job); appendText(state, '['); lastParam := intToString(job^.JobNum); appendText(state, lastParam); @@ -702,7 +713,7 @@ begin { Background file dispatch } job := PBackgroundJob(kalloc(SizeOf(TBackgroundJob))); if job <> nil then begin - memset(uint32(job), 0, SizeOf(TBackgroundJob)); + memset(PtrUInt(job), 0, SizeOf(TBackgroundJob)); job^.StdOut := io.stdio.createOutBuf(1024); job^.StdErr := io.stdio.createOutBuf(1024); job^.LastDrain := 0; @@ -711,7 +722,7 @@ begin job^.JobNum := state^.bg_next_job; pcount := stringSize(params^.Param); if pcount > 31 then pcount := 31; - memcpy(uint32(params^.Param), uint32(@job^.CmdName[0]), pcount); + memcpy(PtrUInt(params^.Param), PtrUInt(@job^.CmdName[0]), pcount); job^.CmdName[pcount] := #0; dispatchPID := driver.storage.filedispatch.dispatch(absPath, params, @@ -719,9 +730,9 @@ begin if dispatchPID > 0 then begin job^.PID := dispatchPID; if state^.bg_jobs = nil then - state^.bg_jobs := DL_New(SizeOf(uint32)); - slotPtr := DL_Add(state^.bg_jobs); - slotPtr^ := uint32(job); + state^.bg_jobs := DL_New(SizeOf(PtrUInt)); + slotPtr := pointer(DL_Add(state^.bg_jobs)); + slotPtr^ := PtrUInt(job); appendText(state, '['); lastParam := intToString(job^.JobNum); appendText(state, lastParam); @@ -732,8 +743,7 @@ begin kfree(void(lastParam)); appendChar(state, #10); end else begin - appendText(state, 'Unknown command. Type HELP for a list.'); - appendChar(state, #10); + showLaunchError(state, job^.StdErr); io.stdio.freeOutBuf(job^.StdOut); io.stdio.freeOutBuf(job^.StdErr); kfree(void(job)); @@ -758,8 +768,7 @@ begin if dispatchPID > 0 then begin state^.ForegroundPID := dispatchPID; end else begin - appendText(state, 'Unknown command. Type HELP for a list.'); - appendChar(state, #10); + showLaunchError(state, state^.fg_stderr); io.stdio.freeOutBuf(state^.fg_stdout); state^.fg_stdout := nil; io.stdio.freeOutBuf(state^.fg_stderr); state^.fg_stderr := nil; io.stdio.freeOutBuf(state^.fg_stdin); state^.fg_stdin := nil; @@ -783,7 +792,7 @@ begin { Reset input line } state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); end; { ============================================================ @@ -831,7 +840,7 @@ begin appendText(state, '^C'); appendChar(state, #10); state^.line_len := 0; - memset(uint32(@state^.line_buf[0]), 0, MAX_LINE); + memset(PtrUInt(@state^.line_buf[0]), 0, MAX_LINE); showPrompt(state); end; exit; @@ -849,8 +858,8 @@ begin if key = LV_KEY_UP then begin if state^.hist_count > 0 then begin if state^.hist_pos = -1 then begin - memcpy(uint32(@state^.line_buf[0]), - uint32(@state^.saved_line[0]), + memcpy(PtrUInt(@state^.line_buf[0]), + PtrUInt(@state^.saved_line[0]), state^.line_len + 1); state^.saved_len := state^.line_len; state^.hist_pos := 0; @@ -863,8 +872,8 @@ begin state^.text_buf[state^.text_len] := #0; state^.line_len := stringSize( @state^.hist[(sint32(state^.hist_head) - 1 - state^.hist_pos + HIST_SIZE * 2) mod HIST_SIZE][0]); - memcpy(uint32(@state^.hist[(sint32(state^.hist_head) - 1 - state^.hist_pos + HIST_SIZE * 2) mod HIST_SIZE][0]), - uint32(@state^.line_buf[0]), + memcpy(PtrUInt(@state^.hist[(sint32(state^.hist_head) - 1 - state^.hist_pos + HIST_SIZE * 2) mod HIST_SIZE][0]), + PtrUInt(@state^.line_buf[0]), state^.line_len + 1); appendText(state, @state^.line_buf[0]); refreshDisplay(state); @@ -882,12 +891,12 @@ begin if state^.hist_pos >= 0 then begin state^.line_len := stringSize( @state^.hist[(sint32(state^.hist_head) - 1 - state^.hist_pos + HIST_SIZE * 2) mod HIST_SIZE][0]); - memcpy(uint32(@state^.hist[(sint32(state^.hist_head) - 1 - state^.hist_pos + HIST_SIZE * 2) mod HIST_SIZE][0]), - uint32(@state^.line_buf[0]), + memcpy(PtrUInt(@state^.hist[(sint32(state^.hist_head) - 1 - state^.hist_pos + HIST_SIZE * 2) mod HIST_SIZE][0]), + PtrUInt(@state^.line_buf[0]), state^.line_len + 1); end else begin - memcpy(uint32(@state^.saved_line[0]), - uint32(@state^.line_buf[0]), + memcpy(PtrUInt(@state^.saved_line[0]), + PtrUInt(@state^.line_buf[0]), state^.saved_len + 1); state^.line_len := state^.saved_len; end; @@ -941,7 +950,7 @@ procedure onClose(wid: uint32); var cnt : Plv_obj; state : PVTermState; - slotPtr : ^uint32; + slotPtr : ^PtrUInt; bgJob : PBackgroundJob; scr_w : sint32; begin @@ -969,7 +978,7 @@ begin { Kill and free background jobs } if state^.bg_jobs <> nil then begin for scr_w := sint32(DL_Size(state^.bg_jobs)) - 1 downto 0 do begin - slotPtr := DL_Get(state^.bg_jobs, uint32(scr_w)); + slotPtr := pointer(DL_Get(state^.bg_jobs, uint32(scr_w))); if slotPtr <> nil then begin bgJob := PBackgroundJob(slotPtr^); if bgJob <> nil then begin @@ -1020,7 +1029,7 @@ begin { Allocate a new instance } state := PVTermState(kalloc(SizeOf(TVTermState))); if state = nil then exit; - memset(uint32(state), 0, SizeOf(TVTermState)); + memset(PtrUInt(state), 0, SizeOf(TVTermState)); state^.hist_pos := -1; state^.cwd := stringCopy('/'); diff --git a/src/app/wasm/app.wasm.cleanup.pas b/src/app/wasm/app.wasm.cleanup.pas index 3ac2e900..067aa0b9 100644 --- a/src/app/wasm/app.wasm.cleanup.pas +++ b/src/app/wasm/app.wasm.cleanup.pas @@ -36,6 +36,7 @@ var begin if heap = nil then exit; if heap^.Memory <> nil then begin + if heap^.PageCount <> 0 then for i := 0 to heap^.PageCount - 1 do begin if heap^.Memory[i] <> nil then kfree(void(heap^.Memory[i])); @@ -73,6 +74,7 @@ var begin if tables = nil then exit; if tables^.Tables <> nil then begin + if tables^.TableCount <> 0 then for i := 0 to tables^.TableCount - 1 do begin if tables^.Tables[i].Elements <> nil then kfree(void(tables^.Tables[i].Elements)); @@ -90,6 +92,7 @@ var begin if ds = nil then exit; if ds^.Segments <> nil then begin + if ds^.SegmentCount <> 0 then for i := 0 to ds^.SegmentCount - 1 do begin if ds^.Segments[i].Data <> nil then kfree(void(ds^.Segments[i].Data)); @@ -107,6 +110,7 @@ var begin if es = nil then exit; if es^.Segments <> nil then begin + if es^.SegmentCount <> 0 then for i := 0 to es^.SegmentCount - 1 do begin if es^.Segments[i].FuncIndices <> nil then kfree(void(es^.Segments[i].FuncIndices)); @@ -124,6 +128,7 @@ var begin if ts = nil then exit; if ts^.Types <> nil then begin + if ts^.TypeCount <> 0 then for i := 0 to ts^.TypeCount - 1 do begin if ts^.Types[i].ParamTypes <> nil then kfree(void(ts^.Types[i].ParamTypes)); @@ -143,6 +148,7 @@ var begin if is_ = nil then exit; if is_^.Entries <> nil then begin + if is_^.ImportCount <> 0 then for i := 0 to is_^.ImportCount - 1 do begin if is_^.Entries[i].ModuleName <> nil then kfree(void(is_^.Entries[i].ModuleName)); @@ -172,6 +178,7 @@ var begin if es = nil then exit; if es^.Entries <> nil then begin + if es^.ExportCount <> 0 then for i := 0 to es^.ExportCount - 1 do begin if es^.Entries[i].Name <> nil then kfree(void(es^.Entries[i].Name)); @@ -189,6 +196,7 @@ var begin if cs = nil then exit; if cs^.Entries <> nil then begin + if cs^.CodeCount <> 0 then for i := 0 to cs^.CodeCount - 1 do begin if cs^.Entries[i].Code <> nil then kfree(void(cs^.Entries[i].Code)); diff --git a/src/app/wasm/app.wasm.io.pas b/src/app/wasm/app.wasm.io.pas index bdcc552e..7dd7b797 100644 --- a/src/app/wasm/app.wasm.io.pas +++ b/src/app/wasm/app.wasm.io.pas @@ -4,9 +4,7 @@ Provides concrete WASI callback functions that route fd 0/1/2 to the owning Asuro process's StdIn/StdOut/StdErr buffers. - An ActiveShim module-level variable is set before each tick batch - and cleared after. Hooks only fire synchronously within wasm_tick - so this is safe under preemptive scheduling. + Hooks resolve the shim from the currently scheduled process. @author(Kieron Morris ) } @@ -20,9 +18,7 @@ uses wasm.types.context, wasm.types.values; -{ Set/clear the active shim — call before/after each tick batch } -procedure setActiveShim(shim : PProcessWASMShim); -procedure clearActiveShim; + { WASI hook implementations — register via wasm_set_fd_write etc. } function asuro_fd_write(fd : TWASMUInt32; buf : TWASMPUInt8; len : TWASMUInt32) : TWASMUInt32; @@ -46,19 +42,13 @@ implementation uses io.stdio, proc.types, wasm.types.wasi, wasm.types.stack, wasm.types.heap, - arch.x86.bda, core.rand, core.util, arch.x86.util; + proc.mgr, arch.platform.interrupts, arch.common.legacyirq, core.rand, core.util, arch.platform.cpu; -var - ActiveShimPtr : PProcessWASMShim; - -procedure setActiveShim(shim : PProcessWASMShim); -begin - ActiveShimPtr := shim; -end; - -procedure clearActiveShim; +function ActiveShimPtr: PProcessWASMShim; begin ActiveShimPtr := nil; + if proc.mgr.CurrentProcess <> nil then + ActiveShimPtr := PProcessWASMShim(proc.mgr.CurrentProcess^.Local); end; { ---- fd_write: fd 1 = stdout, fd 2 = stderr ---- } @@ -81,7 +71,7 @@ begin end; end; - if outbuf = nil then exit; + if (outbuf = nil) or (len = 0) then exit; { Write byte-by-byte into the POutBuf } for i := 0 to len - 1 do @@ -111,7 +101,7 @@ begin avail := inbuf^.len; if avail = 0 then exit; if len < avail then copyLen := len else copyLen := avail; - memcpy(uint32(inbuf^.buf), uint32(buf), copyLen); + memcpy(PtrUInt(inbuf^.buf), PtrUInt(buf), copyLen); asuro_fd_read := copyLen; end; @@ -126,13 +116,16 @@ begin ActiveShimPtr^.ProcessCtx^.ExitCode := code; end; -{ ---- clock_time_get: use BIOS tick counter ---- } +{ ---- Monotonic time from the platform timer ---- } function asuro_clock_time_get(clock_id : TWASMUInt32; precision : TWASMUInt64; var time : TWASMUInt64) : TWASMUInt32; +var + ticks: uint64; begin - { Return time in nanoseconds based on BIOS 1024 Hz counter } - time := TWASMUInt64(arch.x86.bda.Counters.c64) * 976562; + ticks := arch.platform.interrupts.timerTicks; + time := (ticks div PIT_INPUT_HZ) * PIT_DIVISOR * 1000000000 + + ((ticks mod PIT_INPUT_HZ) * PIT_DIVISOR * 1000000000) div PIT_INPUT_HZ; asuro_clock_time_get := WASI_ESUCCESS; end; @@ -141,7 +134,7 @@ end; function asuro_clock_res_get(clock_id : TWASMUInt32; var resolution : TWASMUInt64) : TWASMUInt32; begin - resolution := 976562; { ~0.977ms in nanoseconds (1/1024 s) } + resolution := (uint64(PIT_DIVISOR) * 1000000000 + PIT_INPUT_HZ - 1) div PIT_INPUT_HZ; asuro_clock_res_get := WASI_ESUCCESS; end; diff --git a/src/app/wasm/app.wasm.pas b/src/app/wasm/app.wasm.pas index 4ae337e3..a7341afe 100644 --- a/src/app/wasm/app.wasm.pas +++ b/src/app/wasm/app.wasm.pas @@ -2,8 +2,7 @@ unit app.wasm; interface -uses - boot.mgr; +procedure init; implementation @@ -12,14 +11,15 @@ uses wasm.vm.io, wasm; -procedure init(); +var + initialized: boolean = false; + +procedure init; begin - { Let's test Wasuro! } + if initialized then exit; wasm.vm.io.io_set_writechar(@io.syslog.logchar); wasm.wasm_init; + initialized := true; end; -initialization - boot.mgr.registerBoot('app.wasm', @init, 'Initialize WASM VM Backend', BOOT_MGR_BARRIER_FINAL) - -end. \ No newline at end of file +end. diff --git a/src/app/wasm/app.wasm.runner.pas b/src/app/wasm/app.wasm.runner.pas index e617f9ee..8e807c3d 100644 --- a/src/app/wasm/app.wasm.runner.pas +++ b/src/app/wasm/app.wasm.runner.pas @@ -17,25 +17,36 @@ uses io.stdio, debug.tracer; procedure init; +procedure UnitTest; implementation uses - driver.storage.vfs, driver.storage.types, memory.heap, core.strings, io.syslog, core.util, arch.x86.util, + driver.storage.vfs, driver.storage.types, memory.heap, core.strings, io.syslog, core.util, arch.platform.cpu, proc.mgr, proc.types, driver.storage.filedispatch, - app.wasm.shim, app.wasm.io, app.wasm.cleanup, + app.wasm, app.wasm.shim, app.wasm.io, app.wasm.cleanup, + arch.platform.memory, wasm.types.stack, wasm.types.values, wasm, wasm.types.builtin, wasm.types.context, wasm.types.constants; const WASM_TICK_BUDGET = 1024; { opcodes per yield iteration } WASI_MODULE = 'wasi_snapshot_preview1'; +function memoryAvailable: boolean; +begin + { Wasuro allocates two fixed-capacity stacks before parsing. Include heap + region rounding and working space; reject low-memory launches up front. } + memoryAvailable := arch.platform.memory.freePhysicalBytes > + uint64(2) * DEFAULT_STACK_SIZE * sizeof(TWASMStackEntry) + + uint64(4) * PAGE_SIZE_LMM; +end; + { ---- Param serialization ---- } { Serialize a PParamList into a flat null-terminated buffer stored in the shim. Each param becomes a null-terminated string packed consecutively. Sets shim^.ArgCount, ArgBuf, ArgBufSize. } -procedure serializeParams(shim : PProcessWASMShim; params : PParamList); +function serializeParams(shim : PProcessWASMShim; params : PParamList): boolean; var p : PParamList; total : uint32; @@ -43,6 +54,7 @@ var slen : uint32; dest : uint32; begin + serializeParams := false; { First pass: count params and total buffer size } count := 0; total := 0; @@ -50,6 +62,8 @@ begin while p <> nil do begin if p^.Param <> nil then begin slen := core.strings.stringSize(p^.Param); + if (slen = High(uint32)) or (total > High(uint32) - slen - 1) then + exit; total := total + slen + 1; { string + null terminator } inc(count); end; @@ -61,24 +75,26 @@ begin if (count = 0) or (total = 0) then begin shim^.ArgBuf := nil; + serializeParams := true; exit; end; { Allocate and fill } shim^.ArgBuf := pchar(kalloc(total)); - memset(uint32(shim^.ArgBuf), 0, total); + if shim^.ArgBuf = nil then exit; dest := 0; p := params; while p <> nil do begin if p^.Param <> nil then begin slen := core.strings.stringSize(p^.Param); - memcpy(uint32(p^.Param), uint32(shim^.ArgBuf) + dest, slen); + memcpy(PtrUInt(p^.Param), PtrUInt(shim^.ArgBuf) + dest, slen); dest := dest + slen; shim^.ArgBuf[dest] := #0; { null terminator } inc(dest); end; p := p^.Next; end; + serializeParams := true; end; { ---- Process entry point ---- } @@ -114,12 +130,12 @@ begin exit; { Execute a batch of opcodes } - app.wasm.io.setActiveShim(shim); + for i := 1 to WASM_TICK_BUDGET do begin alive := wasm_tick(wctx); if not alive then break; end; - app.wasm.io.clearActiveShim; + { Yield to scheduler after each batch } proc.mgr.proc_yield; @@ -158,6 +174,11 @@ begin debug.tracer.push_trace('wasmrunner.handler'); wasm_file_handler := 0; + if not memoryAvailable then begin + io.stdio.bufWriteStrLn(stderr_buf, 'WASM: insufficient memory for VM stacks'); + exit; + end; + { 1. Open file once — omRead pre-loads the data and gives us the size } err := eNone; fh := driver.storage.vfs.OpenFile(path, omRead, @err); @@ -184,35 +205,36 @@ begin bytesRead := driver.storage.vfs.ReadFile(fh, 0, buf, fsize); driver.storage.vfs.CloseFile(fh); - if bytesRead = 0 then begin + if bytesRead <> fsize then begin kfree(void(buf)); io.stdio.bufWriteStrLn(stderr_buf, 'WASM: file read failed'); exit; end; - { 3. Parse the WASM binary } - wctx := wasm_load(buf, TWASMPUInt8(uint32(buf) + bytesRead)); - if (wctx = nil) or (not wctx^.ValidBinary) then begin - kfree(void(buf)); - io.stdio.bufWriteStrLn(stderr_buf, 'WASM: invalid binary'); - exit; - end; - - { 4. Create the shim } + { Establish ownership before parsing so every failure uses one cleanup. } shim := PProcessWASMShim(kalloc(SizeOf(TProcessWASMShim))); if shim = nil then begin kfree(void(buf)); io.stdio.bufWriteStrLn(stderr_buf, 'WASM: out of memory (shim)'); exit; end; - memset(uint32(shim), 0, SizeOf(TProcessWASMShim)); - shim^.WASMCtx := wctx; shim^.FileBuffer := buf; shim^.FileSize := bytesRead; shim^.FaultKind := wfNone; + wctx := wasm_load(buf, TWASMPUInt8(PtrUInt(buf) + bytesRead)); + shim^.WASMCtx := wctx; + if (wctx = nil) or (not wctx^.ValidBinary) then begin + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); + io.stdio.bufWriteStrLn(stderr_buf, 'WASM: invalid binary'); + exit; + end; { 5. Serialize command-line params into the shim } - serializeParams(shim, params); + if not serializeParams(shim, params) then begin + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); + io.stdio.bufWriteStrLn(stderr_buf, 'WASM: cannot allocate arguments'); + exit; + end; { 6. Wire up WASI hooks to our IO bridge } wasm_set_fd_write(wctx, @app.wasm.io.asuro_fd_write); @@ -234,11 +256,9 @@ begin wasm_register_wasi_preview1(wctx); { 7. Create the process } - processCtx := proc.mgr.create('WASM', @wasm_entry, void(shim), 5); + processCtx := proc.mgr.createSuspended('WASM', @wasm_entry, void(shim), 5); if processCtx = nil then begin - kfree(void(buf)); - if shim^.ArgBuf <> nil then kfree(void(shim^.ArgBuf)); - kfree(void(shim)); + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); io.stdio.bufWriteStrLn(stderr_buf, 'WASM: failed to create process'); exit; end; @@ -255,10 +275,72 @@ begin proc.mgr.bindResource(processCtx, rkCustom, void(shim), @app.wasm.cleanup.wasm_resource_cleanup); wasm_file_handler := processCtx^.ProcessID; + processCtx^.State := psReady; end; { ---- Initialisation ---- } +procedure UnitTest; +const + { (module (import "wasi_snapshot_preview1" "proc_exit" (func (param i32))) + (func (export "_start") i32.const 42 call 0)) } + binary: array[0..81] of uint8 = ( + $00,$61,$73,$6D,$01,$00,$00,$00, + $01,$08,$02,$60,$01,$7F,$00,$60,$00,$00, + $02,$24,$01,$16, + $77,$61,$73,$69,$5F,$73,$6E,$61,$70,$73,$68,$6F,$74,$5F,$70,$72,$65,$76,$69,$65,$77,$31, + $09,$70,$72,$6F,$63,$5F,$65,$78,$69,$74,$00,$00, + $03,$02,$01,$01, + $07,$0A,$01,$06,$5F,$73,$74,$61,$72,$74,$00,$01, + $0A,$08,$01,$06,$00,$41,$2A,$10,$00,$0B); +var + shim: PProcessWASMShim; + previousLocal: void; + previousExit, steps: uint32; + ok: boolean; +begin + if not memoryAvailable then begin + io.syslog.logln('WASM', 'SKIP: insufficient memory for native WASM execution test'); + exit; + end; + shim := PProcessWASMShim(kalloc(sizeof(TProcessWASMShim))); + if shim = nil then exit; + shim^.FileBuffer := puint8(kalloc(sizeof(binary))); + if shim^.FileBuffer = nil then begin + kfree(void(shim)); + exit; + end; + memcpy(PtrUInt(@binary[0]), PtrUInt(shim^.FileBuffer), sizeof(binary)); + shim^.WASMCtx := wasm_load(shim^.FileBuffer, + TWASMPUInt8(PtrUInt(shim^.FileBuffer) + sizeof(binary))); + ok := shim^.WASMCtx <> nil; + if ok then ok := shim^.WASMCtx^.ValidBinary; + if ok then begin + previousLocal := proc.mgr.CurrentProcess^.Local; + previousExit := proc.mgr.CurrentProcess^.ExitCode; + shim^.ProcessCtx := proc.mgr.CurrentProcess; + proc.mgr.CurrentProcess^.Local := void(shim); + wasm_set_proc_exit(shim^.WASMCtx, @app.wasm.io.asuro_proc_exit); + wasm_register_wasi_preview1(shim^.WASMCtx); + ok := wasm_prepare_start(shim^.WASMCtx); + steps := 0; + while ok and shim^.WASMCtx^.ExecutionState.Running and (steps < 16) do begin + wasm_tick(shim^.WASMCtx); + inc(steps); + end; + ok := ok and (not shim^.WASMCtx^.ExecutionState.Running) and + (shim^.WASMCtx^.ExitCode = 42) and + (proc.mgr.CurrentProcess^.ExitCode = 42); + proc.mgr.CurrentProcess^.Local := previousLocal; + proc.mgr.CurrentProcess^.ExitCode := previousExit; + end; + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); + if ok then + io.syslog.logln('WASM', 'PASS: native WASM executes WASI proc_exit and releases its context') + else + io.syslog.logln('WASM', 'FAIL: native WASM execution'); +end; + procedure init; var magic : array[0..3] of uint8; @@ -267,7 +349,7 @@ begin io.syslog.logln('WASMRUNNER', 'INIT BEGIN.'); { One-time WASURO VM initialisation } - wasm_init; + app.wasm.init; { Register WASM magic ($00 $61 $73 $6D = "\0asm") with file dispatcher } magic[0] := $00; diff --git a/src/arch/common/arch.common.legacyirq.pas b/src/arch/common/arch.common.legacyirq.pas new file mode 100644 index 00000000..59a5942b --- /dev/null +++ b/src/arch/common/arch.common.legacyirq.pas @@ -0,0 +1,98 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared PC interrupt-controller programming. Call with interrupts disabled. } +unit arch.common.legacyirq; + +interface +const + PIT_INPUT_HZ = 1193182; + PIT_DIVISOR = 149; +procedure remap(masterMask, slaveMask: uint8); +procedure masks(masterMask, slaveMask: uint8); +procedure enableLine(line: uint8); +procedure timerStart; +function realIRQ(line: uint8): boolean; +procedure acknowledge(line: uint8); + +implementation +uses arch.platform.cpu; + +procedure writeSlow(port: uint16; value: uint8); +begin + outb(port, value); + outb($80, 0); +end; + +procedure masks(masterMask, slaveMask: uint8); +begin + writeSlow($21, masterMask); + writeSlow($A1, slaveMask); +end; + +procedure remap(masterMask, slaveMask: uint8); +begin + writeSlow($20, $11); + writeSlow($A0, $11); + writeSlow($21, $20); + writeSlow($A1, $28); + writeSlow($21, $04); + writeSlow($A1, $02); + writeSlow($21, $01); + writeSlow($A1, $01); + masks(masterMask, slaveMask); +end; + +procedure enableLine(line: uint8); +var + flags: PtrUInt; +begin + if line >= 16 then exit; + flags := arch.platform.cpu.lock; + if line < 8 then + outb($21, inb($21) and not uint8(1 shl line)) + else begin + outb($A1, inb($A1) and not uint8(1 shl (line - 8))); + outb($21, inb($21) and not uint8(4)); + end; + arch.platform.cpu.unlock(flags); +end; + +procedure timerStart; +begin + { Channel 0, low/high byte, mode 2, binary. } + outb($43, $34); + outb($40, PIT_DIVISOR and $FF); + outb($40, PIT_DIVISOR shr 8); +end; + +function realIRQ(line: uint8): boolean; +begin + realIRQ := line < 16; + if line = 7 then begin + outb($20, $0B); + realIRQ := (inb($20) and $80) <> 0; + end else if line = 15 then begin + outb($A0, $0B); + realIRQ := (inb($A0) and $80) <> 0; + { A spurious slave request still put master IRQ2 in service. } + if not realIRQ then outb($20, $20); + end; +end; + +procedure acknowledge(line: uint8); +begin + if line >= 16 then exit; + if line >= 8 then outb($A0, $20); + outb($20, $20); +end; +end. diff --git a/src/arch/common/platform.cpu.inc b/src/arch/common/platform.cpu.inc new file mode 100644 index 00000000..6c941146 --- /dev/null +++ b/src/arch/common/platform.cpu.inc @@ -0,0 +1,30 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared contract; implemented by each build-selected adapter. } +function lock: PtrUInt; +procedure unlock(flags: PtrUInt); +procedure outb(port: uint16; value: uint8); +function inb(port: uint16): uint8; +procedure outw(port: uint16; value: uint16); +function inw(port: uint16): uint16; +procedure copyBytes(source, dest: PtrUInt; bytes: SizeUInt); +procedure zero(dest: pointer; bytes: SizeUInt); +procedure idle; +procedure outl(port: uint16; value: uint32); +function inl(port: uint16): uint32; + +type + TCPURegisters = record + eax, ebx, ecx, edx: uint32; + end; +procedure queryCPU(leaf, subleaf: uint32; var registers: TCPURegisters); diff --git a/src/arch/common/platform.diagnostics.inc b/src/arch/common/platform.diagnostics.inc new file mode 100644 index 00000000..8a857356 --- /dev/null +++ b/src/arch/common/platform.diagnostics.inc @@ -0,0 +1,18 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared contract; implemented by each build-selected adapter. } +procedure log(tag, message: pchar); +procedure push_trace(name: pchar); +procedure pop_trace; +procedure fatal(tag, message: pchar); +procedure testSummary(tag, passedKey, failedKey: pchar; passed, failed: uint32); diff --git a/src/arch/common/platform.interrupts.inc b/src/arch/common/platform.interrupts.inc new file mode 100644 index 00000000..01cc2a40 --- /dev/null +++ b/src/arch/common/platform.interrupts.inc @@ -0,0 +1,19 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +type + TIRQHandler = procedure; + TTimerHandler = procedure(data: void); +procedure registerIRQ(vector: uint8; handler: TIRQHandler); +procedure registerTimer(handler: TTimerHandler); +procedure routePCI(line, vector: uint8); +function timerTicks: uint64; diff --git a/src/arch/common/platform.memory.inc b/src/arch/common/platform.memory.inc new file mode 100644 index 00000000..bc16d98d --- /dev/null +++ b/src/arch/common/platform.memory.inc @@ -0,0 +1,21 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared contract; implemented by each build-selected adapter. } +function allocateHeapPage: PtrUInt; +function allocateLarge(pages: uint32): PtrUInt; +procedure release(address: PtrUInt; pages: uint32); +function identityReserve(address: PtrUInt): pointer; +function freePhysicalBytes: uint64; +function totalPhysicalBytes: uint64; +function physicalAddress(address: PtrUInt): uint64; +function mapDevice(address: uint64; bytes: SizeUInt): pointer; diff --git a/src/arch/common/platform.process.inc b/src/arch/common/platform.process.inc new file mode 100644 index 00000000..80023dfd --- /dev/null +++ b/src/arch/common/platform.process.inc @@ -0,0 +1,15 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared contract; implemented by each build-selected adapter. } +function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt; +function timerTicks: uint32; diff --git a/src/arch/common/system.init.inc b/src/arch/common/system.init.inc new file mode 100644 index 00000000..e0a1ed0a --- /dev/null +++ b/src/arch/common/system.init.inc @@ -0,0 +1,33 @@ +// Copyright 2021 Kieron Morris & Aaron Hance +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared dispatch for the table emitted by compile_initcalls.sh. + Initializers only register boot work or set globals; they cannot depend on + heap, hardware setup, or the alphabetical order of this generated table. } +var + unitsInitialized: boolean; + +procedure initializeUnits; +var + i: longint; + entry: CodePointer; +begin + if unitsInitialized then exit; + unitsInitialized := true; + + for i := 1 to InitFinalTable.TableCount do begin + entry := InitFinalTable.Procs[i].InitProc; + if entry <> nil then + TProcedure(entry)(); + end; +end; diff --git a/src/arch/common/system.types.inc b/src/arch/common/system.types.inc new file mode 100644 index 00000000..d2072335 --- /dev/null +++ b/src/arch/common/system.types.inc @@ -0,0 +1,197 @@ +// Copyright 2021 Kieron Morris & Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared compiler and scalar types. Included by each architecture system unit. } +type + //internal core.types + cardinal = 0..$FFFFFFFF; + hresult = type longint; + dword = cardinal; + integer = longint; + pchar = ^char; + + //Standard Types + uInt8 = BYTE; + uInt16 = WORD; + uInt32 = DWORD; + uInt64 = QWORD; + uint128 = packed record + case Integer of + 0: ( + Hi : uint64; + Lo : uint64; + ); + 1: ( + DWords : array [0..3] of uint32; + ); + 2: ( + Words : array [0..7] of uint16; + ); + 3: ( + Bytes : array [0..15] of uint8; + ); + end; + + sInt8 = shortint; + sInt16 = smallint; + sInt32 = integer; + sInt64 = int64; + + Float = Single; + + //Pointer Types + PuByte = ^Byte; + PuInt8 = PuByte; + PuInt16 = ^uInt16; + PuInt32 = ^uInt32; + PuInt64 = ^uInt64; + PuInt128 = ^uint128; + + PsInt8 = ^sInt8; + PsInt16 = ^sInt16; + PsInt32 = ^sInt32; + PsInt64 = ^sInt64; + + PFloat = ^Float; + PDouble = ^Double; + + Void = ^uInt32; + + yord = uInt8; + xord = uInt8; + zord = uInt16; + + //Alternate Types + UBit1 = 0..(1 shl 01) - 1; + UBit2 = 0..(1 shl 02) - 1; + UBit3 = 0..(1 shl 03) - 1; + UBit4 = 0..(1 shl 04) - 1; + UBit5 = 0..(1 shl 05) - 1; + UBit6 = 0..(1 shl 06) - 1; + UBit7 = 0..(1 shl 07) - 1; + UBit9 = 0..(1 shl 09) - 1; + UBit10 = 0..(1 shl 10) - 1; + UBit11 = 0..(1 shl 11) - 1; + UBit12 = 0..(1 shl 12) - 1; + UBit13 = 0..(1 shl 13) - 1; + UBit14 = 0..(1 shl 14) - 1; + UBit15 = 0..(1 shl 15) - 1; + UBit16 = 0..(1 shl 16) - 1; + UBit17 = 0..(1 shl 17) - 1; + UBit18 = 0..(1 shl 18) - 1; + UBit19 = 0..(1 shl 19) - 1; + UBit20 = 0..(1 shl 20) - 1; + UBit21 = 0..(1 shl 21) - 1; + UBit22 = 0..(1 shl 22) - 1; + UBit23 = 0..(1 shl 23) - 1; + UBit24 = 0..(1 shl 24) - 1; + UBit25 = 0..(1 shl 25) - 1; + UBit26 = 0..(1 shl 26) - 1; + UBit27 = 0..(1 shl 27) - 1; + UBit28 = 0..(1 shl 28) - 1; + UBit30 = 0..(1 shl 30) - 1; + UBit31 = 0..(1 shl 31) - 1; + + TBitMask = bitpacked record + b0,b1,b2,b3,b4,b5,b6,b7 : Boolean; + end; + PBitMask = ^TBitMask; + + TMask = bitpacked array[0..7] of Boolean; + PMask = ^TMask; + + PText = ^Text; + + { ---- Types required by FPC 3.2.2 compiler internals ---- } + + { Native widths follow the selected compiler target. Fixed-width types above + remain unchanged for hardware descriptors and on-disk structures. } + PtrInt = SizeInt; + PtrUInt = SizeUInt; + ValSInt = SizeInt; + ValUInt = SizeUInt; + NativeInt = SizeInt; + NativeUInt = SizeUInt; + CodePointer = Pointer; + PCodePointer = ^CodePointer; + PSizeInt = ^SizeInt; + + { RTTI type kind enumeration — must match compiler's internal list } + TTypeKind = ( + tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, + tkSet, tkMethod, tkSString, tkLString, tkAString, + tkWString, tkVariant, tkArray, tkRecord, tkInterface, + tkClass, tkObject, tkWChar, tkBool, tkInt64, + tkQWord, tkDynArray, tkInterfaceRaw, tkProcVar, tkUString, + tkUChar, tkHelper, tkFile, tkClassRef, tkPointer + ); + + { GUID record — required by compiler for interface support } + PGuid = ^TGuid; + TGuid = packed record + case Integer of + 1: (Data1: DWord; Data2: Word; Data3: Word; Data4: array[0..7] of Byte); + 2: (D1: DWord; D2: Word; D3: Word; D4: array[0..7] of Byte); + 3: (time_low: DWord; time_mid: Word; time_hi_and_version: Word; + clock_seq_hi_and_reserved: Byte; clock_seq_low: Byte; + node: array[0..5] of Byte); + end; + + { File record core.types — required by compiler for file I/O internals } + FileRec = packed record + Handle : longint; + Mode : longint; + RecSize : SizeInt; + _private : array[0..31] of byte; + UserData : array[0..31] of byte; + name : array[0..255] of char; + end; + PFileRec = ^FileRec; + + TextBuf = array[0..255] of char; + TextRec = packed record + Handle : longint; + Mode : longint; + BufSize : SizeInt; + _private : SizeInt; + BufPos : SizeInt; + BufEnd : SizeInt; + BufPtr : ^TextBuf; + OpenFunc : CodePointer; + InOutFunc : CodePointer; + FlushFunc : CodePointer; + CloseFunc : CodePointer; + UserData : array[0..31] of byte; + name : array[0..255] of char; + LineEnd : array[0..3] of char; + Buffer : TextBuf; + end; + PTextRec = ^TextRec; + + { FPC 3.2.2 INITFINAL table — compiler-generated unit init/finalize table. + Each unit with an initialization or finalization section gets an entry. + The table is emitted by the compiler under the symbol 'INITFINAL'. } + TInitFinalRec = packed record + InitProc : CodePointer; + FinalProc: CodePointer; + end; + + TInitFinalTable = packed record + TableCount : longint; + Procs : array[1..1] of TInitFinalRec; { open array, actual size = TableCount } + end; + PInitFinalTable = ^TInitFinalTable; + + { Procedure type for calling initialization procs from the INITFINAL table } + TProcedure = procedure; diff --git a/src/arch/x86/arch.x86.console.pas b/src/arch/x86/arch.x86.console.pas new file mode 100644 index 00000000..42c3d030 --- /dev/null +++ b/src/arch/x86/arch.x86.console.pas @@ -0,0 +1,85 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Commands extracted from io.stdio (Kieron Morris). } +unit arch.x86.console; + +interface +procedure init; + +implementation +uses io.stdio, core.version, driver.timer.rtc, arch.x86.util; + +procedure cmd_version(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); +begin + bufWriteStr(stdout_buf, ' Asuro Version: '); + bufWriteStrLn(stdout_buf, core.version.VERSION); + bufWriteStr(stdout_buf, ' Compiled on: '); + bufWriteStr(stdout_buf, core.version.COMPILE_DATE); + bufWriteStr(stdout_buf, ' '); + bufWriteStrLn(stdout_buf, core.version.COMPILE_TIME); + bufWriteStrLn(stdout_buf, ' Compiled With: '); + bufWriteStr(stdout_buf, ' NASM - Version: '); + bufWriteStrLn(stdout_buf, core.version.NASM_VERSION); + bufWriteStr(stdout_buf, ' FPC - Version: '); + bufWriteStrLn(stdout_buf, core.version.FPC_VERSION); + bufWriteStr(stdout_buf, ' MAKE - Version: '); + bufWriteStrLn(stdout_buf, core.version.MAKE_VERSION); + bufWriteStr(stdout_buf, ' '); + bufWriteInt(stdout_buf, core.version.LINE_COUNT); + bufWriteStr(stdout_buf, ' lines, across '); + bufWriteInt(stdout_buf, core.version.FILE_COUNT); + bufWriteStrLn(stdout_buf, ' files.'); + bufWriteStr(stdout_buf, ' Baked Drivers: '); + bufWriteIntLn(stdout_buf, core.version.DRIVER_COUNT); + bufWriteStr(stdout_buf, ' Checksum: '); + bufWriteStrLn(stdout_buf, core.version.CHECKSUM); +end; + +procedure cmd_time(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); +var + dt: TDateTime; +begin + dt := getDateTime; + if dt.Day < 10 then bufWriteChar(stdout_buf, '0'); + bufWriteInt(stdout_buf, dt.Day); + bufWriteChar(stdout_buf, '/'); + if dt.Month < 10 then bufWriteChar(stdout_buf, '0'); + bufWriteInt(stdout_buf, dt.Month); + bufWriteChar(stdout_buf, '/'); + bufWriteInt(stdout_buf, dt.Century); + bufWriteInt(stdout_buf, dt.Year); + bufWriteChar(stdout_buf, ' '); + if dt.Hours < 10 then bufWriteChar(stdout_buf, '0'); + bufWriteInt(stdout_buf, dt.Hours); + bufWriteChar(stdout_buf, ':'); + if dt.Minutes < 10 then bufWriteChar(stdout_buf, '0'); + bufWriteInt(stdout_buf, dt.Minutes); + bufWriteChar(stdout_buf, ':'); + if dt.Seconds < 10 then bufWriteChar(stdout_buf, '0'); + bufWriteIntLn(stdout_buf, dt.Seconds); + bufWriteStr(stdout_buf, 'Weekday: '); + bufWriteStrLn(stdout_buf, WeekdayToString(dt.Weekday)); +end; + +procedure cmd_reboot(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); +begin + resetSystem; +end; + +procedure init; +begin + registerCommand('VERSION', @cmd_version, 'Display the running version of Asuro.'); + registerCommand('TIME', @cmd_time, 'Print the current time.'); + registerCommand('REBOOT', @cmd_reboot, 'Reboot the system.'); +end; +end. diff --git a/src/arch/x86/arch.x86.irq.pas b/src/arch/x86/arch.x86.irq.pas index 37e32a7a..abe8342f 100644 --- a/src/arch/x86/arch.x86.irq.pas +++ b/src/arch/x86/arch.x86.irq.pas @@ -1,4 +1,5 @@ // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +23,7 @@ unit arch.x86.irq; interface uses - boot.mgr, + boot.mgr, arch.common.legacyirq, core.util, arch.x86.util, io.syslog; procedure init(); @@ -32,30 +33,11 @@ implementation procedure init(); begin io.syslog.logln('IRQ','INIT START.'); - outb($20, $11); - io_wait; - outb($A0, $11); - io_wait; - outb($21, $20); - io_wait; - outb($A1, $28); - io_wait; - outb($21, $04); - io_wait; - outb($A1, $02); - io_wait; - outb($21, $01); - io_wait; - outb($A1, $01); - io_wait; - outb($21, $00); - io_wait; - outb($A1, $00); - io_wait; + arch.common.legacyirq.remap(0, 0); io.syslog.logln('IRQ','INIT END.'); end; initialization boot.mgr.registerBoot('arch.x86.irq', @init, 'Interrupt Request Line', 'arch.x86.idt'); -end. \ No newline at end of file +end. diff --git a/src/arch/x86/arch.x86.multiboot.pas b/src/arch/x86/arch.x86.multiboot.pas index e256a39c..bcdd53de 100644 --- a/src/arch/x86/arch.x86.multiboot.pas +++ b/src/arch/x86/arch.x86.multiboot.pas @@ -1,4 +1,4 @@ -// Copyright 2021 Kieron Morris +// Copyright 2021 Kieron Morris & Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,67 +21,24 @@ unit arch.x86.multiboot; interface +uses boot.multiboot; + const KERNEL_STACKSIZE = $4000; MULTIBOOT_BOOTLOADER_MAGIC = $2BADB002; type - Pelf_section_header_table_t = ^elf_section_header_table_t; - elf_section_header_table_t = packed record - num: uint32; - size: uint32; - addr: uint32; - shndx: uint32; - end; - - Pmultiboot_info_t = ^multiboot_info_t; - multiboot_info_t = packed record - flags: uint32; - mem_lower: uint32; - mem_upper: uint32; - boot_device: uint32; - cmdline: uint32; - mods_count: uint32; - mods_addr: uint32; - elf_sec: elf_section_header_table_t; - mmap_length: uint32; - mmap_addr: uint32; - drives_legnth : uint32; - drives_addr: uint32; - config_table : uint32; - boot_loader_name : uint32; - apm_table: uint32; - vbe_control_info : uint32; - vbe_mode_info : uint32; - vbe_mode : uint16; - vbe_interface_seg : uint16; - vbe_interface_off : uint16; - vbe_interface_len : uint16; - framebuffer_addr : uint64; - framebuffer_pitch : uint32; - framebuffer_width : uint32; - framebuffer_height: uint32; - framebuffer_bpp : uint8; - end; - - Pmodule_t = ^module_t; - module_t = packed record - mod_start: uint32; - mod_end: uint32; - name: uint32; - reserved: uint32; - end; - - Pmemory_map_t = ^memory_map_t; - memory_map_t = packed record - size: uint32; - base_addr : uint64; - length : uint64; - mtype: uint32; - end; - + elf_section_header_table_t = boot.multiboot.elf_section_header_table_t; + Pelf_section_header_table_t = boot.multiboot.Pelf_section_header_table_t; + multiboot_info_t = boot.multiboot.multiboot_info_t; + Pmultiboot_info_t = boot.multiboot.Pmultiboot_info_t; + module_t = boot.multiboot.module_t; + Pmodule_t = boot.multiboot.Pmodule_t; + memory_map_t = boot.multiboot.memory_map_t; + Pmemory_map_t = boot.multiboot.Pmemory_map_t; + var - multibootinfo : Pmultiboot_info_t = nil; + multibootinfo : Pmultiboot_info_t absolute boot.multiboot.multibootinfo; multibootmagic : uint32; implementation diff --git a/src/arch/x86/arch.x86.startup.pas b/src/arch/x86/arch.x86.startup.pas new file mode 100644 index 00000000..fd29a1ad --- /dev/null +++ b/src/arch/x86/arch.x86.startup.pas @@ -0,0 +1,121 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Compose shared services with the i386 boot manager and platform commands. } +unit arch.x86.startup; + +interface + +implementation +uses + app.mgr, app.vbeinfo, app.divzero, app.bsod, app.iotest, arch.x86.cpu, + driver.net, + driver.video.desktop, driver.timer.rtc, svc.gfxd, + app.uidebug, + driver.video.bga, driver.video, driver.video.gpu, driver.video.vesa, + driver.video.doublebuffer, driver.video.lvgl, + driver.hid.usb.keyboard, + driver.hid.usb.mouse, + driver.hid.ps2.keyboard, driver.hid.ps2.mouse, + driver.storage.ctl.usb, + driver.storage.metatest, + driver.mgr, + driver.bus.pci, + driver.storage.ctl.ahci, + driver.storage.mgr, + driver.storage.vfs, + driver.storage.vol.mgr, + driver.storage.fs.flatfs, + driver.storage.fs.iso9660, + driver.storage.fs.ext2, + driver.storage.fs.fat32, + driver.io.serial, + io.syslog, + debug.tracer, + driver.storage.fs.mgr, + boot.mgr, + memory.heap, + proc.mgr, + io.stdio, + arch.x86.console; + +procedure initApps; +begin + app.mgr.init; + io.stdio.registerCommand('CPU', @arch.x86.cpu.Terminal_Command_CPU, 'CPU Info.'); + app.vbeinfo.init; + app.divzero.init; + app.bsod.init; + app.iotest.init; +end; + +procedure consoleInit; +begin + io.stdio.init; + arch.x86.console.init; +end; +procedure lateInit(); +begin + driver.video.vesa.init(@driver.video.register); + driver.video.doublebuffer.init(@driver.video.register); + + io.syslog.logln('VIDEO', 'VESA Enable.'); + driver.video.enable('VESA'); + + io.syslog.logln('VIDEO', 'Double Buffer Enable.'); + driver.video.enable('BASIC_DOUBLE_BUFFER'); + + { Initialize LVGL early so the boot.splash screen can use it } + io.syslog.logln('VIDEO', 'LVGL: INIT BEGIN.'); + lvgl_init(driver.video.frontBufferWidth, driver.video.frontBufferHeight); + io.syslog.logln('VIDEO', 'LVGL: INIT END.'); +end; + +initialization + boot.mgr.registerBoot('app.mgr', @initApps, 'Program Manager', BOOT_MGR_BARRIER_LATE); + boot.mgr.registerBoot('driver.video.bga', @driver.video.bga.init, 'BGA Display Driver Initialization', 'driver.video'); + boot.mgr.registerBoot('driver.net', @driver.net.init, 'Net Driver', BOOT_MGR_BARRIER_DEVICE); + boot.mgr.registerBoot('driver.hid.ps2.keyboard', @driver.hid.ps2.keyboard.init, 'PS/2 Keyboard Driver', BOOT_MGR_BARRIER_DEVICE); + boot.mgr.registerBoot('driver.hid.ps2.mouse', @driver.hid.ps2.mouse.init, 'PS/2 Mouse Driver', BOOT_MGR_BARRIER_DEVICE); + boot.mgr.registerBoot('proc.desktop', @driver.video.desktop.init, 'Initialize Desktop', 'boot.splash.teardown'); + boot.mgr.registerBoot('svc.gfxd', @svc.gfxd.init, 'GFX Daemon', BOOT_MGR_BARRIER_LATE); + boot.mgr.registerBoot('driver.timer.rtc', @driver.timer.rtc.init, 'Real Time Clock Driver', 'arch.x86.fault'); + boot.mgr.registerBoot('driver.video', @driver.video.init, 'Video Driver Initialization', 'driver.video.gpu'); + boot.mgr.registerBoot('driver.video.gpu', @driver.video.gpu.init, 'GPU Framework', BOOT_MGR_BARRIER_EARLY); + boot.mgr.registerBoot('driver.video_late', @lateInit, 'Video Driver Late Initialization', 'driver.video.*'); + driver.video.lvgl.onDebugToggle := @app.uidebug.toggle; + driver.hid.usb.keyboard.onAttach := @driver.hid.ps2.keyboard.disable; + boot.mgr.registerBoot('driver.hid.usb.keyboard', @driver.hid.usb.keyboard.init, 'USB Keyboard Driver', BOOT_MGR_BARRIER_DEVICE); + boot.mgr.registerBoot('driver.hid.usb.mouse', @driver.hid.usb.mouse.init, 'USB Mouse Driver', BOOT_MGR_BARRIER_DEVICE); + boot.mgr.registerBoot('driver.storage.ctl.usb', @driver.storage.ctl.usb.init, 'USB Mass Storage Driver', BOOT_MGR_BARRIER_DEVICE); + boot.mgr.registerBoot('driver.storage.metatest', @driver.storage.metatest.init, 'Storage Metadata Tests', BOOT_MGR_BARRIER_MID); + boot.mgr.registerBoot('driver.mgr', @driver.mgr.init, 'Driver Manager Initialization', BOOT_MGR_BARRIER_MID); + boot.mgr.registerBoot('driver.bus.pci', @driver.bus.pci.Init, 'PCI Bus Driver', BOOT_MGR_BARRIER_BUS_LATE); + boot.mgr.registerBoot('driver.storage.ctl.ahci', @driver.storage.ctl.ahci.init, 'AHCI Controller', 'driver.storage.fs.*'); + boot.mgr.registerBoot('driver.storage.mgr', @driver.storage.mgr.init, 'Storage Manager', 'driver.storage.vfs'); + boot.mgr.registerBoot('driver.storage.vfs', @driver.storage.vfs.init, 'Virtual File System', BOOT_MGR_BARRIER_STORAGE); + boot.mgr.registerBoot('driver.storage.vol.mount', @driver.storage.vfs.auto_mount_volumes, 'Mount Storage Volumes', BOOT_MGR_BARRIER_LATE); + boot.mgr.registerBoot('driver.storage.vol.mgr', @driver.storage.vol.mgr.init, 'Volume Manager', 'driver.storage.mgr*'); + boot.mgr.registerBoot('driver.storage.fs.flatfs', @driver.storage.fs.flatfs.init, 'FlatFS Filesystem', 'driver.storage.fs.mgr'); + boot.mgr.registerBoot('driver.storage.fs.iso9660', @driver.storage.fs.iso9660.Init, 'IS9660 File System', 'driver.storage.fs.mgr'); + boot.mgr.registerBoot('driver.storage.fs.ext2',@driver.storage.fs.ext2.init,'Ext2 Filesystem','driver.storage.fs.mgr'); + boot.mgr.registerBoot('driver.storage.ext2.worker',@driver.storage.fs.ext2.StartWorker,'Ext2 I/O Worker','proc.mgr'); + boot.mgr.registerBoot('driver.storage.fs.fat32', @driver.storage.fs.fat32.init, 'FAT32 Filesystem', 'driver.storage.fs.mgr'); + boot.mgr.registerBoot('driver.io.serial', @driver.io.serial.init, 'Serial Interface', 'immediate'); + boot.mgr.registerBoot('io.syslog', @io.syslog.init, 'Syslog Interface', 'driver.io.serial'); + boot.mgr.registerBoot('debug.tracer.freeze', @debug.tracer.freeze, 'Debug Tracer Freeze', 'io.syslog' ); + boot.mgr.registerBoot('debug.tracer', @debug.tracer.init, 'Debug Tracer Initialization', 'io.stdio' ); + boot.mgr.registerBoot('driver.storage.fs.mgr', @driver.storage.fs.mgr.init, 'Filesystem Manager', 'driver.storage.vol.mgr'); + boot.mgr.registerBoot('memory.heap', @memory.heap.init, 'Heap Memory Manager', 'arch.*.memory.*'); + boot.mgr.registerBoot('io.stdio', @consoleInit, 'Standard I/O Abstraction', BOOT_MGR_BARRIER_EARLY); + boot.mgr.registerBoot('proc.mgr', @proc.mgr.init, 'Process Manager', 'driver.storage.fs*'); +end. diff --git a/src/arch/x86/arch.x86.util.pas b/src/arch/x86/arch.x86.util.pas index e42fa8bc..b7d7b389 100644 --- a/src/arch/x86/arch.x86.util.pas +++ b/src/arch/x86/arch.x86.util.pas @@ -62,7 +62,6 @@ procedure resetSystem(); function getESP : uint32; -function RolDWord(AValue : uint32; Dist : uint8) : uint32; function RorDWord(AValue : uint32; Dist : uint8) : uint32; function MsSinceSystemBoot : uint64; @@ -380,29 +379,6 @@ begin getTSC:= (hi SHL 32) OR lo; end; -function RolDWord(AValue : uint32; Dist : uint8) : uint32; -var - result : uint32; - i : uint8; - -begin - result:= AValue; - asm - PUSH EAX - end; - for i:=0 to Dist-1 do begin - asm - MOV EAX, result - ROL EAX, 1 - MOV result, EAX - end; - end; - asm - POP EAX - end; - RolDWord:= result; -end; - function RorDWord(AValue : uint32; Dist : uint8) : uint32; var result : uint32; diff --git a/src/arch/x86/isr/arch.x86.isr.tmr0.pas b/src/arch/x86/isr/arch.x86.isr.tmr0.pas index 9dd86a7e..7a3773e3 100644 --- a/src/arch/x86/isr/arch.x86.isr.tmr0.pas +++ b/src/arch/x86/isr/arch.x86.isr.tmr0.pas @@ -1,4 +1,5 @@ // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,14 +23,14 @@ unit arch.x86.isr.tmr0; interface uses - core.util, arch.x86.util, + core.util, arch.x86.util, arch.common.legacyirq, arch.x86.isr.types, arch.x86.isr.mgr, arch.x86.idt; const - PIT_INPUT_HZ = 1193182; - PIT_DIVISOR = 149; + PIT_INPUT_HZ = arch.common.legacyirq.PIT_INPUT_HZ; + PIT_DIVISOR = arch.common.legacyirq.PIT_DIVISOR; procedure register(); procedure hook(hook_method : uint32); diff --git a/src/arch/x86/platform/arch.platform.cpu.pas b/src/arch/x86/platform/arch.platform.cpu.pas new file mode 100644 index 00000000..0167cff3 --- /dev/null +++ b/src/arch/x86/platform/arch.platform.cpu.pas @@ -0,0 +1,116 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Build-selected x86 adapter. } +unit arch.platform.cpu; + +interface +{$i ../../common/platform.cpu.inc} + +implementation +uses arch.x86.util, memory.raw; + +function lock: PtrUInt; assembler; nostackframe; +asm + pushfd; + cli; + pop eax +end; + +procedure unlock(flags: PtrUInt); +begin + if (flags and $200) <> 0 then asm + sti + end; +end; + +procedure idle; +begin + asm + hlt + end; +end; + +procedure outb(port: uint16; value: uint8); +begin + arch.x86.util.outb(port, value); +end; + +function inb(port: uint16): uint8; +begin + inb := arch.x86.util.inb(port); +end; + +procedure copyBytes(source, dest: PtrUInt; bytes: SizeUInt); +begin + if bytes >= 16 then arch.x86.util.__REP_MOVSB_memcpy(source, dest, bytes) + else memory.raw.move(pointer(source), pointer(dest), bytes); +end; + +procedure outw(port: uint16; value: uint16); +begin + arch.x86.util.outw(port, value); +end; + +function inw(port: uint16): uint16; +begin + inw := arch.x86.util.inw(port); +end; + +procedure zero(dest: pointer; bytes: SizeUInt); +var + d: pointer; + b: SizeUInt; +begin + d := dest; + b := bytes; + if b = 0 then exit; + asm + push edi + mov edi, d + mov ecx, b + shr ecx, 2 + xor eax, eax + cld + rep stosd + mov ecx, b + and ecx, 3 + rep stosb + pop edi + end; +end; +procedure outl(port: uint16; value: uint32); +begin + arch.x86.util.outl(port, value); +end; + +function inl(port: uint16): uint32; +begin + inl := arch.x86.util.inl(port); +end; + +procedure queryCPU(leaf, subleaf: uint32; var registers: TCPURegisters); assembler; nostackframe; +asm + push ebx + push edi + mov edi, ecx + mov ecx, edx + cpuid + mov [edi], eax + mov [edi + 4], ebx + mov [edi + 8], ecx + mov [edi + 12], edx + pop edi + pop ebx +end; + +end. diff --git a/src/arch/x86/platform/arch.platform.diagnostics.pas b/src/arch/x86/platform/arch.platform.diagnostics.pas new file mode 100644 index 00000000..0d15ea8f --- /dev/null +++ b/src/arch/x86/platform/arch.platform.diagnostics.pas @@ -0,0 +1,50 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Build-selected x86 adapter. } +unit arch.platform.diagnostics; + +interface +{$i ../../common/platform.diagnostics.inc} + +implementation +uses io.syslog, debug.tracer, core.panic; + +procedure log(tag, message: pchar); +begin + io.syslog.logln(tag, message); +end; + +procedure push_trace(name: pchar); +begin + debug.tracer.push_trace(name); +end; + +procedure pop_trace; +begin + debug.tracer.pop_trace; +end; + +procedure fatal(tag, message: pchar); +begin + core.panic.panic(tag, message, nil); +end; + +procedure testSummary(tag, passedKey, failedKey: pchar; passed, failed: uint32); +begin + io.syslog.log(tag, ''); + io.syslog.writeint(passed); + io.syslog.writestring(' passed, '); + io.syslog.writeint(failed); + io.syslog.writestringln(' failed.'); +end; +end. diff --git a/src/arch/x86/platform/arch.platform.interrupts.pas b/src/arch/x86/platform/arch.platform.interrupts.pas new file mode 100644 index 00000000..47397e20 --- /dev/null +++ b/src/arch/x86/platform/arch.platform.interrupts.pas @@ -0,0 +1,47 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.platform.interrupts; + +interface +{$i ../../common/platform.interrupts.inc} + +implementation +uses arch.x86.isr.mgr, arch.x86.isr.tmr0, arch.x86.isr.ioapic, + arch.x86.bda, arch.platform.cpu; + +function timerTicks: uint64; +var + flags: PtrUInt; +begin + flags := arch.platform.cpu.lock; + timerTicks := Counters.c64; + arch.platform.cpu.unlock(flags); +end; + +procedure registerIRQ(vector: uint8; handler: TIRQHandler); +begin + arch.x86.isr.mgr.registerISR(vector, handler); +end; + +procedure registerTimer(handler: TTimerHandler); +begin + arch.x86.isr.tmr0.hook(PtrUInt(handler)); +end; + +procedure routePCI(line, vector: uint8); +begin + arch.x86.isr.ioapic.init; + arch.x86.isr.ioapic.route_pci_irq(line, vector); +end; + +end. diff --git a/src/arch/x86/platform/arch.platform.memory.pas b/src/arch/x86/platform/arch.platform.memory.pas new file mode 100644 index 00000000..302aa876 --- /dev/null +++ b/src/arch/x86/platform/arch.platform.memory.pas @@ -0,0 +1,126 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Mapping policy extracted from memory.heap (Kieron Morris). } +unit arch.platform.memory; + +interface +const MAX_HEAP_REGIONS = 256; +{$i ../../common/platform.memory.inc} + +implementation +uses arch.x86.memory.virtual, arch.x86.memory.physical, arch.platform.diagnostics; + +function allocateHeapPage: PtrUInt; +var + i: uint32; +begin + allocateHeapPage := 0; + i := KERNEL_PAGE_NUMBER + 4; + while i < 1024 do begin + if arch.x86.memory.virtual.new_page(i) then begin + allocateHeapPage := i shl 22; + exit; + end; + inc(i); + end; +end; + +function allocateLarge(pages: uint32): PtrUInt; +var + curr_page, i: uint16; + miss: boolean; +begin + allocateLarge := 0; + if (pages = 0) or (pages > MAX_HEAP_REGIONS) then exit; + curr_page := 5; + while uint32(curr_page) + pages - 1 < KERNEL_PAGE_NUMBER do begin + miss := false; + for i := 0 to pages - 1 do begin + if not page_mappable(curr_page + i) then begin + miss := true; + curr_page := curr_page + i; + break; + end; + end; + if not miss then begin + i := 0; + while i < pages do begin + if not arch.x86.memory.virtual.new_page(curr_page + i) then begin + while i > 0 do begin + dec(i); + arch.x86.memory.virtual.free_page(curr_page + i); + end; + exit; + end; + inc(i); + end; + allocateLarge := uint32(curr_page) shl 22; + exit; + end; + inc(curr_page); + end; +end; + +procedure release(address: PtrUInt; pages: uint32); +var + i: uint32; + page_num: uint16; +begin + if (pages = 0) or (pages > MAX_HEAP_REGIONS) then begin + fatal('LMM', 'Invalid heap region release'); + exit; + end; + page_num := address shr 22; + for i := 0 to pages - 1 do arch.x86.memory.virtual.free_page(page_num + uint16(i)); +end; + +function identityReserve(address: PtrUInt): pointer; +var + block: uint16; +begin + block := address shr 22; + force_alloc_block(block, 0); + map_page(block, block); + identityReserve := pointer(uint32(block) shl 22); +end; + +function freePhysicalBytes: uint64; +begin + freePhysicalBytes := uint64(pmm_free_blocks) * $400000; +end; + +function totalPhysicalBytes: uint64; +begin + totalPhysicalBytes := uint64(pmm_total_blocks) * $400000; +end; +function physicalAddress(address: PtrUInt): uint64; +begin + physicalAddress := arch.x86.memory.virtual.vtop(address); +end; + +function mapDevice(address: uint64; bytes: SizeUInt): pointer; +var + page, last: uint32; +begin + mapDevice := nil; + if (bytes = 0) or (address > $FFFFFFFF) or (bytes - 1 > $FFFFFFFF - address) then exit; + page := uint32(address) shr 22; + last := uint32(address + bytes - 1) shr 22; + while page <= last do begin + identityReserve(page shl 22); + inc(page); + end; + mapDevice := pointer(PtrUInt(address)); +end; + +end. diff --git a/src/arch/x86/platform/arch.platform.process.pas b/src/arch/x86/platform/arch.platform.process.pas new file mode 100644 index 00000000..75ae5f20 --- /dev/null +++ b/src/arch/x86/platform/arch.platform.process.pas @@ -0,0 +1,31 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Build-selected x86 adapter. } +unit arch.platform.process; + +interface +{$i ../../common/platform.process.inc} + +implementation +uses arch.x86.proc.context, arch.x86.bda; + +function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt; +begin + prepare := arch.x86.proc.context.prepare(stack, bytes, entry); +end; + +function timerTicks: uint32; +begin + timerTicks := PUInt32(@Counters.c32)^; +end; +end. diff --git a/src/arch/x86/proc/arch.x86.proc.context.pas b/src/arch/x86/proc/arch.x86.proc.context.pas new file mode 100644 index 00000000..40bcb326 --- /dev/null +++ b/src/arch/x86/proc/arch.x86.proc.context.pas @@ -0,0 +1,40 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ i386 initial frame, extracted from proc.mgr (Kieron Morris). } +unit arch.x86.proc.context; + +interface +function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt; + +implementation +uses memory.raw; + +function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt; +var + top, frame: PtrUInt; + i: uint32; +begin + prepare := 0; + if (stack = nil) or (entry = nil) or (bytes < 64) or + (PtrUInt(stack) > High(PtrUInt) - bytes) then exit; + top := PtrUInt(stack) + bytes; + frame := top - 60; + memory.raw.fill(pointer(frame), 0, 60); + { GS, FS, ES, DS; PUSHAD; EIP, CS, EFLAGS. } + for i := 0 to 3 do PUInt32(frame + i * 4)^ := $10; + PUInt32(top - 12)^ := PtrUInt(entry); + PUInt32(top - 8)^ := $08; + PUInt32(top - 4)^ := $0202; + prepare := frame; +end; +end. diff --git a/src/arch/x86/proc/arch.x86.proc.sched.pas b/src/arch/x86/proc/arch.x86.proc.sched.pas index 3dcc2a01..c1e5fd9f 100644 --- a/src/arch/x86/proc/arch.x86.proc.sched.pas +++ b/src/arch/x86/proc/arch.x86.proc.sched.pas @@ -1,4 +1,5 @@ // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -35,7 +36,7 @@ uses procedure init; { No idle ESP variable needed — idle is now a formal process (PID 0) - with its SavedESP managed like any other process. } + with its SavedStack managed like any other process. } implementation @@ -51,7 +52,7 @@ var next : PProcessContext; begin { 1. Save ESP into the current process } - proc.mgr.CurrentProcess^.SavedESP := saved_esp; + proc.mgr.CurrentProcess^.SavedStack := saved_esp; { 2. Dispatch all hooks registered on interrupt 32 (timer hooks). This fires TMR_0_ISR.Main which in turn fires BDA tick, @@ -66,7 +67,7 @@ begin proc.mgr.CurrentProcess := next; { 5. Return the selected process ESP } - do_context_switch_work := next^.SavedESP; + do_context_switch_work := next^.SavedStack; end; { ----------------------------------------------------------------------- @@ -129,4 +130,4 @@ begin debug.tracer.push_trace('arch.x86.proc.sched.init'); end; -end. \ No newline at end of file +end. diff --git a/src/arch/x86/system.pas b/src/arch/x86/system.pas index d8d22ea4..ea974ee3 100644 --- a/src/arch/x86/system.pas +++ b/src/arch/x86/system.pas @@ -28,131 +28,12 @@ const BSOD_ENABLE = true; TRACER_ENABLE = true; +type SizeInt = longint; + SizeUInt = longword; + +{$i ../common/system.types.inc} + type - //internal core.types - cardinal = 0..$FFFFFFFF; - hresult = type longint; - dword = cardinal; - integer = longint; - pchar = ^char; - - //Standard Types - uInt8 = BYTE; - uInt16 = WORD; - uInt32 = DWORD; - uInt64 = QWORD; - uint128 = packed record - case Integer of - 0: ( - Hi : uint64; - Lo : uint64; - ); - 1: ( - DWords : array [0..3] of uint32; - ); - 2: ( - Words : array [0..7] of uint16; - ); - 3: ( - Bytes : array [0..15] of uint8; - ); - end; - - sInt8 = shortint; - sInt16 = smallint; - sInt32 = integer; - sInt64 = int64; - - Float = Single; - - //Pointer Types - PuByte = ^Byte; - PuInt8 = PuByte; - PuInt16 = ^uInt16; - PuInt32 = ^uInt32; - PuInt64 = ^uInt64; - PuInt128 = ^uint128; - - PsInt8 = ^sInt8; - PsInt16 = ^sInt16; - PsInt32 = ^sInt32; - PsInt64 = ^sInt64; - - PFloat = ^Float; - PDouble = ^Double; - - Void = ^uInt32; - - yord = uInt8; - xord = uInt8; - zord = uInt16; - - //Alternate Types - UBit1 = 0..(1 shl 01) - 1; - UBit2 = 0..(1 shl 02) - 1; - UBit3 = 0..(1 shl 03) - 1; - UBit4 = 0..(1 shl 04) - 1; - UBit5 = 0..(1 shl 05) - 1; - UBit6 = 0..(1 shl 06) - 1; - UBit7 = 0..(1 shl 07) - 1; - UBit9 = 0..(1 shl 09) - 1; - UBit10 = 0..(1 shl 10) - 1; - UBit11 = 0..(1 shl 11) - 1; - UBit12 = 0..(1 shl 12) - 1; - UBit13 = 0..(1 shl 13) - 1; - UBit14 = 0..(1 shl 14) - 1; - UBit15 = 0..(1 shl 15) - 1; - UBit16 = 0..(1 shl 16) - 1; - UBit17 = 0..(1 shl 17) - 1; - UBit18 = 0..(1 shl 18) - 1; - UBit19 = 0..(1 shl 19) - 1; - UBit20 = 0..(1 shl 20) - 1; - UBit21 = 0..(1 shl 21) - 1; - UBit22 = 0..(1 shl 22) - 1; - UBit23 = 0..(1 shl 23) - 1; - UBit24 = 0..(1 shl 24) - 1; - UBit25 = 0..(1 shl 25) - 1; - UBit26 = 0..(1 shl 26) - 1; - UBit27 = 0..(1 shl 27) - 1; - UBit28 = 0..(1 shl 28) - 1; - UBit30 = 0..(1 shl 30) - 1; - UBit31 = 0..(1 shl 31) - 1; - - TBitMask = bitpacked record - b0,b1,b2,b3,b4,b5,b6,b7 : Boolean; - end; - PBitMask = ^TBitMask; - - TMask = bitpacked array[0..7] of Boolean; - PMask = ^TMask; - - PText = ^Text; - - { ---- Types required by FPC 3.2.2 compiler internals ---- } - - { Size/pointer integer core.types for 32-bit target } - SizeInt = longint; - SizeUInt = cardinal; - PtrInt = longint; - PtrUInt = cardinal; - ValSInt = longint; - ValUInt = cardinal; - NativeInt = longint; - NativeUInt = cardinal; - CodePointer = Pointer; - PCodePointer = ^CodePointer; - PSizeInt = ^SizeInt; - - { RTTI type kind enumeration — must match compiler's internal list } - TTypeKind = ( - tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, - tkSet, tkMethod, tkSString, tkLString, tkAString, - tkWString, tkVariant, tkArray, tkRecord, tkInterface, - tkClass, tkObject, tkWChar, tkBool, tkInt64, - tkQWord, tkDynArray, tkInterfaceRaw, tkProcVar, tkUString, - tkUChar, tkHelper, tkFile, tkClassRef, tkPointer - ); - { jmp_buf for i386 — used by exception handling internals } jmp_buf = packed record ebx, esi, edi: LongInt; @@ -168,64 +49,6 @@ type frametype: LongInt; end; - { GUID record — required by compiler for interface support } - PGuid = ^TGuid; - TGuid = packed record - case Integer of - 1: (Data1: DWord; Data2: Word; Data3: Word; Data4: array[0..7] of Byte); - 2: (D1: DWord; D2: Word; D3: Word; D4: array[0..7] of Byte); - 3: (time_low: DWord; time_mid: Word; time_hi_and_version: Word; - clock_seq_hi_and_reserved: Byte; clock_seq_low: Byte; - node: array[0..5] of Byte); - end; - - { File record core.types — required by compiler for file I/O internals } - FileRec = packed record - Handle : longint; - Mode : longint; - RecSize : SizeInt; - _private : array[0..31] of byte; - UserData : array[0..31] of byte; - name : array[0..255] of char; - end; - PFileRec = ^FileRec; - - TextBuf = array[0..255] of char; - TextRec = packed record - Handle : longint; - Mode : longint; - BufSize : SizeInt; - _private : SizeInt; - BufPos : SizeInt; - BufEnd : SizeInt; - BufPtr : ^TextBuf; - OpenFunc : CodePointer; - InOutFunc : CodePointer; - FlushFunc : CodePointer; - CloseFunc : CodePointer; - UserData : array[0..31] of byte; - name : array[0..255] of char; - LineEnd : array[0..3] of char; - Buffer : TextBuf; - end; - PTextRec = ^TextRec; - - { FPC 3.2.2 INITFINAL table — compiler-generated unit init/finalize table. - Each unit with an initialization or finalization section gets an entry. - The table is emitted by the compiler under the symbol 'INITFINAL'. } - TInitFinalRec = packed record - InitProc : CodePointer; - FinalProc: CodePointer; - end; - - TInitFinalTable = packed record - TableCount : longint; - Procs : array[1..1] of TInitFinalRec; { open array, actual size = TableCount } - end; - PInitFinalTable = ^TInitFinalTable; - - { Procedure type for calling initialization procs from the INITFINAL table } - TProcedure = procedure; var { Compiler-generated INITFINAL table — contains init/finalize procs for @@ -296,7 +119,6 @@ function Round(d: Double): int64; implementation { ---- Raw driver.io.serial debug helpers (COM1 $3F8, no dependencies) ---- } - procedure serial_putch(ch: char); assembler; asm push edx @@ -331,7 +153,8 @@ begin end; procedure serial_putstr(s: pchar); -var i: uint32; +var + i: uint32; begin i := 0; while s[i] <> #0 do begin @@ -373,20 +196,11 @@ begin end; end; +{$i ../common/system.init.inc} + procedure fpc_initializeunits; [public, alias: 'FPC_INITIALIZEUNITS']; compilerproc; -var - i : longint; - proc : CodePointer; begin - { Walk the compiler-generated INITFINAL table and call each unit's - initialization procedure (if non-nil). This runs before kmain, so - initialization sections must only do trivial work — write to static - globals, register boot entries, etc. No I/O or heap allocation. } - for i := 1 to InitFinalTable.TableCount do begin - proc := InitFinalTable.Procs[i].InitProc; - if proc <> nil then - TProcedure(proc)(); - end; + initializeUnits; end; procedure fpc_do_exit; [public, alias: 'FPC_DO_EXIT']; compilerproc; @@ -525,7 +339,8 @@ begin end; function fpc_mul_dword_to_qword(f1, f2: uint32): qword; [public, alias: 'FPC_MUL_DWORD_TO_QWORD']; compilerproc; -var res: qword; +var + res: qword; begin asm MOV EAX, f1 @@ -545,8 +360,10 @@ procedure udiv64(dividendLo, dividendHi, divisorLo, divisorHi: uint32; var bit: longint; begin - quotLo := 0; quotHi := 0; - remLo := 0; remHi := 0; + quotLo := 0; + quotHi := 0; + remLo := 0; + remHi := 0; for bit := 63 downto 0 do begin { rem := rem shl 1 } @@ -744,7 +561,6 @@ begin end; { ---------- Float-to-integer intrinsics ---------- } - function Trunc(d: Double): int64; [public, alias: 'FPC_TRUNC']; var oldcw, newcw: word; @@ -776,8 +592,8 @@ begin end; { ---------- Memory allocation compilerprocs ---------- } - function kernel_kalloc(size: uint32): Pointer; external name 'kernel_kalloc'; + procedure kernel_kfree(p: Pointer); external name 'kernel_kfree'; function fpc_getmem(size: PtrUInt): Pointer; @@ -793,7 +609,6 @@ begin end; { ---------- Exception handling compilerprocs ---------- } - procedure fpc_reraise; [public, alias: 'FPC_RERAISE']; compilerproc; begin { In a bare-metal core.version re-raise is a fatal condition } @@ -818,30 +633,17 @@ begin end; procedure init(); -var - i : longint; - proc : CodePointer; begin ASURO_KERNEL_START := uint32(@AK_START); ASURO_KERNEL_END := uint32(@AK_END); - ASURO_KERNEL_SIZE:= ASURO_KERNEL_END - ASURO_KERNEL_START; + ASURO_KERNEL_SIZE := ASURO_KERNEL_END - ASURO_KERNEL_START; ExceptAddrStack := nil; ErrorAddr := nil; ErrorCode := 0; ExitCode := 0; ExitProc := nil; - { Walk the INITFINAL table generated by compile_initcalls.sh and call - each unit's initialization section. FPC with -n does not insert this - call automatically, so we do it explicitly here — after basic system - globals are set up but before kmain proceeds. - Note: we inline the walk rather than calling fpc_initializeunits - because compilerproc procedures cannot be called from Pascal code. } - for i := 1 to InitFinalTable.TableCount do begin - proc := InitFinalTable.Procs[i].InitProc; - if proc <> nil then - TProcedure(proc)(); - end; + initializeUnits; end; end. diff --git a/src/arch/x86_64/arch.x86_64.boot.pas b/src/arch/x86_64/arch.x86_64.boot.pas new file mode 100644 index 00000000..98d8726c --- /dev/null +++ b/src/arch/x86_64/arch.x86_64.boot.pas @@ -0,0 +1,358 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.x86_64.boot; + +interface + +procedure kmain(info: pointer; magic: uint32); cdecl; + +implementation + +uses + app.mgr, + driver.net, app.wasm.runner, + driver.hid.ps2.keyboard, driver.hid.ps2.mouse, + driver.video.desktop, driver.timer.rtc, svc.gfxd, app.uidebug, + driver.video.lvgl, + driver.video.bga, driver.video, driver.video.gpu, driver.video.doublebuffer, + core.gfx.color, arch.platform.memory, + proc.types, + driver.hid.usb.keyboard, + driver.hid.usb.mouse, + driver.storage.ctl.usb, + driver.bus.usb.types, + driver.bus.usb.core, + driver.bus.usb.hub, + driver.bus.usb.ohci, + driver.storage.metatest, + driver.storage.types, + arch.common.legacyirq, + driver.mgr, + driver.bus.pci, + driver.storage.ctl.ahci, + driver.storage.fs.fat32, + driver.storage.fs.ext2, + driver.storage.vfs, + driver.storage.mgr, + driver.storage.vol.mgr, + driver.storage.fs.iso9660, + io.syslog, + driver.io.serial, + debug.tracer, + core.ds.cfifo, + driver.storage.iorequest, + driver.storage.path, + driver.storage.fs.mgr, + core.ds.lists, + core.ds.hashmap, + core.enc.fnv1a, + io.stdio, + core.strings, + driver.storage.fdtable, + arch.x86_64.proc.sched, + proc.mgr, + arch.x86_64.context, + arch.x86_64.irq, + memory.heap, + debug.early, + arch.x86_64.debug, + arch.x86_64.isr, + boot.multiboot, + arch.x86_64.memory.physical, + arch.x86_64.memory.virtual; + +var + initializationCount: uint32; + +function long_mode_active: boolean; assembler; nostackframe; +asm + mov ecx, $C0000080 + rdmsr + shr eax, 10 + and eax, 1 +end; + +procedure test_high_dma; +var + address: PtrUInt; + frame, oldFrame: uint64; + device: PStorage_Device; + reference: puint8; + i, j, count: uint32; + ok: boolean; +begin + frame := arch.x86_64.memory.physical.alloc_high; + if frame = 0 then begin + puts('SKIP: no high physical frame for DMA test'#13#10); + exit; + end; + address := arch.x86_64.memory.virtual.allocate_region(1); + if address = 0 then begin + arch.x86_64.memory.physical.free_page(frame); + puts('FAIL: DMA test mapping allocation'#13#10); + exit; + end; + if not unmap_page(address, oldFrame) then bootstrap_halt; + arch.x86_64.memory.physical.free_page(oldFrame); + if not map_page(address, frame, true) then bootstrap_halt; + reference := puint8(kalloc(4096)); + ok := reference <> nil; + count := driver.storage.mgr.get_device_count; + if count = 0 then ok := false; + if ok then + for i := 0 to count - 1 do begin + device := driver.storage.mgr.get_device(i); + if (device^.sectorSize = 0) or (device^.sectorSize > 4096) then begin + ok := false; + break; + end; + if (storage_read(device, 0, 1, puint32(reference)) <> eNone) or + (storage_read(device, 0, 1, puint32(address)) <> eNone) then begin + ok := false; + break; + end; + for j := 0 to device^.sectorSize - 1 do + if reference[j] <> puint8(address)[j] then ok := false; + end; + if reference <> nil then kfree(void(reference)); + if not release_region(address, 1) then ok := false; + if ok then + puts('PASS: AHCI DMA above 4 GiB matches every disk sector'#13#10) + else + puts('FAIL: AHCI DMA above 4 GiB'#13#10); +end; + +procedure test_storage_read; +var + handle: TFileHandle; + error: TError; + buffer: puint8; + bytes: uint32; +begin + buffer := puint8(kalloc(4096)); + if buffer = nil then begin + puts('FAIL: storage read buffer allocation'#13#10); + exit; + end; + handle := driver.storage.vfs.OpenFile('/disk/ISOIMAGE/boot/asuro.bin', omRead, @error); + if error = eNone then begin + bytes := driver.storage.vfs.ReadFile(handle, 0, buffer, 4096); + if (bytes = 4096) and (buffer[0] = $7F) and + (buffer[1] = ord('E')) and (buffer[2] = ord('L')) and + (buffer[3] = ord('F')) and (buffer[4] = 2) then + puts('PASS: VFS reads ELF64 kernel through AHCI'#13#10) + else + puts('FAIL: VFS kernel file contents'#13#10); + driver.storage.vfs.CloseFile(handle); + end else + puts('FAIL: VFS opens kernel file'#13#10); + kfree(void(buffer)); +end; + +procedure test_usb_read; +var + device: PStorage_Device; + buffer: puint32; + attempt, i, count: uint32; +begin + device := nil; + for attempt := 0 to 499 do begin + count := driver.storage.mgr.get_device_count; + if count > 0 then + for i := 0 to count - 1 do + if driver.storage.mgr.get_device(i)^.controller = ControllerUSB then + device := driver.storage.mgr.get_device(i); + if device <> nil then break; + proc.mgr.proc_sleep_ms(10); + end; + if device = nil then begin + puts('SKIP: no USB storage device registered'#13#10); + exit; + end; + buffer := puint32(kalloc(device^.sectorSize)); + if buffer = nil then begin + puts('FAIL: USB read buffer allocation'#13#10); + exit; + end; + if storage_read(device, 0, 1, buffer) = eNone then + puts('PASS: USB mass storage reads sector zero through OHCI'#13#10) + else + puts('FAIL: USB mass storage sector read'#13#10); + kfree(void(buffer)); +end; + +procedure init_video(info: Pmultiboot_info_t); +var + framebuffer: pointer; + bytes: SizeUInt; +begin + if (info^.flags and (1 shl 12) = 0) or + (info^.framebuffer_bpp <> 32) or + (info^.framebuffer_width = 0) or (info^.framebuffer_height = 0) or + (info^.framebuffer_pitch < uint64(info^.framebuffer_width) * 4) then begin + puts('FAIL: boot framebuffer geometry'#13#10); + exit; + end; + bytes := SizeUInt(info^.framebuffer_pitch) * info^.framebuffer_height; + framebuffer := arch.platform.memory.mapDevice(info^.framebuffer_addr, bytes); + if framebuffer = nil then begin + puts('FAIL: boot framebuffer mapping'#13#10); + exit; + end; + driver.video.gpu.init; + driver.video.init; + driver.video.doublebuffer.init(@driver.video.register); + driver.video.reinit(PtrUInt(framebuffer), info^.framebuffer_width, + info^.framebuffer_height, info^.framebuffer_pitch, info^.framebuffer_bpp); + driver.video.DrawString(32, 32, 'Asuro x86-64', core.gfx.color.white); + driver.video.Flush; + driver.video.lvgl.lvgl_init(info^.framebuffer_width, info^.framebuffer_height); + driver.video.lvgl.lvgl_handler; + driver.video.Flush; + puts('PASS: native framebuffer and double buffer initialized'#13#10); +end; + +procedure finish_boot(ctx: PProcessContext); +begin + driver.storage.metatest.RunTests(true); + driver.video.bga.init; + driver.net.init; + driver.bus.pci.init; + driver.storage.vfs.auto_mount_volumes; + test_storage_read; + driver.hid.ps2.mouse.init; + driver.hid.ps2.keyboard.init; + driver.hid.usb.keyboard.onAttach := @driver.hid.ps2.keyboard.disable; + driver.bus.usb.core.init; + driver.bus.usb.hub.init; + driver.storage.ctl.usb.init; + driver.hid.usb.keyboard.init; + driver.hid.usb.mouse.init; + driver.bus.usb.ohci.load; + test_usb_read; + test_storage_read; + test_high_dma; + driver.timer.rtc.init; + driver.video.lvgl.onDebugToggle := @app.uidebug.toggle; + driver.video.desktop.init; + app.mgr.init; + app.wasm.runner.UnitTest; + driver.video.lvgl.lvgl_handler; + driver.video.Flush; + {$ifdef ASURO_TEST_UD} + arch.x86_64.isr.testFatal(6); + {$endif} + {$ifdef ASURO_TEST_PF} + arch.x86_64.isr.testFatal(14); + {$endif} + {$ifdef ASURO_TEST_DF} + arch.x86_64.isr.testFatal(8); + {$endif} + svc.gfxd.init; + puts('ASURO x86_64: desktop ready'#13#10); +end; + +procedure kmain(info: pointer; magic: uint32); cdecl; [public, alias: 'kmain']; +var + wide: PtrUInt; +begin + boot.multiboot.multibootinfo := Pmultiboot_info_t(info); + arch.x86_64.debug.init; + arch.x86_64.isr.init; + io.syslog.init; + system.init; + system.init; + if initializationCount <> 1 then begin + puts('FAIL: unit initialization dispatch'#13#10); + bootstrap_halt; + end; + puts('PASS: native unit initialization runs once'#13#10); + puts('ASURO x86_64: entered Pascal'#13#10); + if (magic <> $2BADB002) or (info = nil) then begin + puts('FAIL: Multiboot handoff'#13#10); + bootstrap_halt; + end; + if (sizeof(pointer) <> 8) or (sizeof(PtrUInt) <> 8) or + (sizeof(SizeUInt) <> 8) or (sizeof(uint32) <> 4) then begin + puts('FAIL: native type widths'#13#10); + bootstrap_halt; + end; + wide := PtrUInt($100000000) + PtrUInt(magic); + if (wide shr 32 <> 1) or not long_mode_active then begin + puts('FAIL: long mode or 64-bit arithmetic'#13#10); + bootstrap_halt; + end; + puts('PASS: long mode, Multiboot handoff, 64-bit Pascal types'#13#10); + arch.x86_64.isr.UnitTest; + arch.x86_64.memory.physical.init(Pmultiboot_info_t(info)); + arch.x86_64.memory.physical.UnitTest; + arch.x86_64.memory.virtual.init; + arch.x86_64.memory.virtual.UnitTest; + memory.heap.init; + memory.heap.UnitTest; + driver.bus.usb.types.UnitTest; + driver.bus.usb.core.UnitTest; + driver.bus.usb.hub.UnitTest; + driver.hid.usb.keyboard.UnitTest; + driver.hid.usb.mouse.UnitTest; + driver.bus.usb.ohci.UnitTest; + core.strings.UnitTest; + core.enc.fnv1a.UnitTest; + core.ds.hashmap.UnitTest; + core.ds.lists.TestAllLists; + core.ds.cfifo.UnitTest; + driver.storage.iorequest.UnitTest; + driver.storage.path.UnitTest; + driver.storage.fs.mgr.UnitTest; + driver.storage.fs.mgr.init; + io.stdio.init; + arch.x86_64.irq.init; + arch.x86_64.irq.UnitTest; + arch.x86_64.context.UnitTest; + driver.storage.fdtable.UnitTest; + proc.mgr.init; + arch.x86_64.proc.sched.init; + arch.x86_64.proc.sched.UnitTest; + driver.storage.mgr.init; + driver.storage.vol.mgr.init; + driver.storage.fs.iso9660.init; + driver.storage.fs.ext2.init; + driver.storage.fs.fat32.init; + driver.storage.fs.ext2.StartWorker; + driver.storage.vfs.init; + driver.storage.vfs.auto_mount_volumes; + driver.storage.vfs.UnitTest; + driver.storage.ctl.ahci.UnitTest; + driver.storage.ctl.usb.UnitTest; + init_video(Pmultiboot_info_t(info)); + driver.mgr.init; + driver.storage.ctl.ahci.init; + if proc.mgr.create('bootstrap', @finish_boot, nil, 1) = nil then begin + puts('FAIL: bootstrap process allocation'#13#10); + bootstrap_halt; + end; + arch.common.legacyirq.masks($FE, $FF); + asm + sti + end; + while true do begin + asm + hlt + end; + end; +end; + +initialization + inc(initializationCount); + +end. diff --git a/src/arch/x86_64/arch.x86_64.context.pas b/src/arch/x86_64/arch.x86_64.context.pas new file mode 100644 index 00000000..8b293c90 --- /dev/null +++ b/src/arch/x86_64/arch.x86_64.context.pas @@ -0,0 +1,195 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Architecture context mechanism only. Scheduling policy stays in proc.*. + A saved context points at an aligned 512-byte FX image followed by the + normalized interrupt-frame pointer. Only kernel contexts are supported. } +unit arch.x86_64.context; + +interface +type + PNative = ^PtrUInt; + { Exact order produced by isr/entry.asm. Long mode always pushes SS/RSP, + even without a privilege transition; only the error code is conditional. } + PFrame = ^TFrame; + TFrame = packed record + cr2: uint64; + r15, r14, r13, r12, r11, r10, r9, r8: uint64; + rdi, rsi, rbp, rbx, rdx, rcx, rax: uint64; + vector, error, rip, cs, flags, rsp, ss: uint64; + end; + + TSelectContext = function(saved: PtrUInt): PtrUInt; +procedure setSelector(selector: TSelectContext); +function prepare(stack: pointer; bytes: SizeUInt; entry: pointer; argument: PtrUInt): PtrUInt; +procedure UnitTest; + +implementation +uses proc.types, proc.scheduler, arch.x86_64.util, arch.x86_64.irq, arch.common.legacyirq, memory.heap, debug.early; +var + choose: TSelectContext; + testProcesses: array[0..2] of PProcessContext; + testCurrent: PProcessContext; + testCursor: uint32; + testDone: array[0..1] of uint32; + testFailures: uint32; + testSwitches: uint32; + +procedure setSelector(selector: TSelectContext); +begin + { Caller owns IRQ exclusion and context lifetimes. } + choose := selector; +end; + +function selectContext(saved: PtrUInt): PtrUInt; cdecl; [public, alias: 'x64_select_context']; +var + frame: PFrame; +begin + selectContext := saved; + frame := PFrame(PNative(saved + 512)^); + if (frame^.vector = 32) and assigned(choose) then + selectContext := choose(saved); +end; + +function prepare(stack: pointer; bytes: SizeUInt; entry: pointer; argument: PtrUInt): PtrUInt; +var + top, frameAddress, fx: PtrUInt; + frame: PFrame; +begin + prepare := 0; + if (stack = nil) or (entry = nil) or (bytes < 2048) or + (PtrUInt(stack) > High(PtrUInt) - bytes) then exit; + top := (PtrUInt(stack) + bytes) and not PtrUInt(15); + { SysV entry has RSP mod 16 = 8, as if CALL pushed a return address. } + top := top - 8; + PNative(top)^ := PtrUInt(@bootstrap_halt); + frameAddress := top - sizeof(TFrame); + fx := (frameAddress - 528) and not PtrUInt(15); + zero(pointer(fx), top - fx); + frame := PFrame(frameAddress); + frame^.rdi := argument; + frame^.rip := PtrUInt(entry); + frame^.cs := $18; + frame^.ss := $10; + frame^.flags := $202; + frame^.rsp := top; + PUInt16(fx)^ := $037F; { initial x87 control word; empty tag word } + PUInt32(fx + 24)^ := $1F80; { initial MXCSR } + PNative(fx + 512)^ := frameAddress; + prepare := fx; +end; + +function testGetProcess(index: uint32): PProcessContext; +begin + if index < 3 then testGetProcess := testProcesses[index] else testGetProcess := nil; +end; + +function testSelect(saved: PtrUInt): PtrUInt; +begin + testCurrent^.SavedStack := saved; + testCurrent := proc.scheduler.pickNext(testCurrent, testProcesses[0], 3, + testCursor, @testGetProcess); + inc(testSwitches); + testSelect := testCurrent^.SavedStack; +end; + +function testRegisters(counter: PUInt32; pattern: uint64): uint32; cdecl; external name 'x64_test_context_registers'; + +procedure worker(index: PtrUInt); cdecl; +var + value: PtrUInt; + allocation: void; +begin + value := PtrUInt($1234567800000000) + index; + allocation := memory.heap.kalloc(17 + index); + PUInt64(allocation)^ := value; + if testRegisters(@testSwitches, value) <> 1 then inc(testFailures); + if (PUInt64(allocation)^ <> value) or + (value <> PtrUInt($1234567800000000) + index) then inc(testFailures); + memory.heap.kfree(allocation); + asm + cli + end; + testProcesses[index + 1]^.State := psFinished; + PUInt32(@testDone[index])^ := 1; + asm + sti + end; + { The next timer moves away permanently; stack is freed by bootstrap. } + while true do asm + pause + end; +end; + +procedure UnitTest; +var + stackA, stackB: void; + beforeFree: SizeUInt; + attempts, i: uint32; + saved: PtrUInt; +begin + beforeFree := lmm_total_free; + stackA := kalloc(16384); + stackB := kalloc(16384); + for i := 0 to 2 do begin + testProcesses[i] := PProcessContext(kalloc(sizeof(TProcessContext))); + testProcesses[i]^.ProcessID := i; + testProcesses[i]^.Quantum := 1; + testProcesses[i]^.State := psReady; + end; + testProcesses[1]^.SavedStack := prepare(pointer(stackA), 16384, @worker, 0); + testProcesses[2]^.SavedStack := prepare(pointer(stackB), 16384, @worker, 1); + if (testProcesses[1]^.SavedStack = 0) or (testProcesses[2]^.SavedStack = 0) then begin + puts('FAIL: prepare kernel contexts'#13#10); + bootstrap_halt; + end; + testCurrent := testProcesses[0]; + testCurrent^.State := psRunning; + testCursor := 0; + testDone[0] := 0; + testDone[1] := 0; + testFailures := 0; + testSwitches := 0; + setSelector(@testSelect); + masks($FE, $FF); + asm + sti + end; + attempts := 0; + while ((PUInt32(@testDone[0])^ = 0) or (PUInt32(@testDone[1])^ = 0)) and + (attempts < 10000000) do begin + inc(attempts); + asm + pause + end; + end; + saved := lockIRQ; + masks($FF, $FF); + setSelector(nil); + if (testDone[0] = 0) or (testDone[1] = 0) or (testSwitches < 6) or + ((saved and $200) = 0) then inc(testFailures); + kfree(stackA); + kfree(stackB); + for i := 0 to 2 do begin + kfree(void(testProcesses[i])); + testProcesses[i] := nil; + end; + if lmm_total_free <> beforeFree then inc(testFailures); + field('CONTEXT_SWITCHES', testSwitches); + field('CONTEXT_TESTS_FAILED', testFailures); + if testFailures <> 0 then begin + puts('FAIL: preemptive kernel contexts'#13#10); + bootstrap_halt; + end; + puts('PASS: two kernel contexts preserve registers/x87/SSE, allocate and release stacks'#13#10); +end; +end. diff --git a/src/arch/x86_64/arch.x86_64.debug.pas b/src/arch/x86_64/arch.x86_64.debug.pas new file mode 100644 index 00000000..2179a588 --- /dev/null +++ b/src/arch/x86_64/arch.x86_64.debug.pas @@ -0,0 +1,41 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.x86_64.debug; + +interface +procedure init; + +implementation +uses debug.early; +{ The stub initializes COM1. Bounded polling also allows boot without a UART. } +procedure putchar(ch: char); assembler; nostackframe; +asm + mov ecx, 65536 + mov dx, $3FD +@wait: + in al, dx + test al, $20 + jnz @ready + loop @wait + ret +@ready: + mov eax, edi + mov dx, $3F8 + out dx, al +end; + +procedure init; +begin + debug.early.setWriter(@putchar); +end; +end. diff --git a/src/arch/x86_64/arch.x86_64.irq.pas b/src/arch/x86_64/arch.x86_64.irq.pas new file mode 100644 index 00000000..5e9c1371 --- /dev/null +++ b/src/arch/x86_64/arch.x86_64.irq.pas @@ -0,0 +1,207 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.x86_64.irq; + +interface +type + TIRQHandler = procedure; + TTimerHandler = procedure(data: void); +function registerIRQ(vector: uint8; handler: TIRQHandler): boolean; +function registerTimer(handler: TTimerHandler): boolean; +procedure init; +procedure dispatch(vector: uint32); +procedure UnitTest; +function ticks: uint64; + +implementation +uses arch.common.legacyirq, arch.x86_64.util, debug.early, memory.heap; +type + PIRQHook = ^TIRQHook; + TIRQHook = record + Next: PIRQHook; + Handler: TIRQHandler; + end; + PTimerHook = ^TTimerHook; + TTimerHook = record + Next: PTimerHook; + Handler: TTimerHandler; + end; +var + hooks: array[0..15] of PIRQHook; + timerHooks: PTimerHook; + timerTicks, unexpected, spurious: uint64; + ready: boolean; + +function registerIRQ(vector: uint8; handler: TIRQHandler): boolean; +var + node, current: PIRQHook; + flags: PtrUInt; +begin + registerIRQ := false; + if (vector < 32) or (vector > 47) or (handler = nil) then exit; + flags := lockIRQ; + current := hooks[vector - 32]; + while current <> nil do begin + if current^.Handler = handler then begin + unlockIRQ(flags); + registerIRQ := true; + exit; + end; + current := current^.Next; + end; + node := PIRQHook(kalloc(sizeof(TIRQHook))); + if node <> nil then begin + node^.Handler := handler; + node^.Next := hooks[vector - 32]; + hooks[vector - 32] := node; + registerIRQ := true; + end; + unlockIRQ(flags); +end; + +function registerTimer(handler: TTimerHandler): boolean; +var + node, current: PTimerHook; + flags: PtrUInt; +begin + registerTimer := false; + if handler = nil then exit; + flags := lockIRQ; + current := timerHooks; + while current <> nil do begin + if current^.Handler = handler then begin + unlockIRQ(flags); + registerTimer := true; + exit; + end; + current := current^.Next; + end; + node := PTimerHook(kalloc(sizeof(TTimerHook))); + if node <> nil then begin + node^.Handler := handler; + node^.Next := timerHooks; + timerHooks := node; + registerTimer := true; + end; + unlockIRQ(flags); +end; + +procedure init; +begin + asm + cli + end; + remap($FF, $FF); + timerTicks := 0; + unexpected := 0; + spurious := 0; + timerStart; + ready := true; +end; + +function ticks: uint64; +begin + ticks := PUInt64(@timerTicks)^; +end; + +procedure dispatch(vector: uint32); +var + line: uint8; + hook: PIRQHook; + timer: PTimerHook; +begin + if (not ready) or (vector < 32) or (vector > 47) then begin + puts('FAIL: IRQ dispatched before controller setup'#13#10); + bootstrap_halt; + end; + line := vector - 32; + if not realIRQ(line) then begin + inc(spurious); + exit; + end; + if line = 0 then begin + inc(timerTicks); + timer := timerHooks; + while timer <> nil do begin + timer^.Handler(nil); + timer := timer^.Next; + end; + end else if hooks[line] = nil then + inc(unexpected); + hook := hooks[line]; + while hook <> nil do begin + hook^.Handler; + hook := hook^.Next; + end; + acknowledge(line); +end; + +procedure UnitTest; +var + attempts, failed: uint32; + saved, beforeTicks: uint64; + allocation: void; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then puts('PASS: ') else begin + inc(failed); + puts('FAIL: '); + end; + puts(name); + puts(#13#10); + end; +begin + failed := 0; + { Synthetic spurious vectors must return without delivering device work. } + asm + int $27; + int $2F + end; + Assert((spurious = 2) and (unexpected = 0), 'PIC spurious IRQ7/IRQ15 filtered'); + masks($FE, $FF); { only IRQ0; no storage or keyboard IRQs yet } + beforeTicks := ticks; + asm + sti + end; + attempts := 0; + { Bounded polling, not HLT: a missing timer must report failure. } + while (ticks - beforeTicks < 16) and (attempts < 10000000) do begin + asm + pause + end; + inc(attempts); + end; + saved := lockIRQ; + Assert((saved and $200) <> 0, 'IRQ return restores interrupt enable'); + Assert(ticks - beforeTicks >= 16, 'PIT delivers repeated hardware interrupts'); + beforeTicks := ticks; + for attempts := 1 to 10000 do asm + pause + end; + Assert(ticks = beforeTicks, 'CLI excludes timer interrupts'); + unlockIRQ(saved); + allocation := memory.heap.kalloc(17); + saved := lockIRQ; + Assert((saved and $200) <> 0, 'heap allocation preserves enabled interrupts'); + unlockIRQ(saved); + memory.heap.kfree(allocation); + saved := lockIRQ; + Assert((saved and $200) <> 0, 'heap release preserves enabled interrupts'); + masks($FF, $FF); + Assert(unexpected = 0, 'no unexpected hardware IRQs'); + field('IRQ_TIMER_TICKS', ticks); + field('IRQ_TESTS_FAILED', failed); + if failed <> 0 then bootstrap_halt; +end; +end. diff --git a/src/arch/x86_64/arch.x86_64.isr.pas b/src/arch/x86_64/arch.x86_64.isr.pas new file mode 100644 index 00000000..c9df54ae --- /dev/null +++ b/src/arch/x86_64/arch.x86_64.isr.pas @@ -0,0 +1,249 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.x86_64.isr; + +interface + +procedure init; +procedure UnitTest; +procedure testFatal(vector: uint32); + +implementation + +uses debug.early, arch.x86_64.irq, arch.x86_64.context; + +type + TGate = packed record + offsetLow, selector: uint16; + ist, attributes: uint8; + offsetMiddle: uint16; + offsetHigh, reserved: uint32; + end; + TDescriptorPointer = packed record + limit: uint16; + base: uint64; + end; + TTSS = packed record + reserved0: uint32; + rsp0, rsp1, rsp2, reserved1: uint64; + ist: array[0..6] of uint64; + reserved2: uint64; + reserved3, ioMapBase: uint16; + end; + TTSSDescriptor = packed record + limitLow, baseLow: uint16; + baseMiddle, access, flags, baseHigh: uint8; + baseUpper, reserved: uint32; + end; + +var + { Hardware tables must exist before the heap. Fixed bounded reservations, + not per-fault allocations; GRUB clears their BSS storage. } + gates: array[0..255] of TGate; + idtr: TDescriptorPointer; + tss: TTSS; + tssDescriptor: TTSSDescriptor; external name 'bootstrap_tss_descriptor'; + entryTable: array[0..255] of uint64; external name 'x64_isr_table'; + kernelStackTop: uint8; external name 'stack_top'; + dfBottom: uint8; external name 'x64_df_stack_bottom'; + dfTop: uint8; external name 'x64_df_stack_top'; + nmiTop: uint8; external name 'x64_nmi_stack_top'; + mcTop: uint8; external name 'x64_mc_stack_top'; + breakpointReturn: uint8; external name 'x64_test_breakpoint_return'; + expectedRSP: uint64; external name 'x64_test_expected_rsp'; + pageFaultInstruction: uint8; external name 'x64_test_page_fault_instruction'; + pageFaultReturn: uint8; external name 'x64_test_page_fault_return'; + expectedTest: uint32; + breakpointValid, pageFaultValid, fatalActive: boolean; + +function testBreakpoint: uint32; cdecl; external name 'x64_test_breakpoint'; + +procedure testPageFault; cdecl; external name 'x64_test_page_fault'; + +procedure testInvalidOpcode; cdecl; external name 'x64_test_invalid_opcode'; + +procedure testDoubleFault; cdecl; external name 'x64_test_double_fault'; + +procedure loadTables(descriptor: pointer); assembler; nostackframe; +asm + mov ax, $20 + ltr ax + lidt [rdi] +end; + +procedure init; +var + i: uint32; + address: uint64; +begin + if (sizeof(TGate) <> 16) or (sizeof(TDescriptorPointer) <> 10) or + (sizeof(TTSS) <> 104) or (sizeof(TTSSDescriptor) <> 16) or + (sizeof(TFrame) <> 184) then begin + puts('FAIL: x86_64 hardware table layout'#13#10); + bootstrap_halt; + end; + tss.rsp0 := PtrUInt(@kernelStackTop); + tss.ist[0] := PtrUInt(@dfTop); + tss.ist[1] := PtrUInt(@nmiTop); + tss.ist[2] := PtrUInt(@mcTop); + tss.ioMapBase := sizeof(TTSS); { beyond limit: no I/O permission bitmap } + address := PtrUInt(@tss); + tssDescriptor.limitLow := sizeof(TTSS) - 1; + tssDescriptor.baseLow := uint16(address); + tssDescriptor.baseMiddle := uint8(address shr 16); + tssDescriptor.access := $89; { present, DPL0, available 64-bit TSS } + tssDescriptor.flags := 0; + tssDescriptor.baseHigh := uint8(address shr 24); + tssDescriptor.baseUpper := uint32(address shr 32); + tssDescriptor.reserved := 0; + for i := 0 to 255 do begin + address := entryTable[i]; + gates[i].offsetLow := uint16(address); + gates[i].selector := $18; + gates[i].ist := 0; + gates[i].attributes := $8E; { present DPL0 interrupt gate, clears IF } + gates[i].offsetMiddle := uint16(address shr 16); + gates[i].offsetHigh := uint32(address shr 32); + gates[i].reserved := 0; + end; + gates[8].ist := 1; + gates[2].ist := 2; + gates[18].ist := 3; + idtr.limit := sizeof(gates) - 1; + idtr.base := PtrUInt(@gates); + loadTables(@idtr); + puts('ASURO x86_64: IDT/TSS ready; DF/NMI/MC emergency stacks installed'#13#10); +end; + +procedure dispatch(frame: PFrame); cdecl; [public, alias: 'x64_exception_dispatch']; +const + names: array[0..14] of pchar = ( + 'R15', 'R14', 'R13', 'R12', 'R11', 'R10', 'R9', 'R8', + 'RDI', 'RSI', 'RBP', 'RBX', 'RDX', 'RCX', 'RAX'); +var + i: uint32; + registers: PUInt64; +begin + if (frame^.vector >= 32) and (frame^.vector <= 47) then begin + arch.x86_64.irq.dispatch(frame^.vector); + exit; + end; + registers := @frame^.r15; + if (expectedTest = 1) and (frame^.vector = 3) and + (frame^.rip = PtrUInt(@breakpointReturn)) then begin + breakpointValid := (frame^.error = 0) and (frame^.cs = $18) and + (frame^.ss = $10) and (frame^.rsp = expectedRSP) and + ((frame^.flags and $401) = $401); + for i := 0 to 14 do + if registers[i] <> $1122334455667788 then breakpointValid := false; + { Deliberately destroy FX state inside the handler: the assembly return + path must restore it, not merely rely on Pascal avoiding SIMD. } + asm + fninit + pxor xmm0, xmm0 + pxor xmm15, xmm15 + end; + exit; + end; + if (expectedTest = 2) and (frame^.vector = 14) and + (frame^.rip = PtrUInt(@pageFaultInstruction)) then begin + pageFaultValid := (frame^.error = 2) and (frame^.cr2 = $100000000) and + (frame^.cs = $18) and (frame^.ss = $10); + frame^.rip := PtrUInt(@pageFaultReturn); { recover only this exact probe } + exit; + end; + + if fatalActive then begin + puts('FATAL: nested exception while reporting'#13#10); + field('VECTOR', frame^.vector); + bootstrap_halt; + end; + fatalActive := true; + puts('FATAL x86_64 exception: '); + case frame^.vector of + 2: puts('NMI'); + 6: puts('invalid opcode'); + 8: puts('double fault'); + 13: puts('general protection'); + 14: puts('page fault'); + 18: puts('machine check'); + else + puts('unexpected vector'); + end; + puts(#13#10); + field('VECTOR', frame^.vector); + field('ERROR', frame^.error); + field('RIP', frame^.rip); + field('CS', frame^.cs); + field('RFLAGS', frame^.flags); + field('RSP', frame^.rsp); + field('SS', frame^.ss); + field('CR2', frame^.cr2); + field('FRAME', PtrUInt(frame)); + if (PtrUInt(frame) >= PtrUInt(@dfBottom)) and + (PtrUInt(frame) + sizeof(TFrame) <= PtrUInt(@dfTop)) then + puts('STACK=double-fault IST1'#13#10); + for i := 0 to 14 do field(names[i], registers[i]); + puts('HALTED: exception diagnostics complete'#13#10); + bootstrap_halt; +end; + +procedure UnitTest; +var + passed, failed: uint32; + result: uint32; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then begin + inc(passed); + puts('PASS: '); + end else begin + inc(failed); + puts('FAIL: '); + end; + puts(name); + puts(#13#10); + end; +begin + passed := 0; + failed := 0; + breakpointValid := false; + expectedTest := 1; + result := testBreakpoint; + expectedTest := 0; + Assert(breakpointValid and (result = 1), + 'INT3 frame, GPRs, RFLAGS, x87/SSE and IRETQ'); + pageFaultValid := false; + expectedTest := 2; + testPageFault; + expectedTest := 0; + Assert(pageFaultValid, 'page-fault error code, CR2 and IRETQ recovery'); + field('EXCEPTION_TESTS_PASSED', passed); + field('EXCEPTION_TESTS_FAILED', failed); + if failed <> 0 then bootstrap_halt; +end; + +procedure testFatal(vector: uint32); +begin + puts('TEST: deliberate fatal exception'#13#10); + case vector of + 6: testInvalidOpcode; + 14: testPageFault; + 8: testDoubleFault; + end; + puts('FAIL: fatal exception returned'#13#10); + bootstrap_halt; +end; + +end. diff --git a/src/arch/x86_64/arch.x86_64.util.pas b/src/arch/x86_64/arch.x86_64.util.pas new file mode 100644 index 00000000..5de3b246 --- /dev/null +++ b/src/arch/x86_64/arch.x86_64.util.pas @@ -0,0 +1,76 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.x86_64.util; + +interface +function lockIRQ: PtrUInt; +procedure unlockIRQ(flags: PtrUInt); +procedure zero(dest: pointer; bytes: SizeUInt); +procedure invalidate(address: PtrUInt); +function pageRoot: uint64; +procedure outb(port: uint16; value: uint8); +function inb(port: uint16): uint8; + +implementation + +function lockIRQ: PtrUInt; assembler; nostackframe; +asm + pushfq + cli + pop rax +end; + +procedure unlockIRQ(flags: PtrUInt); assembler; nostackframe; +asm + test rdi, $200 + jz @done + sti +@done: +end; + +procedure zero(dest: pointer; bytes: SizeUInt); assembler; nostackframe; +asm + mov rcx, rsi + shr rcx, 3 + xor eax, eax + cld + rep stosq + mov rcx, rsi + and rcx, 7 + rep stosb +end; + +procedure invalidate(address: PtrUInt); assembler; nostackframe; +asm + invlpg [rdi] +end; + +function pageRoot: uint64; assembler; nostackframe; +asm + mov rax, cr3 +end; + +procedure outb(port: uint16; value: uint8); assembler; nostackframe; +asm + mov edx, edi + mov eax, esi + out dx, al +end; + +function inb(port: uint16): uint8; assembler; nostackframe; +asm + mov edx, edi + xor eax, eax + in al, dx +end; +end. diff --git a/src/arch/x86_64/boot/stub.asm b/src/arch/x86_64/boot/stub.asm new file mode 100644 index 00000000..a617984c --- /dev/null +++ b/src/arch/x86_64/boot/stub.asm @@ -0,0 +1,210 @@ +; Copyright 2026 Aaron Hance +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; http://www.apache.org/licenses/LICENSE-2.0 +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. + +bits 32 +global loader +global bootstrap_halt +global bootstrap_tss_descriptor +global stack_top +extern kmain +extern load_end +extern kernel_end + +; Explicit Multiboot v1 load addresses allow GRUB to load this ELF64 image +; while still handing off in 32-bit protected mode. All bootstrap addresses +; are below 4 GiB. The linker keeps this header first in the load image. +section .multiboot align=4 +header: + dd 0x1BADB002 + dd 0x00010007 + dd -(0x1BADB002 + 0x00010007) + dd header + dd header + dd load_end + dd kernel_end + dd loader + dd 0, 1024, 768, 32 + +section .text +loader: + cli + cld + ; Own flat descriptors before relying on the bootloader's data segments. + lgdt [gdt_pointer] + jmp 0x08:protected_entry +protected_entry: + mov dx, 0x10 + mov ds, dx + mov es, dx + mov ss, dx + mov esp, stack_top + mov esi, eax ; preserve Multiboot magic across CPUID + mov ebp, ebx ; physical Multiboot info pointer + call serial_init + cmp esi, 0x2BADB002 + jne fail_boot + + ; CPUID availability: toggle EFLAGS.ID and restore the original flags. + pushfd + pop eax + mov ecx, eax + xor eax, 1 << 21 + push eax + popfd + pushfd + pop eax + push ecx + popfd + xor eax, ecx + test eax, 1 << 21 + jz fail_cpu + mov eax, 0x80000000 + cpuid + cmp eax, 0x80000001 + jb fail_cpu + mov eax, 0x80000001 + cpuid + test edx, 1 << 29 + jz fail_cpu + + ; Reserve six bootstrap pages: PML4, PDPT, and four page directories. + ; Identity-map the first 4 GiB using 2 MiB leaves, including boot data. + mov edi, pml4 + xor eax, eax + mov ecx, (6 * 4096) / 4 + rep stosd + mov dword [pml4], pdpt + 3 + mov edi, pdpt + mov eax, page_directories + 3 + mov ecx, 4 +.pdpt: + mov [edi], eax + add edi, 8 + add eax, 4096 + loop .pdpt + mov edi, page_directories + mov eax, 0x83 + mov ecx, 2048 +.pages: + mov [edi], eax + add edi, 8 + add eax, 0x200000 + loop .pages + + ; Multiboot enters with paging off. PAE must precede activation of LME. + mov eax, cr4 + or eax, (1 << 5) | (1 << 9) | (1 << 10) + mov cr4, eax + mov eax, pml4 + mov cr3, eax + mov ecx, 0xC0000080 + rdmsr + or eax, 1 << 8 + wrmsr + mov eax, cr0 + and eax, ~((1 << 2) | (1 << 3)) ; clear EM/TS for compiler SSE2 use + or eax, (1 << 31) | (1 << 1) + mov cr0, eax + jmp 0x18:long_entry + +fail_boot: + mov bl, 'B' + jmp fail +fail_cpu: + mov bl, 'C' +fail: + mov ecx, 65536 + mov dx, 0x3FD +.wait: + in al, dx + test al, 0x20 + jnz .write + loop .wait + jmp .halt +.write: + mov dx, 0x3F8 + mov al, bl + out dx, al +.halt: + cli + hlt + jmp .halt + +serial_init: + mov dx, 0x3F9 + xor al, al + out dx, al + mov dx, 0x3FB + mov al, 0x80 + out dx, al + mov dx, 0x3F8 + mov al, 1 + out dx, al + mov dx, 0x3F9 + xor al, al + out dx, al + mov dx, 0x3FB + mov al, 3 + out dx, al + mov dx, 0x3FA + mov al, 0xC7 + out dx, al + mov dx, 0x3FC + mov al, 0x0B + out dx, al + ret + +bits 64 +long_entry: + mov ax, 0x10 + mov ds, ax + mov es, ax + mov ss, ax + xor eax, eax + mov fs, ax + mov gs, ax + mov rsp, stack_top + mov edi, ebp ; SysV AMD64: RDI=info, RSI=magic + xor ebp, ebp + fninit + ldmxcsr [rel initial_mxcsr] + call kmain +bootstrap_halt: + cli + hlt + jmp bootstrap_halt + +section .data align=8 +gdt: + dq 0 + dq 0x00CF9A000000FFFF ; 0x08: 32-bit flat code + dq 0x00CF92000000FFFF ; 0x10: flat data + dq 0x00AF9A000000FFFF ; 0x18: 64-bit code, L=1 D=0 +bootstrap_tss_descriptor: + dq 0, 0 ; 0x20: 16-byte TSS descriptor filled before LTR +gdt_end: +gdt_pointer: + dw gdt_end - gdt - 1 + dd gdt +section .rodata align=4 +initial_mxcsr: + dd 0x1F80 + +section .bss align=4096 +; Early boot has no allocator. These bounded reservations are owned by the +; bootstrap and must remain reserved when the physical allocator is introduced. +pml4: resb 4096 +pdpt: resb 4096 +page_directories: resb 4 * 4096 +align 16 +stack_bottom: resb 16384 +stack_top: diff --git a/src/arch/x86_64/isr/entry.asm b/src/arch/x86_64/isr/entry.asm new file mode 100644 index 00000000..b9ef362b --- /dev/null +++ b/src/arch/x86_64/isr/entry.asm @@ -0,0 +1,276 @@ +; Copyright 2026 Aaron Hance +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; http://www.apache.org/licenses/LICENSE-2.0 +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. + +bits 64 +extern x64_exception_dispatch +extern x64_select_context +extern bootstrap_halt + +global x64_isr_table +global x64_test_breakpoint +global x64_test_breakpoint_return +global x64_test_expected_rsp +global x64_test_page_fault +global x64_test_page_fault_instruction +global x64_test_page_fault_return +global x64_test_double_fault +global x64_test_invalid_opcode +global x64_df_stack_bottom, x64_df_stack_top +global x64_nmi_stack_top, x64_mc_stack_top + +section .text +; Normalize hardware error-code and no-error-code frames. Never use software +; INT to invoke vectors whose CPU-generated exceptions push an error code. +%assign vector 0 +%rep 256 +isr_%+vector: +%if vector != 8 && vector != 10 && vector != 11 && vector != 12 && vector != 13 && vector != 14 && vector != 17 && vector != 21 && vector != 29 && vector != 30 + push qword 0 +%endif + push qword vector + jmp common_entry +%assign vector vector+1 +%endrep + +common_entry: + push rax + push rcx + push rdx + push rbx + push rbp + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + mov rax, cr2 + push rax + ; All interrupted registers are saved before changing DF or calling Pascal. + cld + mov rbx, rsp + sub rsp, 528+15 + and rsp, -16 + fxsave64 [rsp] + mov [rsp+512], rbx + mov r12, rsp + mov rdi, rbx + call x64_exception_dispatch + mov rdi, r12 + call x64_select_context + fxrstor64 [rax] + mov rsp, [rax+512] + add rsp, 8 ; captured CR2 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + add rsp, 16 ; normalized vector and error code + iretq + +; Test the real entry/return path, including nonvolatile registers, high halves, +; DF/CF, x87, and XMM0/XMM15. Preserve the caller's original FX state as well. +x64_test_breakpoint: + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + sub rsp, 536 ; aligned 512-byte FX area + scratch + fxsave64 [rsp] + mov [rel x64_test_expected_rsp], rsp + fninit + fld1 + pcmpeqd xmm0, xmm0 + pcmpeqd xmm15, xmm15 +%macro SEED 1 + mov %1, 0x1122334455667788 +%endmacro + SEED rax + SEED rcx + SEED rdx + SEED rbx + SEED rbp + SEED rsi + SEED rdi + SEED r8 + SEED r9 + SEED r10 + SEED r11 + SEED r12 + SEED r13 + SEED r14 + SEED r15 + std + stc + int3 +x64_test_breakpoint_return: + pushfq + pop qword [rsp+512] + cld +%macro CHECK 1 + cmp %1, [rel expected_gpr] + jne .failed +%endmacro + CHECK rax + CHECK rcx + CHECK rdx + CHECK rbx + CHECK rbp + CHECK rsi + CHECK rdi + CHECK r8 + CHECK r9 + CHECK r10 + CHECK r11 + CHECK r12 + CHECK r13 + CHECK r14 + CHECK r15 + mov eax, [rsp+512] + and eax, 0x401 ; CF and DF must both survive IRETQ + cmp eax, 0x401 + jne .failed + pmovmskb eax, xmm0 + cmp eax, 0xFFFF + jne .failed + pmovmskb eax, xmm15 + cmp eax, 0xFFFF + jne .failed + fistp qword [rsp+512] + cmp qword [rsp+512], 1 + jne .failed + mov eax, 1 + jmp .done +.failed: + xor eax, eax +.done: + fxrstor64 [rsp] + add rsp, 536 + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + ret + +x64_test_page_fault: + mov rax, 0x100000000 ; canonical, just beyond the bootstrap mapping +x64_test_page_fault_instruction: + mov [rax], rax ; write fault: nonzero hardware error code (2) +x64_test_page_fault_return: + ret + +x64_test_invalid_opcode: + ud2 + jmp bootstrap_halt + +x64_test_double_fault: + ; Both ordinary #UD and #PF delivery need this unmapped stack. #DF must + ; switch to IST1 and report the original broken RSP without touching it. + mov rsp, 0x100001000 + ud2 + jmp bootstrap_halt + +section .rodata align=8 +expected_gpr: dq 0x1122334455667788 +x64_isr_table: +%assign vector 0 +%rep 256 + dq isr_%+vector +%assign vector vector+1 +%endrep + +section .bss align=16 +x64_test_expected_rsp: resq 1 +alignb 16 +; Bounded bootstrap reservations, before an allocator exists. Keep these +; reserved in the future allocator. NMI/MC cannot overwrite the #DF stack. +x64_df_stack_bottom: resb 16384 +x64_df_stack_top: + resb 8192 +x64_nmi_stack_top: + resb 8192 +x64_mc_stack_top: + +section .text +global x64_test_context_registers +; RDI: monotonically increasing switch counter; RSI: distinct 64-bit pattern. +; Preserve the caller's ABI/FX state while checking real timer preemption. +x64_test_context_registers: + push rbp + mov rbp, rsp + push r12 + push r13 + push r14 + sub rsp, 560 + and rsp, -16 + fxsave64 [rsp] + mov [rsp+520], rsi + mov r12, rsi + mov r13, rdi + mov r14d, [rdi] + add r14d, 9 + movq xmm15, rsi + punpcklqdq xmm15, xmm15 + mov eax, esi + inc eax + mov [rsp+512], eax + fninit + fild dword [rsp+512] +.wait: + pause + cmp [r13], r14d + jb .wait + xor eax, eax + cmp r12, [rsp+520] + jne .done + cmp rsi, [rsp+520] + jne .done + movq rdx, xmm15 + cmp rdx, [rsp+520] + jne .done + movdqa xmm0, xmm15 + psrldq xmm0, 8 + movq rdx, xmm0 + cmp rdx, [rsp+520] + jne .done + fistp dword [rsp+516] + mov edx, [rsp+512] + cmp edx, [rsp+516] + jne .done + mov eax, 1 +.done: + fxrstor64 [rsp] + lea rsp, [rbp-24] + pop r14 + pop r13 + pop r12 + pop rbp + ret diff --git a/src/arch/x86_64/memory/arch.x86_64.memory.physical.pas b/src/arch/x86_64/memory/arch.x86_64.memory.physical.pas new file mode 100644 index 00000000..925bb82e --- /dev/null +++ b/src/arch/x86_64/memory/arch.x86_64.memory.physical.pas @@ -0,0 +1,361 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ 4 KiB physical frames. Metadata is carved once from verified low RAM before + the heap exists. Non-available memory-map entries always override available + entries. Only the BSP is active; IRQ exclusion is not an SMP lock. } +unit arch.x86_64.memory.physical; + +interface +uses boot.multiboot; +const + FRAME_SIZE = 4096; + MAX_MANAGED = uint64(64) shl 30; +procedure init(info: Pmultiboot_info_t); +function alloc_page(lowOnly: boolean): uint64; +function alloc_high: uint64; +function free_page(address: uint64): boolean; +function reserve_range(address, bytes: uint64): boolean; +function free_count: uint64; +function total_count: uint64; +procedure UnitTest; + +implementation +uses arch.x86_64.util, debug.early; +var + eligible, used: PUInt32; + frameCount, wordCount, hint: uint32; + freeFrames, totalFrames: uint64; + reservedEnd: uint64; + kernelEnd: uint8; external name 'kernel_end'; + mapStart, mapEnd: PtrUInt; + +procedure fail(message: pchar); +begin + puts('FAIL: PMM '); + puts(message); + puts(#13#10); + bootstrap_halt; +end; + +function regionEnd(base, bytes: uint64): uint64; +begin + if (base >= MAX_MANAGED) or (bytes > MAX_MANAGED - base) then + regionEnd := MAX_MANAGED + else regionEnd := base + bytes; +end; + +function nextEntry(entry: Pmemory_map_t): Pmemory_map_t; +begin + nextEntry := Pmemory_map_t(PtrUInt(entry) + entry^.size + 4); +end; + +procedure preserve(address, bytes: uint64); +var + finish: uint64; +begin + if (address >= $100000000) or (bytes > $100000000 - address) then + fail('boot data outside initial mapping'); + finish := address + bytes; + if finish > reservedEnd then reservedEnd := finish; +end; + +procedure setAvailable(base, bytes: uint64; available: boolean); +var + first, last, i, mask: uint32; + finish: uint64; +begin + finish := regionEnd(base, bytes); + if (base >= finish) or (base >= uint64(frameCount) * FRAME_SIZE) then exit; + if available then begin + first := (base + FRAME_SIZE - 1) div FRAME_SIZE; + last := finish div FRAME_SIZE; + end else begin + first := base div FRAME_SIZE; + last := (finish + FRAME_SIZE - 1) div FRAME_SIZE; + end; + if last > frameCount then last := frameCount; + if first >= last then exit; + for i := first to last - 1 do begin + mask := uint32(1) shl (i and 31); + if available then begin + if (eligible[i shr 5] and mask) = 0 then begin + eligible[i shr 5] := eligible[i shr 5] or mask; + inc(freeFrames); + end; + end else if (eligible[i shr 5] and mask) <> 0 then begin + eligible[i shr 5] := eligible[i shr 5] and not mask; + dec(freeFrames); + end; + end; +end; + +function metadataAddress(bytes: uint64): uint64; +var + e, other: Pmemory_map_t; + candidate, finish, nextCandidate: uint64; +begin + metadataAddress := 0; + e := Pmemory_map_t(mapStart); + while PtrUInt(e) < mapEnd do begin + if (e^.mtype = 1) and (e^.base_addr < $100000000) and (e^.length <> 0) then begin + candidate := (e^.base_addr + FRAME_SIZE - 1) and not uint64(FRAME_SIZE - 1); + if candidate < reservedEnd then candidate := reservedEnd; + finish := regionEnd(e^.base_addr, e^.length); + if finish > $100000000 then finish := $100000000; + while (candidate < finish) and (bytes <= finish - candidate) do begin + nextCandidate := candidate; + other := Pmemory_map_t(mapStart); + while PtrUInt(other) < mapEnd do begin + if (other^.mtype <> 1) and (other^.base_addr < candidate + bytes) and + (regionEnd(other^.base_addr, other^.length) > candidate) then begin + nextCandidate := (regionEnd(other^.base_addr, other^.length) + FRAME_SIZE - 1) and not uint64(FRAME_SIZE - 1); + break; + end; + other := nextEntry(other); + end; + if nextCandidate = candidate then begin + metadataAddress := candidate; + exit; + end; + candidate := nextCandidate; { strictly advances across a reserved interval } + end; + end; + e := nextEntry(e); + end; +end; + +procedure init(info: Pmultiboot_info_t); +var + e: Pmemory_map_t; + limit, bytes, storage: uint64; + i, entries: uint32; + modules: Pmodule_t; + sections: PtrUInt; + sectionAddress, sectionBytes: uint64; +begin + if (info = nil) or ((info^.flags and (1 shl 6)) = 0) then fail('memory map missing'); + if (info^.mmap_length = 0) or (info^.mmap_length > $100000) or + (uint64(info^.mmap_addr) + info^.mmap_length > $100000000) then fail('invalid map length'); + mapStart := info^.mmap_addr; + mapEnd := mapStart + info^.mmap_length; + limit := 0; + entries := 0; + e := Pmemory_map_t(mapStart); + while PtrUInt(e) < mapEnd do begin + if (mapEnd - PtrUInt(e) < 24) or (e^.size < 20) or + (uint64(e^.size) + 4 > mapEnd - PtrUInt(e)) then fail('malformed map entry'); + inc(entries); + if entries > 4096 then fail('too many map entries'); + if (e^.mtype = 1) and (e^.length <> 0) and (e^.base_addr < MAX_MANAGED) then + if regionEnd(e^.base_addr, e^.length) > limit then limit := regionEnd(e^.base_addr, e^.length); + e := nextEntry(e); + end; + frameCount := limit div FRAME_SIZE; + if frameCount = 0 then fail('no usable RAM'); + reservedEnd := $1000000; { legacy low-memory devices and firmware } + preserve(0, PtrUInt(@kernelEnd)); + preserve(PtrUInt(info), 4096); + preserve(mapStart, info^.mmap_length); + if (info^.flags and (1 shl 2)) <> 0 then preserve(info^.cmdline, 4096); + if (info^.flags and (1 shl 9)) <> 0 then preserve(info^.boot_loader_name, 4096); + if (info^.flags and (1 shl 3)) <> 0 then begin + if info^.mods_count > 4096 then fail('too many boot modules'); + preserve(info^.mods_addr, uint64(info^.mods_count) * sizeof(module_t)); + modules := Pmodule_t(PtrUInt(info^.mods_addr)); + if info^.mods_count > 0 then for i := 0 to info^.mods_count - 1 do begin + if modules[i].mod_end < modules[i].mod_start then fail('invalid module range'); + preserve(modules[i].mod_start, modules[i].mod_end - modules[i].mod_start); + if modules[i].name <> 0 then preserve(modules[i].name, 4096); + end; + end; + if (info^.flags and (1 shl 4)) <> 0 then + preserve(info^.elf_sec.addr, uint64(info^.elf_sec.num) + info^.elf_sec.size); + if (info^.flags and (1 shl 5)) <> 0 then begin + if (info^.elf_sec.num > 4096) or + ((info^.elf_sec.size <> 40) and (info^.elf_sec.size <> 64)) then fail('invalid ELF sections'); + preserve(info^.elf_sec.addr, uint64(info^.elf_sec.num) * info^.elf_sec.size); + sections := info^.elf_sec.addr; + if info^.elf_sec.num > 0 then for i := 0 to info^.elf_sec.num - 1 do begin + if info^.elf_sec.size = 64 then begin + sectionAddress := PUInt64(sections + 16)^; + sectionBytes := PUInt64(sections + 32)^; + end else begin + sectionAddress := PUInt32(sections + 12)^; + sectionBytes := PUInt32(sections + 20)^; + end; + if (sectionAddress <> 0) and (sectionBytes <> 0) then preserve(sectionAddress, sectionBytes); + inc(sections, info^.elf_sec.size); + end; + end; + reservedEnd := (reservedEnd + FRAME_SIZE - 1) and not uint64(FRAME_SIZE - 1); + wordCount := (frameCount + 31) div 32; + bytes := (uint64(wordCount) * 8 + FRAME_SIZE - 1) and not uint64(FRAME_SIZE - 1); + storage := metadataAddress(bytes); + if storage = 0 then fail('no low RAM for frame metadata'); + eligible := PUInt32(PtrUInt(storage)); + used := PUInt32(PtrUInt(storage + uint64(wordCount) * 4)); + zero(eligible, bytes); + freeFrames := 0; + e := Pmemory_map_t(mapStart); + while PtrUInt(e) < mapEnd do begin + if e^.mtype = 1 then setAvailable(e^.base_addr, e^.length, true); + e := nextEntry(e); + end; + e := Pmemory_map_t(mapStart); + while PtrUInt(e) < mapEnd do begin + if e^.mtype <> 1 then setAvailable(e^.base_addr, e^.length, false); + e := nextEntry(e); + end; + totalFrames := freeFrames; + setAvailable(0, reservedEnd, false); + setAvailable(storage, bytes, false); + hint := 0; + field('PMM_FREE_FRAMES', freeFrames); + field('PMM_MANAGED_LIMIT', uint64(frameCount) * FRAME_SIZE); + field('PMM_METADATA', storage); +end; + +function allocateBetween(first, last: uint32): uint64; +var + saved: PtrUInt; + pass, start, finish, idx, bits, mask, bit: uint32; +begin + allocateBetween := 0; + saved := lockIRQ; + if last > frameCount then last := frameCount; + if first < last then begin + start := hint; + if (start < first) or (start >= last) then start := first; + finish := last; + for pass := 0 to 1 do begin + idx := start; + while idx < finish do begin + bits := eligible[idx shr 5] and not used[idx shr 5]; + bits := bits and ($FFFFFFFF shl (idx and 31)); + if bits <> 0 then begin + bit := 0; + while (bits and (uint32(1) shl bit)) = 0 do inc(bit); + idx := (idx and not uint32(31)) + bit; + if idx >= finish then break; + mask := uint32(1) shl bit; + used[idx shr 5] := used[idx shr 5] or mask; + dec(freeFrames); + hint := idx + 1; + allocateBetween := uint64(idx) * FRAME_SIZE; + unlockIRQ(saved); + exit; + end; + idx := (idx and not uint32(31)) + 32; + end; + finish := start; + start := first; + end; + end; + unlockIRQ(saved); +end; + +function alloc_page(lowOnly: boolean): uint64; +begin + if lowOnly then alloc_page := allocateBetween(0, $100000) + else alloc_page := allocateBetween(0, frameCount); +end; + +function alloc_high: uint64; +begin + alloc_high := allocateBetween($100000, frameCount); +end; + +function free_page(address: uint64): boolean; +var + idx, mask: uint32; + saved: PtrUInt; +begin + free_page := false; + if (address and (FRAME_SIZE - 1) <> 0) or (address div FRAME_SIZE >= frameCount) then exit; + saved := lockIRQ; + idx := address div FRAME_SIZE; + mask := uint32(1) shl (idx and 31); + if ((eligible[idx shr 5] and used[idx shr 5]) and mask) <> 0 then begin + used[idx shr 5] := used[idx shr 5] and not mask; + inc(freeFrames); + if idx < hint then hint := idx; + free_page := true; + end; + unlockIRQ(saved); +end; + +function reserve_range(address, bytes: uint64): boolean; +var + first, last, i: uint32; + saved: PtrUInt; +begin + reserve_range := false; + if (bytes = 0) or (address >= MAX_MANAGED) or (bytes > MAX_MANAGED - address) then exit; + first := address div FRAME_SIZE; + last := (address + bytes + FRAME_SIZE - 1) div FRAME_SIZE; + if last > frameCount then last := frameCount; + saved := lockIRQ; + if first < last then for i := first to last - 1 do + if (used[i shr 5] and (uint32(1) shl (i and 31))) <> 0 then begin + unlockIRQ(saved); + exit; + end; + setAvailable(address, bytes, false); + reserve_range := true; + unlockIRQ(saved); +end; + +function free_count: uint64; +begin + free_count := freeFrames; +end; + +function total_count: uint64; +begin + total_count := totalFrames; +end; + +procedure UnitTest; +var + before, a, b: uint64; + passed, failed: uint32; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then begin + inc(passed); + puts('PASS: '); + end + else begin + inc(failed); + puts('FAIL: '); + end; + puts(name); + puts(#13#10); + end; +begin + passed := 0; + failed := 0; + before := free_count; + a := alloc_page(true); + b := alloc_page(true); + Assert((a <> 0) and (b <> 0) and (a <> b) and (a >= reservedEnd) and + (b >= reservedEnd), 'PMM unique usable frames exclude boot reservations'); + Assert(not free_page(0) and not free_page(a + 1), 'PMM rejects reserved/unaligned frees'); + Assert(free_page(a) and free_page(b), 'PMM releases owned frames'); + Assert(not free_page(a), 'PMM rejects double free'); + Assert(free_count = before, 'PMM free-frame accounting restored'); + field('PMM_TESTS_FAILED', failed); + if failed <> 0 then bootstrap_halt; +end; +end. diff --git a/src/arch/x86_64/memory/arch.x86_64.memory.virtual.pas b/src/arch/x86_64/memory/arch.x86_64.memory.virtual.pas new file mode 100644 index 00000000..4a8cfa9e --- /dev/null +++ b/src/arch/x86_64/memory/arch.x86_64.memory.virtual.pas @@ -0,0 +1,423 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ BSP kernel mappings. Page-table frames stay below 4 GiB so the bootstrap + identity map can access them; data frames may live above 4 GiB. } +unit arch.x86_64.memory.virtual; + +interface +const + ARENA_SIZE = $400000; + ARENA_COUNT = 4096; + HEAP_BASE = PtrUInt($FFFF800000000000); +function allocate_region(arenas: uint32): PtrUInt; +function release_region(address: PtrUInt; arenas: uint32): boolean; +function map_page(address: PtrUInt; physical: uint64; owned: boolean): boolean; +function unmap_page(address: PtrUInt; var physical: uint64): boolean; +function translate(address: PtrUInt): uint64; +function mapDevice(address: uint64; bytes: SizeUInt): pointer; +procedure init; +procedure UnitTest; + +implementation +uses arch.x86_64.memory.physical, arch.x86_64.util, memory.bitmap, debug.early; +const + ADDRESS_MASK = uint64($000FFFFFFFFFF000); + PTE_OWNED = uint64(1) shl 9; +var + root: PUInt64; + arenaBitmap: PUInt32; + tablePages: uint64; + leafFlags: uint64; + failAfter: sint32; { deterministic OOM injection, disabled outside UnitTest } +function acquire(lowOnly: boolean): uint64; +begin + acquire := 0; + if failAfter = 0 then exit; + if failAfter > 0 then dec(failAfter); + acquire := arch.x86_64.memory.physical.alloc_page(lowOnly); +end; + +procedure fail(message: pchar); +begin + puts('FAIL: VMM '); + puts(message); + puts(#13#10); + bootstrap_halt; +end; + +function managed(address: PtrUInt): boolean; +begin + managed := (address >= HEAP_BASE) and (address - HEAP_BASE < uint64(ARENA_COUNT) * ARENA_SIZE); +end; + +function enableNX: boolean; assembler; nostackframe; +asm + push rbx + mov eax, $80000001 + cpuid + xor eax, eax + test edx, 1 shl 20 + jz @done + mov ecx, $C0000080 + rdmsr + or eax, 1 shl 11 + wrmsr + mov eax, 1 +@done: + pop rbx +end; + +function map_page(address: PtrUInt; physical: uint64; owned: boolean): boolean; +var + table, slot: PUInt64; + shift, created: sint32; + frame, saved: uint64; + parents: array[0..2] of PUInt64; + frames: array[0..2] of uint64; +begin + map_page := false; + if not managed(address) or ((address and $FFF) <> 0) or + ((physical and not ADDRESS_MASK) <> 0) then exit; + saved := lockIRQ; + table := root; + created := 0; + shift := 39; + while shift > 12 do begin + slot := @table[(address shr shift) and $1FF]; + if (slot^ and 1) = 0 then begin + frame := acquire(true); + if frame = 0 then break; + zero(pointer(PtrUInt(frame)), 4096); + parents[created] := slot; + frames[created] := frame; + inc(created); + inc(tablePages); + slot^ := frame or 3; + end; + if (slot^ and $80) <> 0 then break; + table := PUInt64(PtrUInt(slot^ and ADDRESS_MASK)); + dec(shift, 9); + end; + if shift = 12 then begin + slot := @table[(address shr 12) and $1FF]; + if (slot^ and 1) = 0 then begin + slot^ := physical or leafFlags; + if owned then slot^ := slot^ or PTE_OWNED; + invalidate(address); + map_page := true; + end; + end; + if not map_page then while created > 0 do begin + dec(created); + parents[created]^ := 0; + if not arch.x86_64.memory.physical.free_page(frames[created]) then fail('table rollback'); + dec(tablePages); + end; + unlockIRQ(saved); +end; + +function leaf(address: PtrUInt): PUInt64; +var + table: PUInt64; + shift: sint32; + entry: uint64; +begin + leaf := nil; + if not managed(address) then exit; + table := root; + shift := 39; + while shift > 12 do begin + entry := table[(address shr shift) and $1FF]; + if ((entry and 1) = 0) or ((entry and $80) <> 0) then exit; + table := PUInt64(PtrUInt(entry and ADDRESS_MASK)); + dec(shift, 9); + end; + leaf := @table[(address shr 12) and $1FF]; +end; + +function translate(address: PtrUInt): uint64; +var + table: PUInt64; + shift: sint32; + entry, span: uint64; +begin + translate := 0; + if (address > $00007FFFFFFFFFFF) and (address < HEAP_BASE) then exit; + table := root; + shift := 39; + while shift >= 12 do begin + entry := table[(address shr shift) and $1FF]; + if (entry and 1) = 0 then exit; + if (shift = 12) or (((entry and $80) <> 0) and (shift < 39)) then begin + span := uint64(1) shl shift; + translate := (entry and ADDRESS_MASK and not (span - 1)) + (address and (span - 1)); + exit; + end; + table := PUInt64(PtrUInt(entry and ADDRESS_MASK)); + dec(shift, 9); + end; +end; + +function mapDevice(address: uint64; bytes: SizeUInt): pointer; +var + first, last, page: PtrUInt; + table, slot: PUInt64; + flags: PtrUInt; +begin + mapDevice := nil; + if (bytes = 0) or (address >= $100000000) or (bytes > $100000000 - address) then exit; + first := PtrUInt(address) and not PtrUInt($1FFFFF); + last := PtrUInt(address + bytes - 1) and not PtrUInt($1FFFFF); + flags := lockIRQ; + page := first; + repeat + table := PUInt64(PtrUInt(root[0] and ADDRESS_MASK)); + table := PUInt64(PtrUInt(table[(page shr 30) and $1FF] and ADDRESS_MASK)); + slot := @table[(page shr 21) and $1FF]; + { Bootstrap low identity leaves are 2 MiB. Disable caching for MMIO. } + slot^ := slot^ or $18; + invalidate(page); + if page = last then break; + inc(page, $200000); + until false; + unlockIRQ(flags); + mapDevice := pointer(PtrUInt(address)); +end; + +procedure prune(address: PtrUInt); +var + parents: array[0..2] of PUInt64; + tables: array[0..2] of PUInt64; + table, slot: PUInt64; + shift, level: sint32; + i: uint32; + empty: boolean; +begin + table := root; + shift := 39; + level := 0; + while shift > 12 do begin + slot := @table[(address shr shift) and $1FF]; + if ((slot^ and 1) = 0) or ((slot^ and $80) <> 0) then exit; + parents[level] := slot; + table := PUInt64(PtrUInt(slot^ and ADDRESS_MASK)); + tables[level] := table; + inc(level); + dec(shift, 9); + end; + while level > 0 do begin + dec(level); + empty := true; + for i := 0 to 511 do if (tables[level][i] and 1) <> 0 then begin + empty := false; + break; + end; + if not empty then exit; + parents[level]^ := 0; + if not arch.x86_64.memory.physical.free_page(PtrUInt(tables[level])) then fail('table release'); + dec(tablePages); + end; +end; + +function unmap_page(address: PtrUInt; var physical: uint64): boolean; +var + slot: PUInt64; + saved: PtrUInt; +begin + unmap_page := false; + physical := 0; + if not managed(address) or ((address and $FFF) <> 0) then exit; + saved := lockIRQ; + slot := leaf(address); + if slot <> nil then if (slot^ and 1) <> 0 then begin + physical := slot^ and ADDRESS_MASK; + slot^ := 0; + invalidate(address); + prune(address); + unmap_page := true; + end; + unlockIRQ(saved); +end; + +procedure releaseMapped(address: PtrUInt; pages: uint32); +var + i: uint32; + slot: PUInt64; + physical: uint64; +begin + if pages = 0 then exit; + for i := 0 to pages - 1 do begin + slot := leaf(address + uint64(i) * 4096); + if slot = nil then fail('missing owned mapping'); + physical := slot^ and ADDRESS_MASK; + slot^ := 0; + invalidate(address + uint64(i) * 4096); + if not arch.x86_64.memory.physical.free_page(physical) then fail('data frame release'); + end; + { Scan empty tables once per 2 MiB span, not once per 4 KiB page. } + i := 0; + while i < pages do begin + prune(address + uint64(i) * 4096); + inc(i, 512); + end; +end; + +function allocate_region(arenas: uint32): PtrUInt; +var + idx: sint32; + i, pages: uint32; + base, saved: PtrUInt; + physical: uint64; +begin + allocate_region := 0; + if (arenas = 0) or (arenas > ARENA_COUNT) or (arenaBitmap = nil) then exit; + saved := lockIRQ; + idx := find_free_run(arenaBitmap, 0, arenas, ARENA_COUNT); + if idx < 0 then begin + unlockIRQ(saved); + exit; + end; + bitmap_set_range(arenaBitmap, idx, arenas); + base := HEAP_BASE + uint64(idx) * ARENA_SIZE; + pages := arenas * (ARENA_SIZE div 4096); + i := 0; + while i < pages do begin + physical := acquire(false); + if physical = 0 then break; + if not map_page(base + uint64(i) * 4096, physical, true) then begin + if not arch.x86_64.memory.physical.free_page(physical) then fail('allocation rollback'); + break; + end; + inc(i); + end; + if i <> pages then begin + releaseMapped(base, i); + bitmap_clear_range(arenaBitmap, idx, arenas); + end else allocate_region := base; + unlockIRQ(saved); +end; + +function release_region(address: PtrUInt; arenas: uint32): boolean; +var + idx, i, pages: uint32; + saved: PtrUInt; + slot: PUInt64; +begin + release_region := false; + if not managed(address) or ((address and (ARENA_SIZE - 1)) <> 0) or + (arenas = 0) or (arenas > ARENA_COUNT) then exit; + idx := (address - HEAP_BASE) div ARENA_SIZE; + if arenas > ARENA_COUNT - idx then exit; + saved := lockIRQ; + if not bitmap_all_set(arenaBitmap, idx, arenas) then begin + unlockIRQ(saved); + exit; + end; + pages := arenas * (ARENA_SIZE div 4096); + for i := 0 to pages - 1 do begin + slot := leaf(address + uint64(i) * 4096); + if slot = nil then begin + unlockIRQ(saved); + exit; + end; + if (slot^ and (PTE_OWNED or 1)) <> (PTE_OWNED or 1) then begin + unlockIRQ(saved); + exit; + end; + end; + releaseMapped(address, pages); + bitmap_clear_range(arenaBitmap, idx, arenas); + release_region := true; + unlockIRQ(saved); +end; + +procedure init; +var + storage: uint64; +begin + root := PUInt64(PtrUInt(pageRoot and ADDRESS_MASK)); + leafFlags := 3; + if enableNX then leafFlags := leafFlags or (uint64(1) shl 63); + tablePages := 0; + failAfter := -1; + storage := arch.x86_64.memory.physical.alloc_page(true); + if storage = 0 then fail('arena metadata allocation'); + arenaBitmap := PUInt32(PtrUInt(storage)); + zero(arenaBitmap, 4096); +end; + +procedure UnitTest; +const TEST_ADDRESS = HEAP_BASE + $800000; + TEST_PATTERN = uint64($FEDCBA9876543210); +var + before, frame, removed, highFrame: uint64; + region: PtrUInt; + passed, failed: uint32; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then begin + inc(passed); + puts('PASS: '); + end + else begin + inc(failed); + puts('FAIL: '); + end; + puts(name); + puts(#13#10); + if not condition then bootstrap_halt; + end; +begin + passed := 0; + failed := 0; + before := free_count; + frame := arch.x86_64.memory.physical.alloc_page(true); + Assert(frame <> 0, 'VMM test frame allocation'); + Assert(map_page(TEST_ADDRESS, frame, false), 'VMM high virtual mapping'); + Assert(not map_page(TEST_ADDRESS, frame, false) and + not map_page(TEST_ADDRESS + 1, frame, false), 'VMM rejects duplicate/unaligned mapping'); + Assert(translate(TEST_ADDRESS + 123) = frame + 123, 'VMM translation includes page offset'); + PUInt64(TEST_ADDRESS)^ := TEST_PATTERN; + Assert(PUInt64(PtrUInt(frame))^ = TEST_PATTERN, 'VMM aliases the expected physical frame'); + Assert(unmap_page(TEST_ADDRESS, removed) and (removed = frame) and + (translate(TEST_ADDRESS) = 0), 'VMM unmap invalidates translation'); + Assert(arch.x86_64.memory.physical.free_page(frame), 'VMM returns backing frame'); + Assert((free_count = before) and (tablePages = 0), 'VMM prunes empty tables without leaks'); + failAfter := 2; + region := allocate_region(1); + failAfter := -1; + Assert((region = 0) and (free_count = before) and (tablePages = 0), 'VMM rolls back partial table allocation on OOM'); + failAfter := 12; + region := allocate_region(1); + failAfter := -1; + Assert((region = 0) and (free_count = before) and (tablePages = 0), 'VMM rolls back partially populated arenas on OOM'); + highFrame := alloc_high; + if highFrame <> 0 then begin + Assert(map_page(TEST_ADDRESS, highFrame, false), 'VMM maps physical RAM above 4 GiB'); + PUInt64(TEST_ADDRESS)^ := $123456789ABCDEF0; + Assert((PUInt64(TEST_ADDRESS)^ = $123456789ABCDEF0) and + (translate(TEST_ADDRESS) = highFrame), 'VMM reads and writes physical RAM above 4 GiB'); + Assert(unmap_page(TEST_ADDRESS, removed) and + arch.x86_64.memory.physical.free_page(highFrame), 'VMM releases high physical RAM'); + end else puts('SKIP: no available physical RAM above 4 GiB'#13#10); + region := allocate_region(1); + Assert(region <> 0, 'VMM contiguous arena backed by 4 KiB frames'); + PUInt64(region)^ := 1; + PUInt64(region + ARENA_SIZE - 8)^ := 2; + Assert((PUInt64(region)^ = 1) and (PUInt64(region + ARENA_SIZE - 8)^ = 2), 'VMM first/last arena pages writable'); + Assert(release_region(region, 1), 'VMM releases complete arena'); + Assert((free_count = before) and (tablePages = 0), 'VMM final frame accounting restored'); + field('VMM_TESTS_FAILED', failed); +end; +end. diff --git a/src/arch/x86_64/platform/arch.platform.cpu.pas b/src/arch/x86_64/platform/arch.platform.cpu.pas new file mode 100644 index 00000000..ee1ce9c5 --- /dev/null +++ b/src/arch/x86_64/platform/arch.platform.cpu.pas @@ -0,0 +1,101 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Build-selected x86_64 adapter. } +unit arch.platform.cpu; + +interface +{$i ../../common/platform.cpu.inc} + +implementation +uses arch.x86_64.util, memory.raw; + +function lock: PtrUInt; +begin + lock := arch.x86_64.util.lockIRQ; +end; + +procedure unlock(flags: PtrUInt); +begin + if (flags and $200) <> 0 then asm + sti + end; +end; + +procedure idle; +begin + asm + hlt + end; +end; + +procedure outb(port: uint16; value: uint8); +begin + arch.x86_64.util.outb(port, value); +end; + +function inb(port: uint16): uint8; +begin + inb := arch.x86_64.util.inb(port); +end; + +procedure copyBytes(source, dest: PtrUInt; bytes: SizeUInt); +begin + memory.raw.move(pointer(source), pointer(dest), bytes); +end; + +procedure zero(dest: pointer; bytes: SizeUInt); +begin + arch.x86_64.util.zero(dest, bytes); +end; +procedure outl(port: uint16; value: uint32); assembler; nostackframe; +asm + mov edx, edi + mov eax, esi + out dx, eax +end; + +function inl(port: uint16): uint32; assembler; nostackframe; +asm + mov edx, edi + in eax, dx +end; + +procedure queryCPU(leaf, subleaf: uint32; var registers: TCPURegisters); assembler; nostackframe; +asm + push rbx + mov r8, rdx + mov eax, edi + mov ecx, esi + cpuid + mov [r8], eax + mov [r8 + 4], ebx + mov [r8 + 8], ecx + mov [r8 + 12], edx + pop rbx +end; + +procedure outw(port: uint16; value: uint16); assembler; nostackframe; +asm + mov edx, edi + mov eax, esi + out dx, ax +end; + +function inw(port: uint16): uint16; assembler; nostackframe; +asm + mov edx, edi + xor eax, eax + in ax, dx +end; + +end. diff --git a/src/arch/x86_64/platform/arch.platform.diagnostics.pas b/src/arch/x86_64/platform/arch.platform.diagnostics.pas new file mode 100644 index 00000000..91495edd --- /dev/null +++ b/src/arch/x86_64/platform/arch.platform.diagnostics.pas @@ -0,0 +1,47 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Build-selected x86_64 adapter. } +unit arch.platform.diagnostics; + +interface +{$i ../../common/platform.diagnostics.inc} + +implementation +uses debug.early; + +procedure log(tag, message: pchar); +begin + debug.early.puts(message); + debug.early.puts(#13#10); +end; + +procedure push_trace(name: pchar); +begin +end; + +procedure pop_trace; +begin +end; + +procedure fatal(tag, message: pchar); +begin + log(tag, message); + bootstrap_halt; +end; + +procedure testSummary(tag, passedKey, failedKey: pchar; passed, failed: uint32); +begin + debug.early.field(passedKey, passed); + debug.early.field(failedKey, failed); +end; +end. diff --git a/src/arch/x86_64/platform/arch.platform.interrupts.pas b/src/arch/x86_64/platform/arch.platform.interrupts.pas new file mode 100644 index 00000000..4de01089 --- /dev/null +++ b/src/arch/x86_64/platform/arch.platform.interrupts.pas @@ -0,0 +1,54 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.platform.interrupts; + +interface +{$i ../../common/platform.interrupts.inc} + +implementation +uses arch.x86_64.irq, arch.x86_64.util, arch.platform.diagnostics; + +function timerTicks: uint64; +begin + timerTicks := arch.x86_64.irq.ticks; +end; + +procedure registerIRQ(vector: uint8; handler: TIRQHandler); +begin + if not arch.x86_64.irq.registerIRQ(vector, handler) then + fatal('IRQ', 'IRQ handler registration failed'); +end; + +procedure registerTimer(handler: TTimerHandler); +begin + if not arch.x86_64.irq.registerTimer(handler) then + fatal('IRQ', 'Timer handler registration failed'); +end; + +procedure routePCI(line, vector: uint8); +var + flags: PtrUInt; +begin + if (line >= 16) or (line = 2) or (vector <> line + 32) then + fatal('IRQ', 'Unsupported PCI legacy IRQ route'); + flags := lockIRQ; + if line < 8 then + outb($21, inb($21) and not uint8(1 shl line)) + else begin + outb($A1, inb($A1) and not uint8(1 shl (line - 8))); + outb($21, inb($21) and not uint8(4)); + end; + unlockIRQ(flags); +end; + +end. diff --git a/src/arch/x86_64/platform/arch.platform.memory.pas b/src/arch/x86_64/platform/arch.platform.memory.pas new file mode 100644 index 00000000..d909a9fd --- /dev/null +++ b/src/arch/x86_64/platform/arch.platform.memory.pas @@ -0,0 +1,66 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +unit arch.platform.memory; + +interface +uses arch.x86_64.memory.virtual; +const MAX_HEAP_REGIONS = arch.x86_64.memory.virtual.ARENA_COUNT; +{$i ../../common/platform.memory.inc} + +implementation +uses arch.x86_64.memory.physical, arch.platform.diagnostics; + +function allocateHeapPage: PtrUInt; +begin + allocateHeapPage := allocate_region(1); +end; + +function allocateLarge(pages: uint32): PtrUInt; +begin + allocateLarge := allocate_region(pages); +end; + +procedure release(address: PtrUInt; pages: uint32); +begin + if not release_region(address, pages) then fatal('LMM', 'Invalid heap region release'); +end; + +function identityReserve(address: PtrUInt): pointer; +begin + identityReserve := nil; + if address < uint64($100000000) then begin + address := (address shr 22) shl 22; + if reserve_range(address, $400000) then identityReserve := pointer(address); + end; +end; + +function freePhysicalBytes: uint64; +begin + freePhysicalBytes := free_count * FRAME_SIZE; +end; + +function totalPhysicalBytes: uint64; +begin + totalPhysicalBytes := total_count * FRAME_SIZE; +end; +function physicalAddress(address: PtrUInt): uint64; +begin + physicalAddress := arch.x86_64.memory.virtual.translate(address); +end; + +function mapDevice(address: uint64; bytes: SizeUInt): pointer; +begin + mapDevice := arch.x86_64.memory.virtual.mapDevice(address, bytes); +end; + +end. diff --git a/src/arch/x86_64/platform/arch.platform.process.pas b/src/arch/x86_64/platform/arch.platform.process.pas new file mode 100644 index 00000000..b5ceb67a --- /dev/null +++ b/src/arch/x86_64/platform/arch.platform.process.pas @@ -0,0 +1,31 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Build-selected x86_64 adapter. } +unit arch.platform.process; + +interface +{$i ../../common/platform.process.inc} + +implementation +uses arch.x86_64.context, arch.x86_64.irq; + +function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt; +begin + prepare := arch.x86_64.context.prepare(stack, bytes, entry, 0); +end; + +function timerTicks: uint32; +begin + timerTicks := uint32(arch.x86_64.irq.ticks); +end; +end. diff --git a/src/arch/x86_64/proc/arch.x86_64.proc.sched.pas b/src/arch/x86_64/proc/arch.x86_64.proc.sched.pas new file mode 100644 index 00000000..66970f6d --- /dev/null +++ b/src/arch/x86_64/proc/arch.x86_64.proc.sched.pas @@ -0,0 +1,217 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Adapter between saved CPU contexts and the shared process manager. } +unit arch.x86_64.proc.sched; + +interface +procedure init; +procedure UnitTest; + +implementation +uses proc.mgr, proc.types, io.stdio, core.strings, driver.storage.fdtable, arch.x86_64.context, arch.x86_64.util, + arch.x86_64.irq, arch.common.legacyirq, memory.heap, memory.raw, + core.ds.dynamic, debug.early; +var + switches, cleaned, returned, woke, exited, forbidden: uint32; + childID, parentID: uint32; + cleanupCtx: PProcessContext; + commandParams: PParamList; + commandBuffer: POutBuf; + commandValid, sleepValid, asyncRetained: boolean; + asyncEntry: PFileDescriptor; + fdReleased: uint32; + +function selectProcess(saved: PtrUInt): PtrUInt; +begin + CurrentProcess^.SavedStack := saved; + CurrentProcess := scheduler_pick_next; + inc(switches); + selectProcess := CurrentProcess^.SavedStack; +end; + +procedure init; +begin + { Caller owns IRQ exclusion; proc.mgr.init must have installed idle. } + setSelector(@selectProcess); +end; + +procedure cleanupResource(handle: void); +begin + inc(cleaned); + { Re-enter cleanup to verify the resource was detached before invocation. } + unbindResource(cleanupCtx, handle); + kfree(handle); +end; + +procedure parentWorker(ctx: PProcessContext); +var + flags: PtrUInt; +begin + flags := lockIRQ; + if ctx^.PendingMsg <> smChildExited then ctx^.State := psAwaiting; + unlockIRQ(flags); + proc_await; + if (ctx^.PendingMsg = smChildExited) and (PtrUInt(ctx^.MsgData) = childID) then inc(woke); + inc(returned); +end; + +procedure childWorker(ctx: PProcessContext); +begin + inc(exited); + proc_exit(7); + inc(forbidden); +end; + +procedure forbiddenWorker(ctx: PProcessContext); +begin + inc(forbidden); +end; + +procedure closeChildFD(data: pointer); +begin + inc(fdReleased); + kfree(void(data)); +end; + +procedure commandWorker(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); +type PState = ^TProcessState; +var + start: uint64; + held: PProcessContext; +begin + commandValid := (params = commandParams) and (stdin_buf = commandBuffer) and + (stdout_buf = commandBuffer) and (stderr_buf = commandBuffer); + start := ticks; + proc_sleep_ms(2); + sleepValid := ticks - start >= (uint64(2) * PIT_INPUT_HZ + PIT_DIVISOR * 1000 - 1) div (PIT_DIVISOR * 1000); + held := findByID(childID); + if held <> nil then begin + while (PState(@held^.State)^ <> psFinished) and (ticks - start < 1000) do proc_yield; + asyncRetained := (held^.State = psFinished) and fd_has_async(asyncEntry); + fd_end_async(asyncEntry); + end; +end; + +procedure UnitTest; +type PCommandBuffer = ^TCommandBuffer; +var + input: PCommandBuffer; + parent, child, killed: PProcessContext; + beforeFree: SizeUInt; + started: uint64; + flags: PtrUInt; + list: PDList; + slot: ^PtrUInt; + i, failed, passed, baseCount: uint32; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then begin + inc(passed); + puts('PASS: '); + end + else begin + inc(failed); + puts('FAIL: '); + end; + puts(name); + puts(#13#10); + end; +begin + failed := 0; + passed := 0; + beforeFree := lmm_total_free; + baseCount := processCount; + list := DL_New(sizeof(PtrUInt)); + for i := 0 to 79 do begin + slot := pointer(DL_Add(list)); + slot^ := PtrUInt($1234567800000000) + i; + end; + slot := pointer(DL_Insert(list, 1)); + slot^ := 777; + Assert(PUInt64(DL_Get(list, 2))^ = uint64($1234567800000001), 'native dynamic list overlapping insertion'); + DL_Delete(list, 1); + Assert(PUInt64(DL_Get(list, 79))^ = uint64($123456780000004F), 'native dynamic list growth and deletion'); + DL_Clear(list); + DL_ShrinkToFit(list); + slot := pointer(DL_Add(list)); + Assert((slot <> nil) and (slot^ = 0), 'empty dynamic list regrows with zeroed slots'); + DL_Free(list); + Assert(lmm_total_free = beforeFree, 'dynamic list releases allocation ownership'); + + cleaned := 0; + returned := 0; + woke := 0; + exited := 0; + forbidden := 0; + parent := createSuspended('lifecycle parent', @parentWorker, nil, 1); + child := createSuspended('lifecycle child', @childWorker, nil, 3); + killed := createSuspended('never run', @forbiddenWorker, nil, 1); + parentID := parent^.ProcessID; + childID := child^.ProcessID; + child^.ParentID := parentID; + asyncRetained := false; + fdReleased := 0; + asyncEntry := @PFDTable(child^.FDTable)^.Entries[0]; + asyncEntry^.InUse := true; + asyncEntry^.FSPrivate := pointer(kalloc(11)); + asyncEntry^.FSCloseHook := @closeChildFD; + fd_begin_async(asyncEntry); + cleanupCtx := parent; + bindResource(parent, rkCustom, kalloc(37), @cleanupResource); + Assert((findByID(parentID) = parent) and (findByName('lifecycle child') = child), 'process lookup preserves native pointers'); + Assert((parent^.State = psSuspended) and (child^.State = psSuspended), 'processes stay suspended until published'); + kill(killed^.ProcessID); + Assert(killed^.State = psFinished, 'killed suspended process never published for execution'); + input := PCommandBuffer(kalloc(sizeof(TCommandBuffer))); + memory.raw.move(pchar('echo scheduled'), input, 14); + commandParams := getParams(input^); + kfree(void(input)); + commandBuffer := createOutBuf(1); + commandValid := false; + sleepValid := false; + runCommand('scheduled ECHO', findCommand('echo')^.method, commandParams, + nil, commandBuffer, nil); + runCommand('lifecycle command', @commandWorker, commandParams, + commandBuffer, commandBuffer, commandBuffer); + parent^.State := psReady; + child^.State := psReady; + started := ticks; + masks($FE, $FF); + asm + sti + end; + repeat + asm + pause + end; + until (processCount = baseCount) or (ticks - started > 16000); + flags := lockIRQ; + masks($FF, $FF); + Assert((flags and $200) <> 0, 'process lifecycle preserves interrupt state'); + Assert((returned = 1) and (exited = 1) and (forbidden = 0), 'return and explicit exit never resume finished processes'); + Assert(woke = 1, 'child completion wakes awaiting parent'); + Assert(cleaned = 1, 'resource callback runs exactly once under reentry'); + Assert((processCount = baseCount) and (CurrentProcess = IdleProcess), 'reaper removes finished processes and retains system tasks'); + Assert(commandValid, 'command arguments and streams installed before execution'); + Assert(sleepValid, 'process sleep uses the programmed PIT rate'); + Assert(asyncRetained and (fdReleased = 1), 'reaper waits for async FD completion then closes exactly once'); + Assert(stringEquals(commandBuffer^.buf, 'scheduled '#10), 'shared ECHO executes through scheduler'); + freeParams(commandParams); + freeOutBuf(commandBuffer); + Assert(lmm_total_free = beforeFree, 'process contexts, stacks, CWD and resources freed'); + field('PROCESS_TESTS_PASSED', passed); + field('PROCESS_TESTS_FAILED', failed); + if failed <> 0 then bootstrap_halt; +end; +end. diff --git a/src/arch/x86_64/system.pas b/src/arch/x86_64/system.pas new file mode 100644 index 00000000..b9f59377 --- /dev/null +++ b/src/arch/x86_64/system.pas @@ -0,0 +1,70 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Minimal bootstrap runtime. Unsupported runtime operations deliberately have + no implementation, so accidental dependencies fail at compile/link time. } +unit system; + +interface + +const TRACER_ENABLE = true; + +type SizeInt = int64; + SizeUInt = qword; + +{$i ../common/system.types.inc} + +type + { SysV AMD64 nonvolatile register layout. No exception-unwinding helpers + are provided at this bootstrap stage. FPC still requires these types. } + jmp_buf = packed record + rbx, rbp, r12, r13, r14, r15, rsp, rip: uint64; + end; + PJmp_buf = ^jmp_buf; + PExceptAddr = ^TExceptAddr; + TExceptAddr = record + buf: PJmp_buf; + next: PExceptAddr; + frametype: longint; + end; + +procedure init; +procedure fpc_initializeunits; compilerproc; + +procedure bootstrap_halt; external name 'bootstrap_halt'; +function Trunc(d: Double): int64; + +implementation + +var + InitFinalTable: TInitFinalTable; external name 'INITFINAL'; + +{$i ../common/system.init.inc} + +procedure init; +begin + initializeUnits; +end; + +procedure fpc_initializeunits; [public, alias: 'FPC_INITIALIZEUNITS']; compilerproc; +begin + initializeUnits; +end; + +{ SysV AMD64 passes Double in XMM0. CVTTSD2SI rounds toward zero without + changing the caller's x87 control word or MXCSR rounding mode. } +function Trunc(d: Double): int64; assembler; nostackframe; +asm + cvttsd2si rax, xmm0 +end; + +end. diff --git a/src/asuro.pas b/src/asuro.pas index 8f26b7e3..dd7f0b57 100644 --- a/src/asuro.pas +++ b/src/asuro.pas @@ -27,7 +27,7 @@ uses io.syslog, core.panic, arch.x86.multiboot, - arch.x86.proc.sched; + arch.x86.proc.sched, arch.x86.startup; procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: uint32); stdcall; diff --git a/src/boot/boot.multiboot.pas b/src/boot/boot.multiboot.pas new file mode 100644 index 00000000..0d856c4a --- /dev/null +++ b/src/boot/boot.multiboot.pas @@ -0,0 +1,78 @@ +// Copyright 2021 Kieron Morris & Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Multiboot v1 wire layout: fixed-width fields on every architecture. } +unit boot.multiboot; + +interface +const MULTIBOOT_BOOTLOADER_MAGIC = $2BADB002; +type + Pelf_section_header_table_t = ^elf_section_header_table_t; + elf_section_header_table_t = packed record + num: uint32; + size: uint32; + addr: uint32; + shndx: uint32; + end; + + Pmultiboot_info_t = ^multiboot_info_t; + multiboot_info_t = packed record + flags: uint32; + mem_lower: uint32; + mem_upper: uint32; + boot_device: uint32; + cmdline: uint32; + mods_count: uint32; + mods_addr: uint32; + elf_sec: elf_section_header_table_t; + mmap_length: uint32; + mmap_addr: uint32; + drives_legnth : uint32; + drives_addr: uint32; + config_table : uint32; + boot_loader_name : uint32; + apm_table: uint32; + vbe_control_info : uint32; + vbe_mode_info : uint32; + vbe_mode : uint16; + vbe_interface_seg : uint16; + vbe_interface_off : uint16; + vbe_interface_len : uint16; + framebuffer_addr : uint64; + framebuffer_pitch : uint32; + framebuffer_width : uint32; + framebuffer_height: uint32; + framebuffer_bpp : uint8; + end; + + Pmodule_t = ^module_t; + module_t = packed record + mod_start: uint32; + mod_end: uint32; + name: uint32; + reserved: uint32; + end; + + Pmemory_map_t = ^memory_map_t; + memory_map_t = packed record + size: uint32; + base_addr : uint64; + length : uint64; + mtype: uint32; + end; + +var multibootinfo: Pmultiboot_info_t = nil; + +implementation +end. diff --git a/src/core/core.strings.helpers.pas b/src/core/core.strings.helpers.pas deleted file mode 100644 index bc4a917b..00000000 --- a/src/core/core.strings.helpers.pas +++ /dev/null @@ -1,272 +0,0 @@ -{ - Core->StringHelpers - Reusable file-oriented search, sort, and format utilities. - - Provides: - - getFileExtension: zero-copy extension extraction - - fmtFileSize: human-readable file size formatting (kalloc'd result) - - sortStringArray: in-place insertion sort on pchar arrays - - sortStringArrayWithData: parallel insertion sort (pchar + uint32 arrays) - - @author(Aaron Hance ) -} -unit core.strings.helpers; - -interface - -type - PPChar = ^pchar; - -{ Return a pointer into name at the last '.' (including the dot). - Returns nil if there is no extension. Zero-copy: does NOT allocate. } -function getFileExtension(name : pchar) : pchar; - -{ Return a kalloc'd human-readable size string (e.g. '1.2 MB'). - Caller must kfree the result. } -function fmtFileSize(sz : uint32) : pchar; - -{ In-place case-insensitive insertion sort on an array of n pchar pointers. } -procedure sortStringArray(arr : PPChar; n : uint32); - -{ In-place case-insensitive insertion sort on an array of n pchar pointers, - swapping a parallel uint32 data array in lockstep. } -procedure sortStringArrayWithData(arr : PPChar; data : puint32; n : uint32); - -procedure UnitTest; - -implementation - -uses - core.strings, - memory.heap, - io.syslog; - -{ ============================================================ - getFileExtension - Walk backwards from end of name to find the last '.'. - Returns pointer into original string at the dot, or nil. - ============================================================ } -function getFileExtension(name : pchar) : pchar; -var - len : uint32; - i : uint32; -begin - getFileExtension := nil; - if name = nil then exit; - len := stringSize(name); - if len = 0 then exit; - i := len; - while i > 0 do begin - i := i - 1; - if name[i] = '.' then begin - getFileExtension := @name[i]; - exit; - end; - end; -end; - -{ ============================================================ - fmtFileSize - Returns a kalloc'd human-readable size string. - Caller must kfree the result. - ============================================================ } -function fmtFileSize(sz : uint32) : pchar; -var - whole, frac : uint32; - s1, s2, s3, res : pchar; -begin - if sz < 1024 then begin - s1 := intToString(sz); - res := stringConcat(s1, ' B'); - kfree(void(s1)); - end else if sz < uint32(1024 * 1024) then begin - whole := sz div 1024; - frac := (sz mod 1024) * 10 div 1024; - s1 := intToString(whole); - s2 := stringConcat(s1, '.'); kfree(void(s1)); - s3 := intToString(frac); - s1 := stringConcat(s2, s3); kfree(void(s2)); kfree(void(s3)); - res := stringConcat(s1, ' KB'); kfree(void(s1)); - end else begin - whole := sz div (1024 * 1024); - frac := (sz mod (1024 * 1024)) * 10 div (1024 * 1024); - s1 := intToString(whole); - s2 := stringConcat(s1, '.'); kfree(void(s1)); - s3 := intToString(frac); - s1 := stringConcat(s2, s3); kfree(void(s2)); kfree(void(s3)); - res := stringConcat(s1, ' MB'); kfree(void(s1)); - end; - fmtFileSize := res; -end; - -{ ============================================================ - sortStringArray - In-place insertion sort using stringCompareCI. - Guarded for n=0 (Lesson #13). - ============================================================ } -procedure sortStringArray(arr : PPChar; n : uint32); -var - i, j : uint32; - tmp : pchar; - p : PPChar; -begin - if (arr = nil) or (n < 2) then exit; - if n > 1 then - for i := 1 to n - 1 do begin - p := PPChar(pointer(uint32(arr) + i * sizeof(pchar))); - tmp := p^; - j := i; - while j > 0 do begin - p := PPChar(pointer(uint32(arr) + (j - 1) * sizeof(pchar))); - if stringCompareCI(tmp, p^) >= 0 then break; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := p^; - j := j - 1; - end; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := tmp; - end; -end; - -{ ============================================================ - sortStringArrayWithData - In-place insertion sort on pchar array, swapping a parallel - uint32 data array in lockstep. Uses stringCompareCI. - Guarded for n=0 (Lesson #13). - ============================================================ } -procedure sortStringArrayWithData(arr : PPChar; data : puint32; n : uint32); -var - i, j : uint32; - tmpN : pchar; - tmpD : uint32; - p : PPChar; - dp : puint32; -begin - if (arr = nil) or (n < 2) then exit; - if n > 1 then - for i := 1 to n - 1 do begin - p := PPChar(pointer(uint32(arr) + i * sizeof(pchar))); - dp := puint32(pointer(uint32(data) + i * sizeof(uint32))); - tmpN := p^; - tmpD := dp^; - j := i; - while j > 0 do begin - p := PPChar(pointer(uint32(arr) + (j - 1) * sizeof(pchar))); - if stringCompareCI(tmpN, p^) >= 0 then break; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := p^; - puint32(pointer(uint32(data) + j * sizeof(uint32)))^ := - puint32(pointer(uint32(data) + (j - 1) * sizeof(uint32)))^; - j := j - 1; - end; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := tmpN; - puint32(pointer(uint32(data) + j * sizeof(uint32)))^ := tmpD; - end; -end; - -{ ============================================================ - UnitTest - ============================================================ } -procedure UnitTest; -var - passed, failed : uint32; - s : pchar; - ext : pchar; - names : array[0..4] of pchar; - sizes : array[0..4] of uint32; - - procedure Assert(condition : boolean; testName : pchar); - var - msg : pchar; - begin - if condition then begin - inc(passed); - end else begin - inc(failed); - msg := stringConcat('FAIL: ', testName); - io.syslog.logln('SEARCH', msg); - kfree(void(msg)); - end; - end; - - procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; - begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('SEARCH', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); - end; - -begin - passed := 0; - failed := 0; - io.syslog.logln('SEARCH', 'Unit tests starting...'); - - { === getFileExtension === } - ext := getFileExtension('readme.txt'); - Assert(ext <> nil, 'getFileExt txt not nil'); - Assert(stringEquals(ext, '.txt'), 'getFileExt txt'); - ext := getFileExtension('archive.tar.gz'); - Assert(stringEquals(ext, '.gz'), 'getFileExt tar.gz'); - ext := getFileExtension('noext'); - Assert(ext = nil, 'getFileExt none'); - ext := getFileExtension(''); - Assert(ext = nil, 'getFileExt empty'); - ext := getFileExtension(nil); - Assert(ext = nil, 'getFileExt nil'); - ext := getFileExtension('.hidden'); - Assert(stringEquals(ext, '.hidden'), 'getFileExt dotfile'); - - { === fmtFileSize === } - s := fmtFileSize(0); - Assert(stringEquals(s, '0 B'), 'fmtFileSize 0'); - kfree(void(s)); - s := fmtFileSize(512); - Assert(stringEquals(s, '512 B'), 'fmtFileSize 512'); - kfree(void(s)); - s := fmtFileSize(1024); - Assert(stringEquals(s, '1.0 KB'), 'fmtFileSize 1KB'); - kfree(void(s)); - s := fmtFileSize(1024 * 1024); - Assert(stringEquals(s, '1.0 MB'), 'fmtFileSize 1MB'); - kfree(void(s)); - - { === sortStringArray === } - names[0] := 'delta'; - names[1] := 'alpha'; - names[2] := 'Charlie'; - names[3] := 'bravo'; - names[4] := 'Echo'; - sortStringArray(@names[0], 5); - Assert(stringEqualsCI(names[0], 'alpha'), 'sort[0]=alpha'); - Assert(stringEqualsCI(names[1], 'bravo'), 'sort[1]=bravo'); - Assert(stringEqualsCI(names[2], 'Charlie'), 'sort[2]=charlie'); - Assert(stringEqualsCI(names[3], 'delta'), 'sort[3]=delta'); - Assert(stringEqualsCI(names[4], 'Echo'), 'sort[4]=echo'); - - { === sortStringArray edge cases === } - sortStringArray(@names[0], 0); { should not crash } - sortStringArray(@names[0], 1); { should not crash } - sortStringArray(nil, 5); { should not crash } - - { === sortStringArrayWithData === } - names[0] := 'delta'; sizes[0] := 4; - names[1] := 'alpha'; sizes[1] := 1; - names[2] := 'Charlie'; sizes[2] := 3; - names[3] := 'bravo'; sizes[3] := 2; - names[4] := 'Echo'; sizes[4] := 5; - sortStringArrayWithData(@names[0], @sizes[0], 5); - Assert(stringEqualsCI(names[0], 'alpha'), 'sortData[0]=alpha'); - Assert(sizes[0] = 1, 'sortData[0] data=1'); - Assert(stringEqualsCI(names[4], 'Echo'), 'sortData[4]=echo'); - Assert(sizes[4] = 5, 'sortData[4] data=5'); - - PrintSummary; -end; - -end. diff --git a/src/core/core.util.pas b/src/core/core.util.pas index 2b1b4e73..8ec8c73e 100644 --- a/src/core/core.util.pas +++ b/src/core/core.util.pas @@ -1,4 +1,5 @@ // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -32,35 +33,28 @@ function switchendian16(b : uint16) : uint16; function switchendian32(b : uint32) : uint32; function getWord(i : uint32; hi : boolean) : uint16; function getByte(i : uint32; index : uint8) : uint8; +function RolDWord(value: uint32; distance: uint8): uint32; -procedure memset(location : uint32; value : uint8; size : uint32); -procedure memcpy(source : uint32; dest : uint32; size : uint32); +procedure memset(location : PtrUInt; value : uint8; size : SizeUInt); +procedure memcpy(source : PtrUInt; dest : PtrUInt; size : SizeUInt); function BCDToUint8(bcd : uint8) : uint8; function HexCharToDecimal(hex : char) : uint8; function abs(x : sint32) : uint32; +procedure waitTicks(count: uint32); + implementation -{$ifdef CPU386} -uses - arch.x86.util; +uses memory.raw, arch.platform.cpu, arch.platform.process; -const - MEMCPY_X86_SMALL_COPY_THRESHOLD = 16; -{$endif} - -procedure scalar_memcpy(source : uint32; dest : uint32; size : uint32); -var - src, dst : puint8; - i : uint32; +function RolDWord(value: uint32; distance: uint8): uint32; begin - if size = 0 then exit; - for i := 0 to size - 1 do begin - src := puint8(source + i); - dst := puint8(dest + i); - dst^ := src^; - end; + distance := distance and 31; + if distance = 0 then + RolDWord := value + else + RolDWord := (value shl distance) or (value shr (32 - distance)); end; function abs(x : sint32) : uint32; @@ -129,30 +123,15 @@ begin switchendian:= (lo(b) SHL 4) OR hi(b); end; -procedure memset(location : uint32; value : uint8; size : uint32); -var - loc : puint8; - i : uint32; - +procedure memset(location : PtrUInt; value : uint8; size : SizeUInt); begin - if size = 0 then exit; - for i:=0 to size-1 do begin - loc:= puint8(location + i); - loc^:= value; - end; + memory.raw.fill(pointer(location), value, size); end; -procedure memcpy(source : uint32; dest : uint32; size : uint32); +procedure memcpy(source : PtrUInt; dest : PtrUInt; size : SizeUInt); begin if size = 0 then exit; -{$ifdef CPU386} - if size < MEMCPY_X86_SMALL_COPY_THRESHOLD then - scalar_memcpy(source, dest, size) - else - arch.x86.util.__REP_MOVSB_memcpy(source, dest, size); -{$else} - scalar_memcpy(source, dest, size); -{$endif} + arch.platform.cpu.copyBytes(source, dest, size); end; function getWord(i : uint32; hi : boolean) : uint16; @@ -178,4 +157,13 @@ begin BCDToUint8:= ((bcd SHR 4) * 10) + (bcd AND $0F); end; +procedure waitTicks(count: uint32); +var + started: uint32; +begin + started := arch.platform.process.timerTicks; + while arch.platform.process.timerTicks - started < count do + arch.platform.cpu.idle; +end; + end. diff --git a/src/core/ds/core.ds.cfifo.pas b/src/core/ds/core.ds.cfifo.pas index 52c07cea..56e7d2b5 100644 --- a/src/core/ds/core.ds.cfifo.pas +++ b/src/core/ds/core.ds.cfifo.pas @@ -24,7 +24,7 @@ interface uses core.ds.types, memory.heap, - core.util, arch.x86.util; + core.util; { ============================================================================ } { Contiguous FIFO Queue — CFIFO_* API } @@ -93,7 +93,7 @@ uses implementation uses - io.syslog, core.strings; + arch.platform.diagnostics, core.strings; { ---------------------------------------------------------------------------- } { Internal helpers } @@ -102,38 +102,43 @@ uses {** Shifts elements back to index 0, called when Head >= Capacity div 2. **} procedure CFIFO_Compact(Queue : PCFIFOQueue); var - src : uint32; - len : uint32; + src : PtrUInt; + len : SizeUInt; begin if (Queue^.Head = 0) or (Queue^.Count = 0) then exit; - src := uint32(Queue^.Data) + (Queue^.Head * Queue^.ElementSize); - len := Queue^.Count * Queue^.ElementSize; + src := PtrUInt(Queue^.Data) + (SizeUInt(Queue^.Head) * Queue^.ElementSize); + len := SizeUInt(Queue^.Count) * Queue^.ElementSize; { memcpy is safe here because dst < src (Head > 0), so no overlap issue } - memcpy(src, uint32(Queue^.Data), len); + memcpy(src, PtrUInt(Queue^.Data), len); Queue^.Head := 0; end; {** Doubles the buffer capacity, compacting first. **} -procedure CFIFO_Grow(Queue : PCFIFOQueue); +function CFIFO_Grow(Queue : PCFIFOQueue): boolean; var newCap : uint32; newBuf : void; begin + CFIFO_Grow := false; + if Queue^.Capacity > High(uint32) div 2 then exit; + { Compact so elements start at index 0 before copying } CFIFO_Compact(Queue); newCap := Queue^.Capacity * 2; if newCap = 0 then newCap := 8; - newBuf := kalloc(newCap * Queue^.ElementSize); - memset(uint32(newBuf), 0, newCap * Queue^.ElementSize); + if SizeUInt(newCap) > High(SizeUInt) div Queue^.ElementSize then exit; + newBuf := kalloc(SizeUInt(newCap) * Queue^.ElementSize); + if newBuf = nil then exit; if Queue^.Count > 0 then - memcpy(uint32(Queue^.Data), uint32(newBuf), Queue^.Count * Queue^.ElementSize); + memcpy(PtrUInt(Queue^.Data), PtrUInt(newBuf), SizeUInt(Queue^.Count) * Queue^.ElementSize); kfree(Queue^.Data); Queue^.Data := newBuf; Queue^.Capacity := newCap; + CFIFO_Grow := true; end; { ---------------------------------------------------------------------------- } @@ -141,24 +146,36 @@ end; { ---------------------------------------------------------------------------- } function CFIFO_New(ElementSize : uint32; InitialCapacity : uint32) : PCFIFOQueue; +var + queue: PCFIFOQueue; begin - CFIFO_New := PCFIFOQueue(kalloc(sizeof(TCFIFOQueue))); - CFIFO_New^.ElementSize := ElementSize; - CFIFO_New^.Capacity := InitialCapacity; - CFIFO_New^.Count := 0; - CFIFO_New^.Head := 0; - CFIFO_New^.Data := kalloc(InitialCapacity * ElementSize); - memset(uint32(CFIFO_New^.Data), 0, InitialCapacity * ElementSize); + CFIFO_New := nil; + if ElementSize = 0 then exit; + if InitialCapacity = 0 then InitialCapacity := 8; + if SizeUInt(InitialCapacity) > High(SizeUInt) div ElementSize then exit; + + queue := PCFIFOQueue(kalloc(sizeof(TCFIFOQueue))); + if queue = nil then exit; + queue^.Data := kalloc(SizeUInt(InitialCapacity) * ElementSize); + if queue^.Data = nil then begin + kfree(void(queue)); + exit; + end; + queue^.ElementSize := ElementSize; + queue^.Capacity := InitialCapacity; + CFIFO_New := queue; end; procedure CFIFO_Enqueue(Queue : PCFIFOQueue; Data : void); var tail : uint32; - dst : uint32; + dst : PtrUInt; begin + if (Queue = nil) or (Data = nil) then exit; + { Grow if all slots are consumed } if Queue^.Count >= Queue^.Capacity then - CFIFO_Grow(Queue); + if not CFIFO_Grow(Queue) then exit; { Compact if there's no room at the back even though Count < Capacity, which happens when Head has advanced past zero } @@ -169,20 +186,20 @@ begin tail := Queue^.Count; { Head is now 0 } end; - dst := uint32(Queue^.Data) + (tail * Queue^.ElementSize); - memcpy(uint32(Data), dst, Queue^.ElementSize); + dst := PtrUInt(Queue^.Data) + (SizeUInt(tail) * Queue^.ElementSize); + memcpy(PtrUInt(Data), dst, Queue^.ElementSize); Queue^.Count := Queue^.Count + 1; end; function CFIFO_Dequeue(Queue : PCFIFOQueue; Data : void) : boolean; var - src : uint32; + src : PtrUInt; begin CFIFO_Dequeue := false; if Queue^.Count = 0 then exit; - src := uint32(Queue^.Data) + (Queue^.Head * Queue^.ElementSize); - memcpy(src, uint32(Data), Queue^.ElementSize); + src := PtrUInt(Queue^.Data) + (SizeUInt(Queue^.Head) * Queue^.ElementSize); + memcpy(src, PtrUInt(Data), Queue^.ElementSize); Queue^.Head := Queue^.Head + 1; Queue^.Count := Queue^.Count - 1; @@ -203,7 +220,7 @@ begin if Queue^.Count = 0 then CFIFO_Peek := nil else - CFIFO_Peek := void(uint32(Queue^.Data) + (Queue^.Head * Queue^.ElementSize)); + CFIFO_Peek := void(PtrUInt(Queue^.Data) + (SizeUInt(Queue^.Head) * Queue^.ElementSize)); end; function CFIFO_Size(Queue : PCFIFOQueue) : uint32; @@ -232,6 +249,7 @@ procedure UnitTest; var q : PCFIFOQueue; v, r : uint32; + nativeValue, nativeResult: PtrUInt; ok : boolean; p : void; i : uint32; @@ -246,32 +264,16 @@ var end else begin inc(failed); msg := stringConcat('FAIL: ', testName); - io.syslog.logln('CFIFO', msg); + arch.platform.diagnostics.log('CFIFO', msg); kfree(void(msg)); end; end; - procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; - begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('CFIFO', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); - end; begin passed := 0; failed := 0; - io.syslog.logln('CFIFO', 'Unit tests starting...'); + arch.platform.diagnostics.log('CFIFO', 'Unit tests starting...'); { === New / Empty / Size / Capacity === } q := CFIFO_New(sizeof(uint32), 4); @@ -306,12 +308,18 @@ begin Assert(CFIFO_Size(q) = 6, 'Size after grow'); { === Verify FIFO order: 30,40,50,60,70,80 === } - ok := CFIFO_Dequeue(q, @r); Assert(r = 30, 'Order 30'); - ok := CFIFO_Dequeue(q, @r); Assert(r = 40, 'Order 40'); - ok := CFIFO_Dequeue(q, @r); Assert(r = 50, 'Order 50'); - ok := CFIFO_Dequeue(q, @r); Assert(r = 60, 'Order 60'); - ok := CFIFO_Dequeue(q, @r); Assert(r = 70, 'Order 70'); - ok := CFIFO_Dequeue(q, @r); Assert(r = 80, 'Order 80'); + ok := CFIFO_Dequeue(q, @r); + Assert(r = 30, 'Order 30'); + ok := CFIFO_Dequeue(q, @r); + Assert(r = 40, 'Order 40'); + ok := CFIFO_Dequeue(q, @r); + Assert(r = 50, 'Order 50'); + ok := CFIFO_Dequeue(q, @r); + Assert(r = 60, 'Order 60'); + ok := CFIFO_Dequeue(q, @r); + Assert(r = 70, 'Order 70'); + ok := CFIFO_Dequeue(q, @r); + Assert(r = 80, 'Order 80'); Assert(CFIFO_IsEmpty(q), 'Empty after drain'); { === Edge: peek/dequeue on empty === } @@ -322,7 +330,15 @@ begin CFIFO_Free(q); - PrintSummary; + q := CFIFO_New(sizeof(PtrUInt), 0); + nativeValue := PtrUInt(q); + CFIFO_Enqueue(q, void(@nativeValue)); + Assert(CFIFO_Dequeue(q, void(@nativeResult)) and (nativeResult = nativeValue), + 'Native pointer survives queue storage'); + CFIFO_Free(q); + Assert(CFIFO_New(0, 4) = nil, 'Zero-sized elements rejected'); + + testSummary('CFIFO', 'CFIFO_TESTS_PASSED', 'CFIFO_TESTS_FAILED', passed, failed); end; end. diff --git a/src/core/ds/core.ds.dynamic.pas b/src/core/ds/core.ds.dynamic.pas new file mode 100644 index 00000000..80de780e --- /dev/null +++ b/src/core/ds/core.ds.dynamic.pas @@ -0,0 +1,230 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Dynamic-list implementation shared with the legacy core.ds.lists API. + Original interface: Kieron Morris. Capacity remains bounded to uint32 bytes; + addresses are native-width. Operations require caller-owned synchronization. } +unit core.ds.dynamic; + +interface +type + PDList = ^TDList; + TDList = record + Count: uint32; + Data: void; + ElementSize, DataSize: uint32; + end; + +function DL_New(ElementSize : uint32) : PDList; +function DL_Add(DList : PDList) : Void; +function DL_Delete(DList : PDList; idx : uint32) : boolean; +function DL_Get(DList : PDList; idx : uint32) : Void; +function DL_Set(DList : PDList; idx : uint32; elm : puint32) : boolean; +function DL_Size(DList : PDList) : uint32; +procedure DL_Free(DList : PDList); +function DL_IndexOf(DList : PDList; elm : puint32) : uint32; +function DL_Contains(DList : PDList; elm : puint32) : boolean; +function DL_Concat(DList1 : PDList; DList2 : PDList) : PDList; +procedure DL_Clear(DList : PDList); +function DL_Insert(DList : PDList; idx : uint32) : Void; +function DL_Capacity(DList : PDList) : uint32; +procedure DL_Reserve(DList : PDList; NewCapacity : uint32); +procedure DL_ShrinkToFit(DList : PDList); + +implementation +uses memory.heap, memory.raw; + +function resize(list: PDList; bytes: uint32): boolean; +var + data: void; +begin + resize := false; + if bytes < list^.Count * list^.ElementSize then exit; + if bytes = list^.DataSize then begin + resize := true; + exit; + end; + data := nil; + if bytes <> 0 then begin + data := kalloc(bytes); + if data = nil then exit; + memory.raw.move(pointer(list^.Data), pointer(data), list^.Count * list^.ElementSize); + end; + kfree(list^.Data); + list^.Data := data; + list^.DataSize := bytes; + resize := true; +end; + +function ensure(list: PDList; count: uint32): boolean; +var + bytes, capacity: uint32; +begin + ensure := false; + if (list = nil) or (list^.ElementSize = 0) then exit; + if count > High(uint32) div list^.ElementSize then exit; + bytes := count * list^.ElementSize; + if bytes <= list^.DataSize then begin + ensure := true; + exit; + end; + capacity := list^.DataSize; + if capacity <= High(uint32) div 2 then capacity := capacity * 2; + if capacity < bytes then capacity := bytes; + ensure := resize(list, capacity); +end; + +function DL_New(ElementSize: uint32): PDList; +var + list: PDList; + count: uint32; +begin + DL_New := nil; + if (ElementSize = 0) or (ElementSize > High(uint32) div 4) then exit; + list := PDList(kalloc(sizeof(TDList))); + if list = nil then exit; + list^.ElementSize := ElementSize; + if ElementSize > 128 then count := 4 else + if ElementSize > 64 then count := 8 else + if ElementSize > 32 then count := 16 else + if ElementSize > 16 then count := 32 else count := 64; + if not ensure(list, count) then begin + kfree(void(list)); + exit; + end; + DL_New := list; +end; + +function DL_Insert(DList: PDList; idx: uint32): void; +var + address: PtrUInt; +begin + DL_Insert := nil; + if DList = nil then exit; + if (idx > DList^.Count) or (DList^.Count = High(uint32)) then exit; + if not ensure(DList, DList^.Count + 1) then exit; + address := PtrUInt(DList^.Data) + SizeUInt(idx) * DList^.ElementSize; + memory.raw.move(pointer(address), pointer(address + DList^.ElementSize), + (DList^.Count - idx) * DList^.ElementSize); + memory.raw.fill(pointer(address), 0, DList^.ElementSize); + inc(DList^.Count); + DL_Insert := void(address); +end; + +function DL_Add(DList: PDList): void; +begin + DL_Add := nil; + if DList <> nil then DL_Add := DL_Insert(DList, DList^.Count); +end; + +function DL_Delete(DList: PDList; idx: uint32): boolean; +var + address: PtrUInt; +begin + DL_Delete := false; + if DList = nil then exit; + if idx >= DList^.Count then exit; + address := PtrUInt(DList^.Data) + SizeUInt(idx) * DList^.ElementSize; + memory.raw.move(pointer(address + DList^.ElementSize), pointer(address), + (DList^.Count - idx - 1) * DList^.ElementSize); + dec(DList^.Count); + { Retain capacity; deletion in the scheduler must not allocate or thrash. + Explicit DL_ShrinkToFit remains available at a suitable safe point. } + DL_Delete := true; +end; + +function DL_Get(DList: PDList; idx: uint32): void; +begin + DL_Get := nil; + if DList = nil then exit; + if idx < DList^.Count then + DL_Get := void(PtrUInt(DList^.Data) + SizeUInt(idx) * DList^.ElementSize); +end; + +function DL_Set(DList: PDList; idx: uint32; elm: PUInt32): boolean; +var + target: void; +begin + target := DL_Get(DList, idx); + DL_Set := (target <> nil) and (elm <> nil); + if DL_Set then memory.raw.move(pointer(elm), pointer(target), DList^.ElementSize); +end; + +function DL_Size(DList: PDList): uint32; +begin + DL_Size := 0; + if DList <> nil then DL_Size := DList^.Count; +end; + +procedure DL_Free(DList: PDList); +begin + if DList = nil then exit; + kfree(DList^.Data); + kfree(void(DList)); +end; + +function DL_IndexOf(DList: PDList; elm: PUInt32): uint32; +var + offset: PtrUInt; +begin + DL_IndexOf := High(uint32); + if (DList = nil) or (elm = nil) then exit; + if PtrUInt(elm) < PtrUInt(DList^.Data) then exit; + offset := PtrUInt(elm) - PtrUInt(DList^.Data); + if (offset < SizeUInt(DList^.Count) * DList^.ElementSize) and + (offset mod DList^.ElementSize = 0) then DL_IndexOf := offset div DList^.ElementSize; +end; + +function DL_Contains(DList: PDList; elm: PUInt32): boolean; +begin + DL_Contains := DL_IndexOf(DList, elm) <> High(uint32); +end; + +function DL_Concat(DList1, DList2: PDList): PDList; +var + count, oldCount: uint32; +begin + DL_Concat := nil; + if (DList1 = nil) or (DList2 = nil) then exit; + if DList1^.ElementSize <> DList2^.ElementSize then exit; + count := DList2^.Count; + oldCount := DList1^.Count; + if count > High(uint32) - oldCount then exit; + if not ensure(DList1, oldCount + count) then exit; + memory.raw.move(pointer(DList2^.Data), + pointer(PtrUInt(DList1^.Data) + SizeUInt(oldCount) * DList1^.ElementSize), + count * DList1^.ElementSize); + DList1^.Count := oldCount + count; + DL_Concat := DList1; +end; + +procedure DL_Clear(DList: PDList); +begin + if DList <> nil then DList^.Count := 0; +end; + +function DL_Capacity(DList: PDList): uint32; +begin + DL_Capacity := 0; + if DList <> nil then DL_Capacity := DList^.DataSize div DList^.ElementSize; +end; + +procedure DL_Reserve(DList: PDList; NewCapacity: uint32); +begin + ensure(DList, NewCapacity); +end; + +procedure DL_ShrinkToFit(DList: PDList); +begin + if DList <> nil then resize(DList, DList^.Count * DList^.ElementSize); +end; +end. diff --git a/src/core/ds/core.ds.hashmap.pas b/src/core/ds/core.ds.hashmap.pas index 68402dd6..56274a26 100644 --- a/src/core/ds/core.ds.hashmap.pas +++ b/src/core/ds/core.ds.hashmap.pas @@ -1,4 +1,5 @@ // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,9 +27,7 @@ uses memory.heap, core.enc.fnv1a, core.strings, - io.syslog, - debug.tracer, - core.util, arch.x86.util; + arch.platform.diagnostics; type DPHashItem = ^PHashItem; @@ -59,6 +58,7 @@ function newEx(size : uint32; loadFactor : Single) : PHashMap; procedure add(map : PHashMap; key : pchar; value : void); function get(map : PHashMap; key : pchar) : void; procedure delete(map : PHashMap; key : pchar; freeItem : boolean); +procedure UnitTest; procedure printMap(map : PHashMap); procedure forEach(map : PHashMap; cb : THashForEachCb; ud : void); @@ -66,7 +66,7 @@ implementation function KeyHash(key : pchar) : uint32; begin - KeyHash := Hash_FNV1a32(void(uint32(key)), StringSize(key)); + KeyHash := Hash_FNV1a32(void(key), StringSize(key)); end; function hashIndex(size : uint32; hash : uint32) : uint32; @@ -74,31 +74,10 @@ begin hashIndex:= hash mod size; end; -function newItem : PHashItem; -begin - newItem:= PHashItem(kalloc(sizeof(THashItem))); - newItem^.Next:= nil; - newItem^.Key:= nil; - newItem^.Data:= nil; - newItem^.Hash:= 0; -end; - procedure putItem(table : DPHashItem; item : PHashItem; idx : uint32); -var - ExistingItem : PHashItem; - c : uint32; - begin - ExistingItem:= table[idx]; - if ExistingItem = nil then begin - table[idx]:= item; - end else begin - c:=0; - while ExistingItem^.Next <> nil do begin - ExistingItem:= ExistingItem^.Next; - end; - ExistingItem^.Next:= item; - end; + item^.Next := table[idx]; + table[idx] := item; end; procedure checkAndRebase(map : PHashMap); @@ -117,12 +96,11 @@ begin loadFactor:= map^.LoadFactor; max_load:= size * loadFactor; if map^.count > max_load then begin - NewSize:= map^.Size * 2; - NewTable:= DPHashItem(kalloc(sizeof(PHashItem) * NewSize)); + if map^.Size > High(uint32) div 2 then exit; + NewSize := map^.Size * 2; + if SizeUInt(NewSize) > High(SizeUInt) div sizeof(PHashItem) then exit; + NewTable:= DPHashItem(kalloc(SizeUInt(NewSize) * sizeof(PHashItem))); If NewTable <> nil then begin - for i:=0 to NewSize-1 do begin - NewTable[i]:= nil; - end; for i:=0 to map^.size-1 do begin item:= map^.table[i]; while item <> nil do begin @@ -141,40 +119,34 @@ end; procedure add(map : PHashMap; key : pchar; value : void); var - Idx : uint32; - hash : uint32; - Item : PHashItem; - nItem : PHashItem; - + idx, hash: uint32; + item: PHashItem; begin - if (map <> nil) and (key <> nil) then begin - hash:= KeyHash(key); - Idx:= hashIndex(map^.size, hash); - if map^.Table[Idx] = nil then begin - Item:= newItem; - Item^.Next:= nil; - map^.Table[Idx]:= Item; - end else begin - //Collision - Item:= map^.Table[Idx]; - while not StringEquals(Item^.key, key) do begin - if Item^.Next <> nil then begin - Item:= Item^.Next; - end else begin - nItem:= newItem(); - Item^.Next:= nItem; - Item:= nItem; - Item^.Next:= nil; - break; - end; - end; + if (map = nil) or (key = nil) then exit; + + hash := KeyHash(key); + idx := hashIndex(map^.Size, hash); + item := map^.Table[idx]; + while item <> nil do begin + if (item^.Hash = hash) and StringEquals(item^.Key, key) then begin + item^.Data := value; + exit; end; - if Item^.Key = nil then Item^.Key:= stringCopy(key); - Item^.Data:= value; - Item^.Hash:= hash; - inc(map^.count); - checkAndRebase(map); + item := item^.Next; end; + + item := PHashItem(kalloc(sizeof(THashItem))); + if item = nil then exit; + item^.Key := stringCopy(key); + if item^.Key = nil then begin + kfree(void(item)); + exit; + end; + item^.Data := value; + item^.Hash := hash; + putItem(map^.Table, item, idx); + inc(map^.Count); + checkAndRebase(map); end; function get(map : PHashMap; key : pchar) : void; @@ -200,19 +172,22 @@ end; function newEx(size : uint32; loadFactor : Single) : PHashMap; var - Map : PHashMap; - i : uint32; - + map: PHashMap; begin - Map:= PHashMap(kalloc(sizeof(THashMap))); - Map^.size:= size; - Map^.Table:= DPHashItem(Kalloc(sizeof(PHashItem) * Size)); - Map^.LoadFactor:= loadFactor; - Map^.Count:= 0; - for i:=0 to size-1 do begin - Map^.Table[i]:= nil; + newEx := nil; + if (size = 0) or (SizeUInt(size) > High(SizeUInt) div sizeof(PHashItem)) then exit; + if not (loadFactor > 0) then exit; + + map := PHashMap(kalloc(sizeof(THashMap))); + if map = nil then exit; + map^.Table := DPHashItem(kalloc(SizeUInt(size) * sizeof(PHashItem))); + if map^.Table = nil then begin + kfree(void(map)); + exit; end; - newEx:= Map; + map^.Size := size; + map^.LoadFactor := loadFactor; + newEx := map; end; function new : PHashMap; @@ -275,31 +250,79 @@ end; procedure printMap(map : PHashMap); var - i,c : uint32; - item : PHashItem; - + i: uint32; + item: PHashItem; begin - for i:=0 to map^.Size-1 do begin - if map^.table[i] <> nil then begin - io.syslog.writestring('Map['); - io.syslog.writeint(i); - io.syslog.writestring(']->(0)="'); - io.syslog.writestring(map^.table[i]^.key); - io.syslog.writestring('"'); - item:= map^.table[i]^.next; - c:=1; - while item <> nil do begin - io.syslog.writestring('->('); - io.syslog.writeint(c); - io.syslog.writestring(')="'); - io.syslog.writestring(item^.key); - io.syslog.writestring('"'); - inc(c); - item:= item^.next; - end; - io.syslog.writestringln(' '); + if (map = nil) or (map^.Size = 0) then exit; + + for i := 0 to map^.Size - 1 do begin + item := map^.Table[i]; + while item <> nil do begin + log('HASHMAP', item^.Key); + item := item^.Next; end; end; end; -end. \ No newline at end of file +procedure UnitTest; +var + map: PHashMap; + value: void; + key: pchar; + i, passed, failed: uint32; + beforeFree: SizeUInt; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then + inc(passed) + else begin + inc(failed); + log('HASHMAP', name); + end; + end; +begin + passed := 0; + failed := 0; + beforeFree := lmm_total_free; + value := kalloc(sizeof(PtrUInt)); + map := newEx(1, 100); + add(map, 'a', value); + add(map, 'b', value); + add(map, 'c', value); + Assert((map^.Count = 3) and (get(map, 'a') = value) and + (get(map, 'c') = value), 'FAIL: native hash collision chain'); + add(map, 'b', nil); + Assert((map^.Count = 3) and (get(map, 'b') = nil), 'FAIL: hash replacement count'); + delete(map, 'b', false); + Assert((map^.Count = 2) and (get(map, 'a') = value) and + (get(map, 'c') = value), 'FAIL: hash collision middle deletion'); + delete(map, 'c', false); + delete(map, 'a', false); + Assert(map^.Count = 0, 'FAIL: hash collision head and tail deletion'); + kfree(void(map^.Table)); + kfree(void(map)); + + map := new; + for i := 0 to 39 do begin + key := intToString(i); + add(map, key, value); + kfree(void(key)); + end; + Assert((map^.Count = 40) and (map^.Size > HASHMAP_DEFAULT_SIZE), + 'FAIL: hash growth'); + for i := 0 to 39 do begin + key := intToString(i); + Assert(get(map, key) = value, 'FAIL: hash rebase preserves high pointers'); + delete(map, key, false); + kfree(void(key)); + end; + kfree(void(map^.Table)); + kfree(void(map)); + kfree(value); + Assert(newEx(0, 0.75) = nil, 'FAIL: zero-sized hash table rejected'); + Assert(lmm_total_free = beforeFree, 'FAIL: hash allocation accounting'); + testSummary('HASHMAP', 'HASHMAP_TESTS_PASSED', 'HASHMAP_TESTS_FAILED', passed, failed); +end; + +end. diff --git a/src/core/ds/core.ds.lists.pas b/src/core/ds/core.ds.lists.pas index 7f78e468..17555c5b 100644 --- a/src/core/ds/core.ds.lists.pas +++ b/src/core/ds/core.ds.lists.pas @@ -2,6 +2,7 @@ // core.ds.lists.pas // // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,65 +23,26 @@ unit core.ds.lists; interface uses - io.syslog, + core.ds.types, core.ds.dynamic, memory.heap, - debug.tracer, - core.util, arch.x86.util; + arch.platform.diagnostics, + core.util; type {** @abstract Pointer to a managed linked list node. **} - PLinkedList = ^TLinkedList; - - {** - @abstract Managed linked list node record. - @field Previous Pointer to the previous node. - @field Data Pointer to the stored data. - @field Next Pointer to the next node. - **} - TLinkedList = record - Previous : PLinkedList; - Data : void; - Next : PLinkedList; - end; - - {** - @abstract Pointer to the base of a managed linked list. - **} - PLinkedListBase = ^TLinkedListBase; - - {** - @abstract Base record for a managed linked list. - @field Count Number of elements in the list. - @field Head Pointer to the first node. - @field ElementSize Size (in bytes) of each element. - **} - TLinkedListBase = record - Count : uint32; - Head : PLinkedList; - ElementSize : uint32; - end; + PLinkedList = core.ds.types.PLinkedList; + TLinkedList = core.ds.types.TLinkedList; + PLinkedListBase = core.ds.types.PLinkedListBase; + TLinkedListBase = core.ds.types.TLinkedListBase; {** @abstract Pointer to a dynamic list. **} - PDList = ^TDList; + PDList = core.ds.dynamic.PDList; + TDList = core.ds.dynamic.TDList; - {** - @abstract Record for a dynamic list. - @field Count Number of elements in use. - @field Data Pointer to the underlying buffer. - @field ElementSize Size (in bytes) of each element. - @field DataSize Total allocated bytes for Data. - **} - TDList = record - Count : uint32; // number of elements in use - Data : void; // pointer to the underlying buffer - ElementSize : uint32; // size of each element in bytes - DataSize : uint32; // total allocated bytes for Data - end; - { Function/Procedure definitions } // ******************* String Linked List Routines ******************* @@ -325,6 +287,9 @@ implementation uses core.strings; +type + PStringSlot = ^pchar; + { ---------------------------------------------------------------------------- } { Managed Linked List } { ---------------------------------------------------------------------------- } @@ -337,6 +302,7 @@ uses function LL_New(ElementSize : uint32) : PLinkedListBase; begin LL_New := PLinkedListBase(kalloc(sizeof(TLinkedListBase))); + if LL_New = nil then exit; LL_New^.ElementSize := ElementSize; LL_New^.Count := 0; LL_New^.Head := nil; @@ -348,38 +314,10 @@ end; @returns Pointer to the newly allocated data space. **} function LL_Add(LinkedList : PLinkedListBase) : Void; -var - Element : PLinkedList; - Base : PLinkedList; begin - if LinkedList^.Head = nil then - begin - Element := PLinkedList(kalloc(sizeof(TLinkedList))); - Element^.Previous := nil; - Element^.Next := nil; - Element^.Data := kalloc(LinkedList^.ElementSize); - memset(uint32(Element^.Data), 0, LinkedList^.ElementSize); - - LinkedList^.Head := Element; - LinkedList^.Count := 1; - LL_Add := Element^.Data; - end - else - begin - Base := LinkedList^.Head; - while Base^.Next <> nil do - Base := Base^.Next; - - Element := PLinkedList(kalloc(sizeof(TLinkedList))); - Element^.Previous := Base; - Element^.Next := nil; - Element^.Data := kalloc(LinkedList^.ElementSize); - memset(uint32(Element^.Data), 0, LinkedList^.ElementSize); - - Base^.Next := Element; - LinkedList^.Count := LinkedList^.Count + 1; - LL_Add := Element^.Data; - end; + LL_Add := nil; + if LinkedList <> nil then + LL_Add := LL_Insert(LinkedList, LinkedList^.Count); end; {** @@ -443,63 +381,37 @@ end; **} function LL_Insert(LinkedList : PLinkedListBase; idx : uint32) : void; var - i : uint32; - Base : PLinkedList; - Prev, Next : PLinkedList; - Element : PLinkedList; + i: uint32; + next, prev, element: PLinkedList; begin - LL_Insert := nil; - if idx > LinkedList^.Count then - Exit; + LL_Insert := nil; + if LinkedList = nil then exit; + if (idx > LinkedList^.Count) or (LinkedList^.Count = High(uint32)) then exit; - Base := LinkedList^.Head; - i := 0; - while (i < idx) and (Base <> nil) do - begin - Base := Base^.Next; - Inc(i); - end; + prev := nil; + next := LinkedList^.Head; + for i := 1 to idx do begin + prev := next; + next := next^.Next; + end; - // Insert at head - if i = 0 then - begin - Element := PLinkedList(kalloc(sizeof(TLinkedList))); - Element^.Data := kalloc(LinkedList^.ElementSize); - memset(uint32(Element^.Data), 0, LinkedList^.ElementSize); - Element^.Next := LinkedList^.Head; - Element^.Previous := nil; - if LinkedList^.Head <> nil then - LinkedList^.Head^.Previous := Element; - LinkedList^.Head := Element; - LinkedList^.Count := LinkedList^.Count + 1; - LL_Insert := Element^.Data; - end - else - begin - // Insert in the middle or end - if Base = nil then - Exit; - Prev := Base^.Previous; - Next := Base; - - Element := PLinkedList(kalloc(sizeof(TLinkedList))); - Element^.Data := kalloc(LinkedList^.ElementSize); - memset(uint32(Element^.Data), 0, LinkedList^.ElementSize); - - Element^.Previous := Prev; - Element^.Next := Next; - - if Prev = nil then - LinkedList^.Head := Element + element := PLinkedList(kalloc(sizeof(TLinkedList))); + if element = nil then exit; + element^.Data := kalloc(LinkedList^.ElementSize); + if element^.Data = nil then begin + kfree(void(element)); + exit; + end; + element^.Previous := prev; + element^.Next := next; + if prev = nil then + LinkedList^.Head := element else - Prev^.Next := Element; - - if Next <> nil then - Next^.Previous := Element; - - LinkedList^.Count := LinkedList^.Count + 1; - LL_Insert := Element^.Data; - end; + prev^.Next := element; + if next <> nil then + next^.Previous := element; + inc(LinkedList^.Count); + LL_Insert := element^.Data; end; {** @@ -548,40 +460,8 @@ end; @returns Pointer to the resulting linked list. **} function LL_FromString(str : pchar; delimter : char) : PLinkedListBase; -var - list : PLinkedListBase; - head, tail : pchar; - size : uint32; - null_delim : boolean; - elm : puint32; - out_str : pchar; begin - list := LL_New(sizeof(uint32)); - LL_FromString := list; - - head := str; - tail := head; - null_delim := false; - - while not null_delim do - begin - if (head^ = delimter) or (head^ = char(0)) then - begin - if head^ = char(0) then - null_delim := true; - size := uint32(head) - uint32(tail); - if size > 0 then - begin - elm := puint32(LL_Add(list)); - out_str := stringNew(size + 1); - // Copy from tail -> out_str - memcpy(uint32(tail), uint32(out_str), size); - elm^ := uint32(out_str); - end; - tail := head + 1; - end; - Inc(head); - end; + LL_FromString := STRLL_FromString(str, delimter); end; { ---------------------------------------------------------------------------- } @@ -595,10 +475,10 @@ end; **} procedure STRLL_Add(LinkedList : PLinkedListBase; str : pchar); var - ptr : puint32; + ptr : PStringSlot; begin - ptr := puint32(LL_Add(LinkedList)); - ptr^ := uint32(str); + ptr := PStringSlot(LL_Add(LinkedList)); + if ptr <> nil then ptr^ := str; end; {** @@ -609,9 +489,9 @@ end; **} function STRLL_Get(LinkedList : PLinkedListBase; idx : uint32) : pchar; var - ptr : puint32; + ptr : PStringSlot; begin - ptr := puint32(LL_Get(LinkedList, idx)); + ptr := PStringSlot(LL_Get(LinkedList, idx)); STRLL_Get := nil; if ptr <> nil then STRLL_Get := pchar(ptr^); @@ -623,7 +503,7 @@ end; **} function STRLL_New : PLinkedListBase; begin - STRLL_New := LL_New(sizeof(uint32)); + STRLL_New := LL_New(sizeof(pchar)); end; {** @@ -646,11 +526,12 @@ procedure STRLL_Delete(LinkedList : PLinkedListBase; idx : uint32); var ptr : pchar; begin - debug.tracer.push_trace('core.ds.lists.STRLL_Delete (Don''t add static core.strings)'); + arch.platform.diagnostics.push_trace('core.ds.lists.STRLL_Delete (Don''t add static core.strings)'); ptr := STRLL_Get(LinkedList, idx); if ptr <> nil then kfree(void(ptr)); LL_Delete(LinkedList, idx); + pop_trace; end; {** @@ -683,38 +564,46 @@ end; **} function STRLL_FromString(str : pchar; delimter : char) : PLinkedListBase; var - list : PLinkedListBase; - head, tail : pchar; - size : uint32; - null_delim : boolean; - elm : puint32; - out_str : pchar; + list: PLinkedListBase; + head, tail, text: pchar; + size: SizeUInt; + slot: PStringSlot; begin - list := LL_New(sizeof(uint32)); - STRLL_FromString := list; + STRLL_FromString := nil; + if str = nil then exit; + list := STRLL_New; + if list = nil then exit; - head := str; - tail := head; - null_delim := false; - - while not null_delim do - begin - if (head^ = delimter) or (head^ = char(0)) then - begin - if head^ = char(0) then - null_delim := true; - size := uint32(head) - uint32(tail); - if size > 0 then - begin - elm := puint32(LL_Add(list)); - out_str := stringNew(size + 1); - memcpy(uint32(tail), uint32(out_str), size); - elm^ := uint32(out_str); - end; - tail := head + 1; - end; - Inc(head); - end; + head := str; + tail := str; + repeat + if (head^ = delimter) or (head^ = #0) then begin + size := PtrUInt(head) - PtrUInt(tail); + if size > 0 then begin + if size >= High(uint32) then begin + STRLL_Free(list); + exit; + end; + text := stringNew(uint32(size)); + if text = nil then begin + STRLL_Free(list); + exit; + end; + slot := PStringSlot(LL_Add(list)); + if slot = nil then begin + kfree(void(text)); + STRLL_Free(list); + exit; + end; + memcpy(PtrUInt(tail), PtrUInt(text), size); + slot^ := text; + end; + tail := head + 1; + end; + if head^ = #0 then break; + inc(head); + until false; + STRLL_FromString := list; end; { ---------------------------------------------------------------------------- } @@ -727,500 +616,132 @@ end; @returns Pointer to the newly allocated dynamic list. **} function DL_New(ElementSize : uint32) : PDList; -var - DL : PDList; - initialBytes : uint32; begin - DL := PDList(kalloc(sizeof(TDList))); - DL^.ElementSize := ElementSize; - DL^.Count := 0; - - // Pick an initial capacity based on the element size - if ElementSize > 128 then - initialBytes := ElementSize * 4 - else if ElementSize > 64 then - initialBytes := ElementSize * 8 - else if ElementSize > 32 then - initialBytes := ElementSize * 16 - else if ElementSize > 16 then - initialBytes := ElementSize * 32 - else - initialBytes := ElementSize * 64; - - DL^.Data := kalloc(initialBytes); - DL^.DataSize := initialBytes; - - DL_New := DL; + DL_New := core.ds.dynamic.DL_New(ElementSize); end; -{** - @abstract Adds a new element to the dynamic list. - @param DList Pointer to the dynamic list. - @returns Pointer to the newly allocated slot. - @discussion Resizes the underlying buffer (doubling its size) if necessary. -**} function DL_Add(DList : PDList) : Void; -var - newElem : pointer; - oldData : pointer; - oldSize : uint32; begin - // Check if we need to resize - if ((DList^.Count + 1) * DList^.ElementSize) > DList^.DataSize then - begin - push_trace('core.ds.lists.DL_Add: Resizing'); - oldData := DList^.Data; - oldSize := DList^.DataSize; - - DList^.DataSize := DList^.DataSize * 2; - DList^.Data := kalloc(DList^.DataSize); - memset(uint32(DList^.Data), 0, DList^.DataSize); - - // Copy old data into the new buffer - memcpy(uint32(oldData), uint32(DList^.Data), oldSize); - - kfree(oldData); - end; - - // Address of the new element - newElem := pointer(uint32(DList^.Data) + (DList^.Count * DList^.ElementSize)); - // Zero out the newly added slot - memset(uint32(newElem), 0, DList^.ElementSize); - - DList^.Count := DList^.Count + 1; - DL_Add := newElem; + DL_Add := core.ds.dynamic.DL_Add(DList); end; -{** - @abstract Deletes the element at the specified index from the dynamic list. - @param DList Pointer to the dynamic list. - @param idx Zero-based index of the element to delete. - @returns True if deletion was successful, false otherwise. - @discussion Shifts subsequent elements left and shrinks the buffer if underutilized. -**} function DL_Delete(DList : PDList; idx : uint32) : boolean; -var - sourcePtr, destPtr : pointer; - shiftSize : uint32; - oldData : pointer; - oldSize : uint32; begin - if (idx >= DList^.Count) then - begin - DL_Delete := false; - exit; - end; - - // If not removing the last element, shift the tail left by one element. - if idx < (DList^.Count - 1) then - begin - sourcePtr := pointer(uint32(DList^.Data) + ((idx + 1) * DList^.ElementSize)); - destPtr := pointer(uint32(DList^.Data) + (idx * DList^.ElementSize)); - shiftSize := (DList^.Count - idx - 1) * DList^.ElementSize; - memcpy(uint32(sourcePtr), uint32(destPtr), shiftSize); - end; - - // Decrement element count - DList^.Count := DList^.Count - 1; - - // Possibly shrink the buffer if usage is very low - if (DList^.Count > 0) and ((DList^.Count * DList^.ElementSize) < (DList^.DataSize div 2)) then - begin - oldData := DList^.Data; - oldSize := DList^.DataSize; - - DList^.DataSize := DList^.DataSize div 2; - if DList^.DataSize < (DList^.Count * DList^.ElementSize) then - DList^.DataSize := (DList^.Count * DList^.ElementSize); - - DList^.Data := kalloc(DList^.DataSize); - memset(uint32(DList^.Data), 0, DList^.DataSize); - - memcpy(uint32(oldData), uint32(DList^.Data), (DList^.Count * DList^.ElementSize)); - kfree(oldData); - end; - - DL_Delete := true; + DL_Delete := core.ds.dynamic.DL_Delete(DList, idx); end; -{** - @abstract Retrieves the element data at the specified index in the dynamic list. - @param DList Pointer to the dynamic list. - @param idx Zero-based index of the desired element. - @returns Pointer to the element data, or nil if index is out of range. -**} function DL_Get(DList : PDList; idx : uint32) : Void; begin - if (idx >= DList^.Count) then - begin - DL_Get := nil; - exit; - end; - - DL_Get := pointer(uint32(DList^.Data) + (idx * DList^.ElementSize)); + DL_Get := core.ds.dynamic.DL_Get(DList, idx); end; -{** - @abstract Sets the element at the specified index by copying data from the provided pointer. - @param DList Pointer to the dynamic list. - @param idx Zero-based index of the element to set. - @param elm Pointer to the source data. - @returns True if the operation was successful, false otherwise. -**} function DL_Set(DList : PDList; idx : uint32; elm : puint32) : boolean; -var - targetPtr : pointer; begin - if (idx >= DList^.Count) then - begin - DL_Set := false; - exit; - end; - - // Copy the data from elm into the dynamic list - targetPtr := pointer(uint32(DList^.Data) + (idx * DList^.ElementSize)); - memcpy(uint32(elm), uint32(targetPtr), DList^.ElementSize); - - DL_Set := true; + DL_Set := core.ds.dynamic.DL_Set(DList, idx, elm); end; -{** - @abstract Returns the number of elements currently in the dynamic list. - @param DList Pointer to the dynamic list. - @returns The element count. -**} function DL_Size(DList : PDList) : uint32; begin - DL_Size := DList^.Count; + DL_Size := core.ds.dynamic.DL_Size(DList); end; -{** - @abstract Frees the entire dynamic list and its underlying buffer. - @param DList Pointer to the dynamic list. -**} procedure DL_Free(DList : PDList); begin - if DList = nil then exit; - if DList^.Data <> nil then - kfree(DList^.Data); - kfree(void(DList)); + core.ds.dynamic.DL_Free(DList); end; -{** - @abstract Searches for an element in the dynamic list. - @param DList Pointer to the dynamic list. - @param elm Pointer to the element to find. - @returns The zero-based index of the element if found; otherwise, returns -1. - @discussion The search is based on pointer equality. -**} function DL_IndexOf(DList : PDList; elm : puint32) : uint32; -var - i : uint32; - temp : puint32; begin - for i := 0 to DList^.Count - 1 do - begin - temp := puint32(uint32(DList^.Data) + (i * DList^.ElementSize)); - if (temp = elm) then - begin - DL_IndexOf := i; - exit; - end; - end; - DL_IndexOf := uint32(-1); + DL_IndexOf := core.ds.dynamic.DL_IndexOf(DList, elm); end; -{** - @abstract Determines whether the dynamic list contains the specified element. - @param DList Pointer to the dynamic list. - @param elm Pointer to the element to search for. - @returns True if the element is present, false otherwise. -**} function DL_Contains(DList : PDList; elm : puint32) : boolean; begin - DL_Contains := (DL_IndexOf(DList, elm) <> uint32(-1)); + DL_Contains := core.ds.dynamic.DL_Contains(DList, elm); end; -{** - @abstract Concatenates two dynamic lists. - @param DList1 Pointer to the destination dynamic list. - @param DList2 Pointer to the source dynamic list. - @returns Pointer to the concatenated dynamic list (DList1), or nil if element sizes differ. -**} function DL_Concat(DList1 : PDList; DList2 : PDList) : PDList; -var - i : uint32; - ptr : pointer; begin - if DList1^.ElementSize <> DList2^.ElementSize then - begin - DL_Concat := nil; - exit; - end; - - for i := 0 to DList2^.Count - 1 do - begin - ptr := DL_Add(DList1); - memcpy(uint32(DL_Get(DList2, i)), uint32(ptr), DList1^.ElementSize); - end; - - DL_Concat := DList1; + DL_Concat := core.ds.dynamic.DL_Concat(DList1, DList2); end; -{** - @abstract Clears the dynamic list by resetting the count to zero. - @param DList Pointer to the dynamic list. - @discussion The underlying buffer is retained for fast reuse. -**} procedure DL_Clear(DList : PDList); begin - if (DList = nil) then exit; - DList^.Count := 0; - // To clear memory, uncomment the following line: - // memset(uint32(DList^.Data), 0, DList^.DataSize); + core.ds.dynamic.DL_Clear(DList); end; -{** - @abstract Inserts a new element at the specified index in the dynamic list. - @param DList Pointer to the dynamic list. - @param idx Zero-based index where the element should be inserted. - @returns Pointer to the newly allocated slot, or nil if idx is out of range. - @discussion Subsequent elements are shifted right. The buffer is resized if needed. -**} function DL_Insert(DList : PDList; idx : uint32) : Void; -var - newElem : pointer; - oldData : pointer; - oldSize : uint32; - shiftSrc : pointer; - shiftDest : pointer; - shiftSize : uint32; begin - DL_Insert := nil; - if (DList = nil) then exit; - if (idx > DList^.Count) then - exit; // cannot insert beyond the current count - - // Ensure there's space for one more element - if ((DList^.Count + 1) * DList^.ElementSize) > DList^.DataSize then - begin - oldData := DList^.Data; - oldSize := DList^.DataSize; - - DList^.DataSize := DList^.DataSize * 2; - DList^.Data := kalloc(DList^.DataSize); - memset(uint32(DList^.Data), 0, DList^.DataSize); - - memcpy(uint32(oldData), uint32(DList^.Data), oldSize); - kfree(oldData); - end; - - // Shift elements to the right if inserting in the middle - if (idx < DList^.Count) then - begin - shiftSrc := pointer(uint32(DList^.Data) + (idx * DList^.ElementSize)); - shiftDest := pointer(uint32(DList^.Data) + ((idx + 1) * DList^.ElementSize)); - shiftSize := (DList^.Count - idx) * DList^.ElementSize; - // Shift overlapping regions; if memcpy doesn't handle overlap, use memmove. - memcpy(uint32(shiftSrc), uint32(shiftDest), shiftSize); - end; - - // Allocate the new element slot - newElem := pointer(uint32(DList^.Data) + (idx * DList^.ElementSize)); - memset(uint32(newElem), 0, DList^.ElementSize); - - DList^.Count := DList^.Count + 1; - DL_Insert := newElem; + DL_Insert := core.ds.dynamic.DL_Insert(DList, idx); end; -{** - @abstract Returns the current capacity (number of elements) that the dynamic list can hold. - @param DList Pointer to the dynamic list. - @returns The capacity based on the allocated buffer size. -**} function DL_Capacity(DList : PDList) : uint32; begin - if DList = nil then - DL_Capacity := 0 - else - DL_Capacity := (DList^.DataSize div DList^.ElementSize); + DL_Capacity := core.ds.dynamic.DL_Capacity(DList); end; -{** - @abstract Ensures the dynamic list has enough space for at least NewCapacity elements. - @param DList Pointer to the dynamic list. - @param NewCapacity Desired capacity (in number of elements). - @discussion Reallocates the buffer if necessary. -**} procedure DL_Reserve(DList : PDList; NewCapacity : uint32); -var - neededBytes : uint32; - oldData : pointer; - oldSize : uint32; begin - if (DList = nil) then exit; - - neededBytes := NewCapacity * DList^.ElementSize; - if (neededBytes <= DList^.DataSize) then - exit; // Already sufficient - - oldData := DList^.Data; - oldSize := DList^.DataSize; - - DList^.DataSize := neededBytes; - DList^.Data := kalloc(DList^.DataSize); - - memset(uint32(DList^.Data), 0, DList^.DataSize); - memcpy(uint32(oldData), uint32(DList^.Data), oldSize); - - kfree(oldData); + core.ds.dynamic.DL_Reserve(DList, NewCapacity); end; -{** - @abstract Shrinks the dynamic list's allocated memory to fit its current count. - @param DList Pointer to the dynamic list. - @discussion If the list is empty, the buffer is freed. -**} procedure DL_ShrinkToFit(DList : PDList); -var - exactBytes : uint32; - oldData : pointer; begin - if (DList = nil) then exit; - exactBytes := DList^.Count * DList^.ElementSize; - if (exactBytes = 0) then - begin - // For an empty list, free the buffer and reset values. - kfree(DList^.Data); - DList^.Data := nil; - DList^.DataSize := 0; - exit; - end; - - if exactBytes >= DList^.DataSize then exit; // Already optimal - - oldData := DList^.Data; - DList^.Data := kalloc(exactBytes); - - memcpy(uint32(oldData), uint32(DList^.Data), exactBytes); - kfree(oldData); - DList^.DataSize := exactBytes; + core.ds.dynamic.DL_ShrinkToFit(DList); end; procedure TestAllLists; var - i : uint32; - p : pointer; - ll : PLinkedListBase; // Managed linked list - strList: PLinkedListBase; // String linked list - dlist : PDList; // Dynamic list - intPtr : puint32; // For dynamic list integer values - testStr: pchar; - testStr2: pchar; + list: PLinkedListBase; + slot: puint32; + source: pchar; + beforeFree: SizeUInt; + passed, failed: uint32; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then + inc(passed) + else begin + inc(failed); + log('LISTS', name); + end; + end; begin - { --- Test Managed Linked List --- } - io.syslog.writeString('--- Testing Managed Linked List ---'); - ll := LL_New(sizeof(uint32)); - for i := 1 to 3 do - begin - p := LL_Add(ll); - puint32(p)^ := i; // Store the value (1, 2, 3) - end; - io.syslog.writeString('Managed Linked List Size: '); - io.syslog.writeintln(LL_Size(ll)); - for i := 0 to LL_Size(ll) - 1 do - begin - p := LL_Get(ll, i); - io.syslog.writeString('Element '); - io.syslog.writeint(i); - io.syslog.writeString(': '); - io.syslog.writeintln(puint32(p)^); - end; - LL_Free(ll); + passed := 0; + failed := 0; + beforeFree := lmm_total_free; + list := LL_New(sizeof(uint32)); + slot := puint32(LL_Add(list)); + slot^ := 10; + slot := puint32(LL_Insert(list, 0)); + slot^ := 5; + slot := puint32(LL_Insert(list, 2)); + slot^ := 20; + Assert((LL_Size(list) = 3) and (puint32(LL_Get(list, 0))^ = 5) and + (puint32(LL_Get(list, 2))^ = 20), 'FAIL: list head and tail insertion'); + Assert(LL_Delete(list, 1) and (puint32(LL_Get(list, 1))^ = 20), + 'FAIL: list middle deletion'); + Assert((LL_Get(list, 2) = nil) and (LL_Insert(list, 3) = nil), + 'FAIL: list bounds'); + LL_Free(list); - { --- Test String Linked List --- } - io.syslog.writeString(''); - io.syslog.writeString('--- Testing String Linked List ---'); - testStr := stringNew(5); - PuInt8(testStr)^ := ord('H'); - PuInt8(testStr + 1)^ := ord('e'); - PuInt8(testStr + 2)^ := ord('l'); - PuInt8(testStr + 3)^ := ord('l'); - PuInt8(testStr + 4)^ := ord('o'); - - testStr2 := stringNew(5); - puint8(testStr2)^ := ord('W'); - puint8(testStr2 + 1)^ := ord('o'); - puint8(testStr2 + 2)^ := ord('r'); - puint8(testStr2 + 3)^ := ord('l'); - puint8(testStr2 + 4)^ := ord('d'); - - - strList := STRLL_New; - STRLL_Add(strList, testStr); - STRLL_Add(strList, testStr2); - io.syslog.writeString('String Linked List Size: '); - io.syslog.writeintln(STRLL_Size(strList)); - for i := 0 to STRLL_Size(strList) - 1 do - begin - io.syslog.writeString('String '); - io.syslog.writeint(i); - io.syslog.writeString(': '); - io.syslog.writeString(STRLL_Get(strList, i)); - - end; - STRLL_Free(strList); - - { --- Test Dynamic List --- } - io.syslog.writeString(''); - io.syslog.writeString('--- Testing Dynamic List ---'); - dlist := DL_New(sizeof(uint32)); - for i := 1 to 5 do - begin - intPtr := DL_Add(dlist); - intPtr^ := i * 10; // Store values: 10, 20, 30, 40, 50 - end; - io.syslog.writeString('Dynamic List Size: '); - io.syslog.writeintln(DL_Size(dlist)); - for i := 0 to DL_Size(dlist) - 1 do - begin - intPtr := DL_Get(dlist, i); - io.syslog.writeString('Element '); - io.syslog.writeint(i); - io.syslog.writeString(': '); - io.syslog.writeintln(intPtr^); - end; - - { Insert a new element at index 2 } - intPtr := DL_Insert(dlist, 2); - if intPtr <> nil then - intPtr^ := 999; // Inserted value 999 at index 2 - io.syslog.writeString('After insertion at index 2, size: '); - io.syslog.writeintln(DL_Size(dlist)); - for i := 0 to DL_Size(dlist) - 1 do - begin - intPtr := DL_Get(dlist, i); - io.syslog.writeString('Element '); - io.syslog.writeint(i); - io.syslog.writeString(': '); - io.syslog.writeintln(intPtr^); - end; - - { Delete element at index 3 } - if DL_Delete(dlist, 3) then - io.syslog.writeString('Deleted element at index 3.'); - io.syslog.writeString('After deletion, size: '); - io.syslog.writeintln(DL_Size(dlist)); - for i := 0 to DL_Size(dlist) - 1 do - begin - intPtr := DL_Get(dlist, i); - io.syslog.writeString('Element '); - io.syslog.writeint(i); - io.syslog.writeString(': '); - io.syslog.writeintln(intPtr^); - end; - DL_Free(dlist); + source := stringCopy('/alpha//beta/'); + list := STRLL_FromString(source, '/'); + kfree(void(source)); + Assert((STRLL_Size(list) = 2) and StringEquals(STRLL_Get(list, 0), 'alpha') and + StringEquals(STRLL_Get(list, 1), 'beta'), 'FAIL: native string list split'); + STRLL_Delete(list, 0); + Assert(StringEquals(STRLL_Get(list, 0), 'beta'), 'FAIL: string list delete'); + STRLL_Free(list); + list := LL_FromString('one:two', ':'); + Assert(StringEquals(STRLL_Get(list, 1), 'two'), 'FAIL: shared string splitter'); + STRLL_Free(list); + list := STRLL_FromString('', '/'); + Assert(STRLL_Size(list) = 0, 'FAIL: empty string list'); + STRLL_Free(list); + Assert(lmm_total_free = beforeFree, 'FAIL: list allocation accounting'); + testSummary('LISTS', 'LISTS_TESTS_PASSED', 'LISTS_TESTS_FAILED', passed, failed); end; - end. diff --git a/src/core/ds/core.ds.types.pas b/src/core/ds/core.ds.types.pas index c4aacce3..d61f99b6 100644 --- a/src/core/ds/core.ds.types.pas +++ b/src/core/ds/core.ds.types.pas @@ -1,4 +1,5 @@ // Copyright 2024 Aaron Hance +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,6 +23,38 @@ unit core.ds.types; interface type + PLinkedList = ^TLinkedList; + + {** + @abstract Managed linked list node record. + @field Previous Pointer to the previous node. + @field Data Pointer to the stored data. + @field Next Pointer to the next node. + **} + TLinkedList = record + Previous : PLinkedList; + Data : void; + Next : PLinkedList; + end; + + {** + @abstract Pointer to the base of a managed linked list. + **} + PLinkedListBase = ^TLinkedListBase; + + {** + @abstract Base record for a managed linked list. + @field Count Number of elements in the list. + @field Head Pointer to the first node. + @field ElementSize Size (in bytes) of each element. + **} + TLinkedListBase = record + Count : uint32; + Head : PLinkedList; + ElementSize : uint32; + end; + + { ------ Shared node for FIFO & LIFO ------ } {** @@ -223,4 +256,4 @@ type implementation -end. \ No newline at end of file +end. diff --git a/src/core/enc/core.enc.base64.pas b/src/core/enc/core.enc.base64.pas index 002b1556..09780adc 100644 --- a/src/core/enc/core.enc.base64.pas +++ b/src/core/enc/core.enc.base64.pas @@ -26,7 +26,7 @@ uses memory.heap, core.strings, debug.tracer, - core.util, arch.x86.util; + core.util, arch.platform.cpu; const base64_enc_map : array[0..63] of char = ('A','B','C','D','E', @@ -64,7 +64,7 @@ begin // integer overflow end else begin output := PuInt8(kalloc(olen)); - b64end := PuInt8(uInt32(src) + uInt32(len)); + b64end := PuInt8(PtrUInt(src) + uInt32(len)); b64in := src; b64pos := output; line_len := 0; @@ -130,7 +130,7 @@ var begin pad := 0; - memset(uInt32(@dtable[0]), $80, 256); + memset(PtrUInt(@dtable[0]), $80, 256); for i := 0 to (sizeof(base64_enc_map) - 2) do begin dtable[uInt8(base64_enc_map[i])] := uInt8(i); diff --git a/src/core/enc/core.enc.fnv1a.pas b/src/core/enc/core.enc.fnv1a.pas index 34ac856b..22701e79 100644 --- a/src/core/enc/core.enc.fnv1a.pas +++ b/src/core/enc/core.enc.fnv1a.pas @@ -47,7 +47,7 @@ interface implementation uses - io.syslog, + arch.platform.diagnostics, memory.heap, core.strings; @@ -58,13 +58,13 @@ var h : uint32; begin h := $811C9DC5; - p := puint8(uint32(Data)); + p := puint8(Data); if DataLen > 0 then for i := 0 to DataLen - 1 do begin h := h xor uint32(p^); h := h * $01000193; - p := puint8(uint32(p) + 1); + inc(p); end; Hash_FNV1a32 := h; end; @@ -76,13 +76,13 @@ var h : uint64; begin h := uint64($CBF29CE484222325); - p := puint8(uint32(Data)); + p := puint8(Data); if DataLen > 0 then for i := 0 to DataLen - 1 do begin h := h xor uint64(p^); h := h * uint64($00000100000001B3); - p := puint8(uint32(p) + 1); + inc(p); end; Hash_FNV1a64 := h; end; @@ -103,36 +103,28 @@ var else begin inc(failed); msg := stringConcat('FAIL: ', testName); - io.syslog.logln('FNV1A', msg); + arch.platform.diagnostics.log('FNV1A', msg); kfree(void(msg)); end; end; - procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; - begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('FNV1A', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); - end; begin passed := 0; failed := 0; - io.syslog.logln('FNV1A', 'Unit tests starting...'); + arch.platform.diagnostics.log('FNV1A', 'Unit tests starting...'); { Two distinct 5-byte inputs } - buf1[0] := ord('h'); buf1[1] := ord('e'); buf1[2] := ord('l'); buf1[3] := ord('l'); buf1[4] := ord('o'); - buf2[0] := ord('w'); buf2[1] := ord('o'); buf2[2] := ord('r'); buf2[3] := ord('l'); buf2[4] := ord('d'); + buf1[0] := ord('h'); + buf1[1] := ord('e'); + buf1[2] := ord('l'); + buf1[3] := ord('l'); + buf1[4] := ord('o'); + buf2[0] := ord('w'); + buf2[1] := ord('o'); + buf2[2] := ord('r'); + buf2[3] := ord('l'); + buf2[4] := ord('d'); { === Known basis value: zero-length input returns initial h === } Assert(Hash_FNV1a32(void(@buf1[0]), 0) = $811C9DC5, 'FNV1a32 empty = basis $811C9DC5'); @@ -155,7 +147,7 @@ begin h64a := Hash_FNV1a64(void(@buf1[0]), 5); Assert(uint32(h64a) <> h32a, 'FNV1a32 != low32(FNV1a64)'); - PrintSummary; + testSummary('FNV1A', 'FNV1A_TESTS_PASSED', 'FNV1A_TESTS_FAILED', passed, failed); end; end. diff --git a/src/core/enc/core.enc.md5.pas b/src/core/enc/core.enc.md5.pas index 1d4c28c0..6c1f052c 100644 --- a/src/core/enc/core.enc.md5.pas +++ b/src/core/enc/core.enc.md5.pas @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -{ +{ Include->MD5 - MD5 checksum. - + @author(Angus C ) @author(Kieron Morris ) } @@ -24,7 +24,7 @@ unit core.enc.md5; interface uses - core.util, arch.x86.util, + core.util, arch.platform.cpu, memory.heap, debug.tracer; @@ -70,7 +70,7 @@ begin T^ := S[0] or (S[1] shl 8) or (S[2] shl 16) or (S[3] shl 24); inc(S,4); inc(T); - end; + end; end; procedure MD5Transform(Context: PMD5Context; Buffer: Pointer); @@ -166,14 +166,14 @@ end; procedure MD5Init(context : PMD5Context); begin debug.tracer.push_trace('core.enc.md5.MD5Init'); - memset(uInt32(context), 0, SizeOf(TMD5Context)); + memset(PtrUInt(context), 0, SizeOf(TMD5Context)); context^.Align := 64; context^.State[0] := $67452301; context^.State[1] := $efcdab89; context^.State[2] := $98badcfe; context^.State[3] := $10325476; context^.Length := 0; - context^.BufferCount := 0; + context^.BufferCount := 0; end; procedure MD5Update(context : PMD5Context; buffer : PuInt8; bufferLen : uInt32); @@ -183,33 +183,33 @@ var Num : uInt32; i : uint32; -begin +begin debug.tracer.push_trace('core.enc.md5.MD5Update'); if bufferLen = 0 then Exit; Align := context^.Align; Src := buffer; Num := 0; - if context^.BufferCount > 0 then begin - Num := Align - context^.BufferCount; + if context^.BufferCount > 0 then begin + Num := Align - context^.BufferCount; if Num > bufferLen then num := bufferLen; - memcpy(uInt32(Src), uInt32(@context^.Buffer[Context^.BufferCount]), Num); - context^.BufferCount := context^.BufferCount + Num; - Src := PuInt8(uInt32(Src) + uint32(Num)); - if context^.BufferCount = Align then begin - MD5Transform(context, @context^.buffer[0]); + memcpy(PtrUInt(Src), PtrUInt(@context^.Buffer[Context^.BufferCount]), Num); + context^.BufferCount := context^.BufferCount + Num; + Src := PuInt8(PtrUInt(Src) + uint32(Num)); + if context^.BufferCount = Align then begin + MD5Transform(context, @context^.buffer[0]); context^.BufferCount := 0; - end; + end; end; Num := bufferLen - Num; While Num >= Align do begin MD5Transform(context, Src); - Src := PuInt8(uInt32(Src) + Align); + Src := PuInt8(PtrUInt(Src) + Align); Num := Num - Align; end; - if Num > 0 then begin - context^.BufferCount := Num; - memcpy(uInt32(Src), uInt32(@context^.Buffer[0]), Num); - end; + if Num > 0 then begin + context^.BufferCount := Num; + memcpy(PtrUInt(Src), PtrUInt(@context^.Buffer[0]), Num); + end; end; function MD5Final(context : PMD5Context) : PMD5Digest; @@ -229,12 +229,12 @@ begin else Pads := 56 - context^.BufferCount; MD5Update(context, PuInt8(@MD5_Padding[0]), Pads); - MD5Update(context, PuInt8(@Length), 8); - Digest := PMD5Digest(kalloc(SizeOf(TMD5Digest))); - memset(uInt32(Digest), 0, SizeOf(TMD5Digest)); - Invert(PuInt8(@context^.State), PuInt32(Digest), 16); - memset(uInt32(context), 0, SizeOf(TMD5Context)); - MD5Final := Digest; + MD5Update(context, PuInt8(@Length), 8); + Digest := PMD5Digest(kalloc(SizeOf(TMD5Digest))); + memset(PtrUInt(Digest), 0, SizeOf(TMD5Digest)); + Invert(PuInt8(@context^.State), PuInt32(Digest), 16); + memset(PtrUInt(context), 0, SizeOf(TMD5Context)); + MD5Final := Digest; end; function MD5Buffer(buffer : PuInt8; bufferLen : uInt32) : PMD5Digest; @@ -258,4 +258,4 @@ begin MD5To32:= Input^.DWords[0] xor Input^.DWords[1] xor Input^.DWords[2] xor Input^.DWords[3]; end; -end. \ No newline at end of file +end. diff --git a/src/core/strings/core.strings.helpers.pas b/src/core/strings/core.strings.helpers.pas index bc4a917b..e18c0b64 100644 --- a/src/core/strings/core.strings.helpers.pas +++ b/src/core/strings/core.strings.helpers.pas @@ -112,16 +112,16 @@ begin if (arr = nil) or (n < 2) then exit; if n > 1 then for i := 1 to n - 1 do begin - p := PPChar(pointer(uint32(arr) + i * sizeof(pchar))); + p := PPChar(pointer(PtrUInt(arr) + i * sizeof(pchar))); tmp := p^; j := i; while j > 0 do begin - p := PPChar(pointer(uint32(arr) + (j - 1) * sizeof(pchar))); + p := PPChar(pointer(PtrUInt(arr) + (j - 1) * sizeof(pchar))); if stringCompareCI(tmp, p^) >= 0 then break; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := p^; + PPChar(pointer(PtrUInt(arr) + j * sizeof(pchar)))^ := p^; j := j - 1; end; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := tmp; + PPChar(pointer(PtrUInt(arr) + j * sizeof(pchar)))^ := tmp; end; end; @@ -142,21 +142,21 @@ begin if (arr = nil) or (n < 2) then exit; if n > 1 then for i := 1 to n - 1 do begin - p := PPChar(pointer(uint32(arr) + i * sizeof(pchar))); - dp := puint32(pointer(uint32(data) + i * sizeof(uint32))); + p := PPChar(pointer(PtrUInt(arr) + i * sizeof(pchar))); + dp := puint32(pointer(PtrUInt(data) + i * sizeof(uint32))); tmpN := p^; tmpD := dp^; j := i; while j > 0 do begin - p := PPChar(pointer(uint32(arr) + (j - 1) * sizeof(pchar))); + p := PPChar(pointer(PtrUInt(arr) + (j - 1) * sizeof(pchar))); if stringCompareCI(tmpN, p^) >= 0 then break; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := p^; - puint32(pointer(uint32(data) + j * sizeof(uint32)))^ := - puint32(pointer(uint32(data) + (j - 1) * sizeof(uint32)))^; + PPChar(pointer(PtrUInt(arr) + j * sizeof(pchar)))^ := p^; + puint32(pointer(PtrUInt(data) + j * sizeof(uint32)))^ := + puint32(pointer(PtrUInt(data) + (j - 1) * sizeof(uint32)))^; j := j - 1; end; - PPChar(pointer(uint32(arr) + j * sizeof(pchar)))^ := tmpN; - puint32(pointer(uint32(data) + j * sizeof(uint32)))^ := tmpD; + PPChar(pointer(PtrUInt(arr) + j * sizeof(pchar)))^ := tmpN; + puint32(pointer(PtrUInt(data) + j * sizeof(uint32)))^ := tmpD; end; end; diff --git a/src/core/strings/core.strings.pas b/src/core/strings/core.strings.pas index 8c46a43b..8536ab11 100644 --- a/src/core/strings/core.strings.pas +++ b/src/core/strings/core.strings.pas @@ -1,4 +1,5 @@ // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,9 +24,7 @@ unit core.strings; interface uses - core.util, arch.x86.util, - memory.heap, - core.ds.lists; + core.util, memory.heap; function charToLower(c : char) : char; function charToUpper(c : char) : char; @@ -62,7 +61,7 @@ procedure UnitTest; implementation uses - io.syslog; + arch.platform.diagnostics; function hexStringToInt(str : pchar) : uint32; var @@ -160,20 +159,20 @@ var begin size:= stringSize(str); result:= stringNew(size); - memcpy(uint32(str), uint32(result), size); + if result = nil then begin + stringCopy:= nil; + exit; + end; + memcpy(PtrUInt(str), PtrUInt(result), size); stringCopy:= result; end; function stringNew(size : uint32) : pchar; -var - result : pchar; - newSize : uint32; - begin - newSize:= size + 1; - result:= pchar(kalloc(newSize)); - memset(uint32(result), 0, newSize); - stringNew:= result; + stringNew:= nil; + if size = high(uint32) then exit; + { kalloc zeroes the buffer and its terminating byte. } + stringNew:= pchar(kalloc(size + 1)); end; function stringSize(str : pchar) : uint32; @@ -200,13 +199,22 @@ var begin size1:= stringSize(str1); size2:= stringSize(str2); + if size2 >= high(uint32) - size1 then begin + stringConcat:= nil; + exit; + end; result:= stringNew(size1 + size2); - memcpy(uint32(str1), uint32(result), size1); - memcpy(uint32(str2), uint32(result + size1), size2); + if result = nil then begin + stringConcat:= nil; + exit; + end; + memcpy(PtrUInt(str1), PtrUInt(result), size1); + memcpy(PtrUInt(str2), PtrUInt(result + size1), size2); stringConcat:= result; end; // Trim the string to the specified length. + function stringTrim(str : pchar; length : uInt32) : pchar; var result : pchar; @@ -215,11 +223,16 @@ begin strLen:= stringSize(str); if length > strLen then length:= strLen; result:= stringNew(length); - memcpy(uint32(str), uint32(result), length); + if result = nil then begin + stringTrim:= nil; + exit; + end; + memcpy(PtrUInt(str), PtrUInt(result), length); stringTrim:= result; end; // Return a substring of the string. + function stringSub(str : pchar; start, size : uint32) : pchar; var result : pchar; @@ -230,13 +243,18 @@ begin stringSub:= stringNew(0); exit; end; - if start + size > strLen then size:= strLen - start; + if size > strLen - start then size:= strLen - start; result:= stringNew(size); - memcpy(uint32(str)+start, uint32(result), size); + if result = nil then begin + stringSub:= nil; + exit; + end; + memcpy(PtrUInt(str)+start, PtrUInt(result), size); stringSub:= result; end; // Compare find against str starting at position pos, without requiring full string equality. + function stringMatchAt(str : pchar; pos : uint32; find : pchar) : boolean; var i, findLen, strLen : uint32; @@ -248,7 +266,8 @@ begin stringMatchAt:= true; exit; end; - if pos + findLen > strLen then exit; + if pos > strLen then exit; + if findLen > strLen - pos then exit; for i:= 0 to findLen - 1 do begin if str[pos + i] <> find[i] then exit; end; @@ -256,6 +275,7 @@ begin end; // Replace first instance of a string with another. + function stringReplace(str, find, replace : pchar) : pchar; var result : pchar; @@ -279,17 +299,25 @@ begin // If we found the find string, replace it. if found then begin + if replLen >= high(uint32) - (strLen - findLen) then begin + stringReplace:= nil; + exit; + end; result:= stringNew(strLen - findLen + replLen); + if result = nil then begin + stringReplace:= nil; + exit; + end; // Copy prefix before match if pos > 0 then - memcpy(uint32(str), uint32(result), pos); + memcpy(PtrUInt(str), PtrUInt(result), pos); // Copy replacement if replLen > 0 then - memcpy(uint32(replace), uint32(result + pos), replLen); + memcpy(PtrUInt(replace), PtrUInt(result + pos), replLen); // Copy suffix after match suffixLen:= strLen - pos - findLen; if suffixLen > 0 then - memcpy(uint32(str) + pos + findLen, uint32(result) + pos + replLen, suffixLen); + memcpy(PtrUInt(str) + pos + findLen, PtrUInt(result) + pos + replLen, suffixLen); stringReplace:= result; end else begin stringReplace:= stringCopy(str); @@ -298,6 +326,7 @@ end; // Find the index of the first instance of a string. + function stringIndexOf(str, find : pchar) : sint32; var i, strLen, findLen : uint32; @@ -333,13 +362,28 @@ function stringCompare(a, b : pchar) : sint32; var i : uint32; begin - if (a = nil) and (b = nil) then begin stringCompare := 0; exit; end; - if a = nil then begin stringCompare := -1; exit; end; - if b = nil then begin stringCompare := 1; exit; end; + if (a = nil) and (b = nil) then begin + stringCompare := 0; + exit; + end; + if a = nil then begin + stringCompare := -1; + exit; + end; + if b = nil then begin + stringCompare := 1; + exit; + end; i := 0; while (a[i] <> #0) and (b[i] <> #0) do begin - if ord(a[i]) < ord(b[i]) then begin stringCompare := -1; exit; end; - if ord(a[i]) > ord(b[i]) then begin stringCompare := 1; exit; end; + if ord(a[i]) < ord(b[i]) then begin + stringCompare := -1; + exit; + end; + if ord(a[i]) > ord(b[i]) then begin + stringCompare := 1; + exit; + end; i := i + 1; end; if a[i] = b[i] then @@ -358,15 +402,30 @@ var i : uint32; ca, cb : char; begin - if (a = nil) and (b = nil) then begin stringCompareCI := 0; exit; end; - if a = nil then begin stringCompareCI := -1; exit; end; - if b = nil then begin stringCompareCI := 1; exit; end; + if (a = nil) and (b = nil) then begin + stringCompareCI := 0; + exit; + end; + if a = nil then begin + stringCompareCI := -1; + exit; + end; + if b = nil then begin + stringCompareCI := 1; + exit; + end; i := 0; while (a[i] <> #0) and (b[i] <> #0) do begin ca := charToLower(a[i]); cb := charToLower(b[i]); - if ord(ca) < ord(cb) then begin stringCompareCI := -1; exit; end; - if ord(ca) > ord(cb) then begin stringCompareCI := 1; exit; end; + if ord(ca) < ord(cb) then begin + stringCompareCI := -1; + exit; + end; + if ord(ca) > ord(cb) then begin + stringCompareCI := 1; + exit; + end; i := i + 1; end; if a[i] = b[i] then @@ -395,8 +454,14 @@ var begin strLen := stringSize(str); findLen := stringSize(find); - if findLen = 0 then begin stringIndexOfCI := 0; exit; end; - if findLen > strLen then begin stringIndexOfCI := -1; exit; end; + if findLen = 0 then begin + stringIndexOfCI := 0; + exit; + end; + if findLen > strLen then begin + stringIndexOfCI := -1; + exit; + end; for i := 0 to strLen - findLen do begin found := true; if findLen > 0 then @@ -406,7 +471,10 @@ begin break; end; end; - if found then begin stringIndexOfCI := i; exit; end; + if found then begin + stringIndexOfCI := i; + exit; + end; end; stringIndexOfCI := -1; end; @@ -427,10 +495,16 @@ var i, prefLen : uint32; begin stringStartsWith := false; - if prefix = nil then begin stringStartsWith := true; exit; end; + if prefix = nil then begin + stringStartsWith := true; + exit; + end; if str = nil then exit; prefLen := stringSize(prefix); - if prefLen = 0 then begin stringStartsWith := true; exit; end; + if prefLen = 0 then begin + stringStartsWith := true; + exit; + end; if stringSize(str) < prefLen then exit; if prefLen > 0 then for i := 0 to prefLen - 1 do @@ -446,10 +520,16 @@ var i, prefLen : uint32; begin stringStartsWithCI := false; - if prefix = nil then begin stringStartsWithCI := true; exit; end; + if prefix = nil then begin + stringStartsWithCI := true; + exit; + end; if str = nil then exit; prefLen := stringSize(prefix); - if prefLen = 0 then begin stringStartsWithCI := true; exit; end; + if prefLen = 0 then begin + stringStartsWithCI := true; + exit; + end; if stringSize(str) < prefLen then exit; if prefLen > 0 then for i := 0 to prefLen - 1 do @@ -465,11 +545,17 @@ var i, strLen, sufLen : uint32; begin stringEndsWith := false; - if suffix = nil then begin stringEndsWith := true; exit; end; + if suffix = nil then begin + stringEndsWith := true; + exit; + end; if str = nil then exit; strLen := stringSize(str); sufLen := stringSize(suffix); - if sufLen = 0 then begin stringEndsWith := true; exit; end; + if sufLen = 0 then begin + stringEndsWith := true; + exit; + end; if strLen < sufLen then exit; if sufLen > 0 then for i := 0 to sufLen - 1 do @@ -485,11 +571,17 @@ var i, strLen, sufLen : uint32; begin stringEndsWithCI := false; - if suffix = nil then begin stringEndsWithCI := true; exit; end; + if suffix = nil then begin + stringEndsWithCI := true; + exit; + end; if str = nil then exit; strLen := stringSize(str); sufLen := stringSize(suffix); - if sufLen = 0 then begin stringEndsWithCI := true; exit; end; + if sufLen = 0 then begin + stringEndsWithCI := true; + exit; + end; if strLen < sufLen then exit; if sufLen > 0 then for i := 0 to sufLen - 1 do @@ -555,7 +647,7 @@ end; function int64ToString(i : int64) : pchar; var result : pchar; - tmp : int64; + tmp, magnitude : uint64; len : uint32; pos : uint32; neg : boolean; @@ -568,10 +660,11 @@ begin exit; end; neg:= (i < 0); - if neg then i:= -i; + if neg then magnitude:= uint64(-(i + 1)) + 1 + else magnitude:= uint64(i); { Count digits } len:= 0; - tmp:= i; + tmp:= magnitude; while tmp > 0 do begin inc(len); tmp:= tmp div 10; @@ -580,7 +673,7 @@ begin { Build string from right to left } result:= stringNew(len); pos:= len; - tmp:= i; + tmp:= magnitude; while tmp > 0 do begin dec(pos); result[pos]:= char((tmp mod 10) + 48); @@ -747,40 +840,23 @@ var passed, failed : uint32; procedure Assert(condition : boolean; testName : pchar); - var - msg : pchar; + var msg : pchar; begin if condition then begin inc(passed); end else begin inc(failed); msg:= stringConcat('FAIL: ', testName); - io.syslog.logln('STRINGS', msg); + arch.platform.diagnostics.log('STRINGS', msg); kfree(void(msg)); end; end; - procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; - begin - pStr:= intToString(passed); - fStr:= intToString(failed); - msg:= stringConcat(pStr, ' passed, '); - tmp:= stringConcat(msg, fStr); - kfree(void(msg)); - msg:= stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('STRINGS', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); - end; begin passed:= 0; failed:= 0; - io.syslog.logln('STRINGS', 'Unit tests starting...'); + arch.platform.diagnostics.log('STRINGS', 'Unit tests starting...'); { === stringSize === } Assert(stringSize('HELLO') = 5, 'stringSize(HELLO)=5'); @@ -789,6 +865,7 @@ begin Assert(stringSize(nil) = 0, 'stringSize(nil)=0'); { === stringNew === } + Assert(stringNew(high(uint32)) = nil, 'stringNew rejects terminator overflow'); s:= stringNew(0); Assert(stringSize(s) = 0, 'stringNew(0) is empty'); kfree(void(s)); @@ -1019,6 +1096,15 @@ begin Assert(stringEquals(s, '1000000'), 'int64ToString large'); kfree(void(s)); + s:= int64ToString(low(int64)); + Assert(stringEquals(s, '-9223372036854775808'), 'int64ToString minimum'); + kfree(void(s)); + s:= stringSub('ABC', 1, high(uint32)); + Assert(stringEquals(s, 'BC'), 'substring size cannot wrap'); + kfree(void(s)); + Assert(not stringMatchAt('ABC', high(uint32), 'BC'), 'match position cannot wrap'); + Assert((Trunc(-3.75) = -3) and (Trunc(3.75) = 3), 'Trunc rounds toward zero'); + { === doubleToStr === } s:= doubleToStr(0.0); Assert(stringEquals(s, '0'), 'doubleToStr zero'); @@ -1114,7 +1200,8 @@ begin Assert(stringEndsWithCI('Hello', 'xyz') = false, 'stringEndsWithCI no match'); { Print summary } - PrintSummary; + + arch.platform.diagnostics.testSummary('STRINGS', 'STRINGS_TESTS_PASSED', 'STRINGS_TESTS_FAILED', passed, failed); end; -end. \ No newline at end of file +end. diff --git a/src/debug/debug.early.pas b/src/debug/debug.early.pas new file mode 100644 index 00000000..fb1ee081 --- /dev/null +++ b/src/debug/debug.early.pas @@ -0,0 +1,61 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Allocation-free diagnostics. The architecture supplies the character sink; + formatting remains independent of UARTs, graphics, heaps, and interrupts. } +unit debug.early; + +interface +type + TCharWriter = procedure(ch: char); +procedure setWriter(writer: TCharWriter); +procedure puts(message: pchar); +procedure hex(value: uint64); +procedure field(name: pchar; value: uint64); + +implementation +var + sink: TCharWriter; + +procedure setWriter(writer: TCharWriter); +begin + sink := writer; +end; + +procedure puts(message: pchar); +begin + if sink = nil then exit; + while message^ <> #0 do begin + sink(message^); + inc(message); + end; +end; + +procedure hex(value: uint64); +const + digits: array[0..15] of char = '0123456789ABCDEF'; +var + shift: sint32; +begin + if sink = nil then exit; + for shift := 15 downto 0 do + sink(digits[(value shr (shift * 4)) and $F]); +end; + +procedure field(name: pchar; value: uint64); +begin + puts(name); + puts('='); + hex(value); + puts(#13#10); +end; +end. diff --git a/src/debug/debug.tracer.pas b/src/debug/debug.tracer.pas index 70e53248..37d8fa44 100644 --- a/src/debug/debug.tracer.pas +++ b/src/debug/debug.tracer.pas @@ -37,8 +37,7 @@ procedure print_traces; implementation uses - boot.mgr, - core.util, arch.x86.util, core.strings, io.stdio, io.syslog; + core.util, core.strings, io.stdio, io.syslog; const MAX_TRACE = 40; @@ -186,9 +185,4 @@ begin end; end; -initialization - //procedure registerBoot(Name: PChar; InitProc: TBootProc; Status: PChar; DependsOn: PChar); - boot.mgr.registerBoot('debug.tracer.freeze', @freeze, 'Debug Tracer Freeze', 'io.syslog' ); - boot.mgr.registerBoot('debug.tracer', @init, 'Debug Tracer Initialization', 'io.stdio' ); - -end. \ No newline at end of file +end. diff --git a/src/driver/bus/driver.bus.pci.pas b/src/driver/bus/driver.bus.pci.pas index 23b12808..efc2daa1 100644 --- a/src/driver/bus/driver.bus.pci.pas +++ b/src/driver/bus/driver.bus.pci.pas @@ -23,13 +23,11 @@ unit driver.bus.pci; interface uses - boot.mgr, debug.tracer, - core.util, arch.x86.util, + core.util, arch.platform.cpu, io.syslog, driver.types, memory.heap, - arch.x86.memory.virtual, driver.mgr; const @@ -148,7 +146,7 @@ begin result := loadDeviceConfig(bus, slot, 0); if result = true then begin if devices[device_count - 1].header_type and $80 <> 0 then begin - for func := 1 to 8 do begin + for func := 1 to 7 do begin loadDeviceConfig(bus, slot, func); end; end; @@ -404,7 +402,7 @@ begin devices[device_count] := device; device_count := device_count + 1; - driver.mgr.register_device('driver.bus.pci Device', DevID, @device); + driver.mgr.register_device('driver.bus.pci Device', DevID, @devices[device_count - 1]); kfree(void(DevID)); end else begin loadBusConfig(bus, slot, func, device); @@ -474,7 +472,4 @@ begin pop_trace; end; -Initialization - boot.mgr.registerBoot('driver.bus.pci', @Init, 'PCI Bus Driver', BOOT_MGR_BARRIER_BUS_LATE); - end. diff --git a/src/driver/bus/usb/driver.bus.usb.core.pas b/src/driver/bus/usb/driver.bus.usb.core.pas index 60ff1031..c0181c10 100644 --- a/src/driver/bus/usb/driver.bus.usb.core.pas +++ b/src/driver/bus/usb/driver.bus.usb.core.pas @@ -24,7 +24,7 @@ interface uses driver.bus.usb.types, - core.util, arch.x86.util, + core.util, arch.platform.cpu, arch.platform.diagnostics, memory.heap, core.ds.lists, driver.types, @@ -33,7 +33,7 @@ uses io.syslog, core.strings, io.stdio, - arch.x86.bda; + arch.platform.process; { ========================= HC Management ========================= } @@ -41,7 +41,7 @@ uses Returns the stable heap-allocated pointer to the HC record (not the caller's stack copy). } function register_hc(hc : PUSBHCDriver) : PUSBHCDriver; -{ Unregister a host controller. } +{ Unregister an empty host controller. Remove attached devices first. } procedure unregister_hc(hc : PUSBHCDriver); { Get the number of registered host controllers. } @@ -199,9 +199,14 @@ begin if (hc <> nil) and (HCList <> nil) then begin entry := PUSBHCDriver(LL_Add(HCList)); if entry <> nil then begin - memcpy(uint32(hc), uint32(entry), sizeof(TUSBHCDriver)); + memcpy(PtrUInt(hc), PtrUInt(entry), sizeof(TUSBHCDriver)); { Initialize per-HC device list } entry^.Devices := LL_New(sizeof(PUSBDevice)); + if entry^.Devices = nil then begin + LL_Delete(HCList, LL_Size(HCList) - 1); + pop_trace; + exit; + end; entry^.HotplugArmed := false; entry^.PortChangePending := false; io.syslog.log('driver.bus.usb Core', 'Registered HC: '); @@ -219,15 +224,20 @@ var begin push_trace('driver.bus.usb.core.unregister_hc'); if (hc <> nil) and (HCList <> nil) then begin - for i := 0 to LL_Size(HCList) - 1 do begin - entry := PUSBHCDriver(LL_Get(HCList, i)); - if (entry <> nil) and (entry^.BaseAddr = hc^.BaseAddr) and (entry^.HCType = hc^.HCType) then begin - LL_Delete(HCList, i); - io.syslog.log('driver.bus.usb Core', 'Unregistered HC: '); - io.syslog.writestringln(hc^.Name); - break; + if LL_Size(HCList) > 0 then + for i := 0 to LL_Size(HCList) - 1 do begin + entry := PUSBHCDriver(LL_Get(HCList, i)); + if (entry <> nil) and (entry^.BaseAddr = hc^.BaseAddr) and (entry^.HCType = hc^.HCType) then begin + if entry^.Devices <> nil then begin + if LL_Size(entry^.Devices) <> 0 then break; + LL_Free(entry^.Devices); + end; + io.syslog.log('driver.bus.usb Core', 'Unregistered HC: '); + io.syslog.writestringln(hc^.Name); + LL_Delete(HCList, i); + break; + end; end; - end; end; pop_trace; end; @@ -363,11 +373,11 @@ begin end; { Poll until complete (with real-time timeout: 5 seconds at 1024 Hz) } - polls := arch.x86.bda.Counters.c32; + polls := arch.platform.process.timerTicks; while (transfer.Status = tsInProgress) or (transfer.Status = tsNotStarted) do begin if dev^.HC^.fnPoll <> nil then dev^.HC^.fnPoll(dev^.HC); - if (arch.x86.bda.Counters.c32 - polls) > 5120 then begin + if (arch.platform.process.timerTicks - polls) > 5120 then begin usb_control_msg := tsTimeout; pop_trace; exit; @@ -522,11 +532,11 @@ begin ticks := timeoutMs; if ticks = 0 then ticks := 5120; { default 5 seconds } - startTick := arch.x86.bda.Counters.c32; + startTick := arch.platform.process.timerTicks; while (transfer^.Status = tsInProgress) or (transfer^.Status = tsNotStarted) do begin if dev^.HC^.fnPoll <> nil then dev^.HC^.fnPoll(dev^.HC); - if (arch.x86.bda.Counters.c32 - startTick) > ticks then begin + if (arch.platform.process.timerTicks - startTick) > ticks then begin kfree(void(transfer)); usb_bulk_transfer_wait := tsTimeout; pop_trace; @@ -560,12 +570,12 @@ begin offset := sizeof(TUSBConfigDescriptor); count := 0; while offset < totalLen do begin - bLen := PUint8(uint32(configBuf) + offset)^; + bLen := PUint8(PtrUInt(configBuf) + offset)^; if bLen = 0 then exit; { corrupt } - bType := PUint8(uint32(configBuf) + offset + 1)^; + bType := PUint8(PtrUInt(configBuf) + offset + 1)^; if bType = USB_DESC_INTERFACE then begin if count = index then begin - usb_find_interface_n := PUSBInterfaceDescriptor(uint32(configBuf) + offset); + usb_find_interface_n := PUSBInterfaceDescriptor(PtrUInt(configBuf) + offset); exit; end; inc(count); @@ -584,14 +594,14 @@ begin usb_count_endpoints := 0; if configBuf = nil then exit; { Skip past the interface descriptor itself } - bLen := PUint8(uint32(configBuf) + ifaceOffset)^; + bLen := PUint8(PtrUInt(configBuf) + ifaceOffset)^; if bLen = 0 then exit; offset := ifaceOffset + bLen; count := 0; while offset < totalLen do begin - bLen := PUint8(uint32(configBuf) + offset)^; + bLen := PUint8(PtrUInt(configBuf) + offset)^; if bLen = 0 then break; - bType := PUint8(uint32(configBuf) + offset + 1)^; + bType := PUint8(PtrUInt(configBuf) + offset + 1)^; { Stop at next interface descriptor } if bType = USB_DESC_INTERFACE then break; if bType = USB_DESC_ENDPOINT then @@ -611,18 +621,18 @@ begin usb_find_endpoint := nil; if configBuf = nil then exit; { Skip past the interface descriptor itself } - bLen := PUint8(uint32(configBuf) + ifaceOffset)^; + bLen := PUint8(PtrUInt(configBuf) + ifaceOffset)^; if bLen = 0 then exit; offset := ifaceOffset + bLen; count := 0; while offset < totalLen do begin - bLen := PUint8(uint32(configBuf) + offset)^; + bLen := PUint8(PtrUInt(configBuf) + offset)^; if bLen = 0 then break; - bType := PUint8(uint32(configBuf) + offset + 1)^; + bType := PUint8(PtrUInt(configBuf) + offset + 1)^; if bType = USB_DESC_INTERFACE then break; if bType = USB_DESC_ENDPOINT then begin if count = index then begin - usb_find_endpoint := PUSBEndpointDescriptor(uint32(configBuf) + offset); + usb_find_endpoint := PUSBEndpointDescriptor(PtrUInt(configBuf) + offset); exit; end; inc(count); @@ -651,6 +661,7 @@ var addr : uint8; i : uint32; DevID : PDeviceIdentifier; + deviceSlot: PPUSBDevice; begin push_trace('driver.bus.usb.core.enumerate_device'); enumerate_device := nil; @@ -771,13 +782,13 @@ begin dev^.NumEndpoints := 0; while offset + 2 <= totalLen do begin { Read descriptor length and type } - if PUint8(uint32(cfgBuf) + offset)^ < 2 then break; - if offset + PUint8(uint32(cfgBuf) + offset)^ > totalLen then break; + if PUint8(PtrUInt(cfgBuf) + offset)^ < 2 then break; + if offset + PUint8(PtrUInt(cfgBuf) + offset)^ > totalLen then break; - if PUint8(uint32(cfgBuf) + offset + 1)^ = USB_DESC_ENDPOINT then begin - if (PUint8(uint32(cfgBuf) + offset)^ >= sizeof(TUSBEndpointDescriptor)) and + if PUint8(PtrUInt(cfgBuf) + offset + 1)^ = USB_DESC_ENDPOINT then begin + if (PUint8(PtrUInt(cfgBuf) + offset)^ >= sizeof(TUSBEndpointDescriptor)) and (dev^.NumEndpoints < USB_MAX_ENDPOINTS) then begin - epDesc := PUSBEndpointDescriptor(uint32(cfgBuf) + offset); + epDesc := PUSBEndpointDescriptor(PtrUInt(cfgBuf) + offset); dev^.Endpoints[dev^.NumEndpoints].Address := usb_ep_number(epDesc^.bEndpointAddress); dev^.Endpoints[dev^.NumEndpoints].Direction := usb_ep_direction(epDesc^.bEndpointAddress); dev^.Endpoints[dev^.NumEndpoints].PipeType := usb_ep_pipe_type(epDesc^.bmAttributes); @@ -789,7 +800,7 @@ begin end; { Advance by bLength } - offset := offset + PUint8(uint32(cfgBuf) + offset)^; + offset := offset + PUint8(PtrUInt(cfgBuf) + offset)^; end; { Step 8: SET_CONFIGURATION } @@ -808,15 +819,15 @@ begin offset := sizeof(TUSBConfigDescriptor); ifDesc := nil; while offset + 2 <= totalLen do begin - if PUint8(uint32(cfgBuf) + offset)^ < 2 then break; - if offset + PUint8(uint32(cfgBuf) + offset)^ > totalLen then break; - if (PUint8(uint32(cfgBuf) + offset + 1)^ = USB_DESC_INTERFACE) and - (PUint8(uint32(cfgBuf) + offset)^ >= sizeof(TUSBInterfaceDescriptor)) then begin - ifDesc := PUSBInterfaceDescriptor(uint32(cfgBuf) + offset); + if PUint8(PtrUInt(cfgBuf) + offset)^ < 2 then break; + if offset + PUint8(PtrUInt(cfgBuf) + offset)^ > totalLen then break; + if (PUint8(PtrUInt(cfgBuf) + offset + 1)^ = USB_DESC_INTERFACE) and + (PUint8(PtrUInt(cfgBuf) + offset)^ >= sizeof(TUSBInterfaceDescriptor)) then begin + ifDesc := PUSBInterfaceDescriptor(PtrUInt(cfgBuf) + offset); dev^.InterfaceNumber := ifDesc^.bInterfaceNumber; break; end; - offset := offset + PUint8(uint32(cfgBuf) + offset)^; + offset := offset + PUint8(PtrUInt(cfgBuf) + offset)^; end; DevID := PDeviceIdentifier(kalloc(sizeof(TDeviceIdentifier))); @@ -837,15 +848,21 @@ begin end; DevID^.ex := nil; + deviceSlot := nil; + if hc^.Devices <> nil then deviceSlot := PPUSBDevice(LL_Add(hc^.Devices)); + if deviceSlot = nil then begin + kfree(void(DevID)); + kfree(void(cfgBuf)); + kfree(void(dev)); + pop_trace; + exit; + end; + deviceSlot^ := dev; driver.mgr.register_device('driver.bus.usb Device', DevID, void(dev)); kfree(void(DevID)); kfree(void(cfgBuf)); - { Track device in HC's device list (store pointer, not struct) } - if hc^.Devices <> nil then begin - puint32(LL_Add(hc^.Devices))^ := uint32(dev); - end; io.syslog.log('driver.bus.usb Core', 'Device enumerated at address '); io.syslog.writeintln(addr); @@ -915,13 +932,14 @@ begin { Remove from HC's device list } if (hc <> nil) and (hc^.Devices <> nil) then begin - for i := 0 to LL_Size(hc^.Devices) - 1 do begin - dptr := PUSBDevice(puint32(LL_Get(hc^.Devices, i))^); - if dptr = dev then begin - LL_Delete(hc^.Devices, i); - break; + if LL_Size(hc^.Devices) > 0 then + for i := 0 to LL_Size(hc^.Devices) - 1 do begin + dptr := PPUSBDevice(LL_Get(hc^.Devices, i))^; + if dptr = dev then begin + LL_Delete(hc^.Devices, i); + break; + end; end; - end; end; io.syslog.log('driver.bus.usb Core', 'Removed device at address '); @@ -961,13 +979,14 @@ begin { Check if a device is currently tracked on this port } found := false; if hc^.Devices <> nil then begin - for i := 0 to LL_Size(hc^.Devices) - 1 do begin - dev := PUSBDevice(puint32(LL_Get(hc^.Devices, i))^); - if (dev <> nil) and (dev^.HCPort = port) then begin - found := true; - break; + if LL_Size(hc^.Devices) > 0 then + for i := 0 to LL_Size(hc^.Devices) - 1 do begin + dev := PPUSBDevice(LL_Get(hc^.Devices, i))^; + if (dev <> nil) and (dev^.HCPort = port) then begin + found := true; + break; + end; end; - end; end; if (status AND $01) <> 0 then begin @@ -1002,6 +1021,7 @@ begin hc := PUSBHCDriver(LL_Get(HCList, i)); if (hc <> nil) and hc^.PortChangePending then begin hc^.PortChangePending := false; + if hc^.NumPorts > 0 then for port := 0 to hc^.NumPorts - 1 do usb_rescan_port(hc, port); end; @@ -1029,6 +1049,7 @@ begin io.stdio.bufWriteIntLn(stdout_buf, hcCount); io.stdio.bufWriteNewLine(stdout_buf); + if hcCount > 0 then for i := 0 to hcCount - 1 do begin hc := get_hc(i); if hc = nil then continue; @@ -1065,7 +1086,7 @@ begin io.stdio.bufWriteStrLn(stdout_buf, ' (no devices)'); end else begin for j := 0 to devCount - 1 do begin - dev := PUSBDevice(puint32(LL_Get(hc^.Devices, j))^); + dev := PPUSBDevice(LL_Get(hc^.Devices, j))^; if dev = nil then continue; io.stdio.bufWriteStr(stdout_buf, ' Dev '); @@ -1165,23 +1186,17 @@ var end; procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('USBCORE', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); + testSummary('USBCORE', 'USBCORE_TESTS_PASSED', 'USBCORE_TESTS_FAILED', passed, failed); end; var + savedList: PLinkedListBase; + fixture, registered: PUSBHCDriver; + device: PUSBDevice; + slot: PPUSBDevice; + beforeFree: SizeUInt; + savedIRQ: PtrUInt; setup : TUSBSetupPacket; cfgBuf : Pointer; ifDesc : PUSBInterfaceDescriptor; @@ -1191,6 +1206,43 @@ begin failed := 0; io.syslog.logln('USBCORE', 'Unit tests starting...'); + savedIRQ := arch.platform.cpu.lock; + savedList := HCList; + beforeFree := lmm_total_free; + HCList := LL_New(sizeof(TUSBHCDriver)); + fixture := PUSBHCDriver(kalloc(sizeof(TUSBHCDriver))); + device := PUSBDevice(kalloc(sizeof(TUSBDevice))); + if (HCList <> nil) and (fixture <> nil) and (device <> nil) then begin + fixture^.Name := 'USB native-pointer fixture'; + registered := register_hc(fixture); + Assert(registered <> nil, 'register heap-backed controller'); + if registered <> nil then begin + Assert((registered <> fixture) and (get_hc(0) = registered), 'stable controller copy'); + slot := PPUSBDevice(LL_Add(registered^.Devices)); + Assert(slot <> nil, 'allocate native device slot'); + if slot <> nil then begin + slot^ := device; + device^.HC := registered; + Assert(PPUSBDevice(LL_Get(registered^.Devices, 0))^ = device, 'device slot preserves native pointer'); + usb_remove_device(device); + device := nil; + Assert(LL_Size(registered^.Devices) = 0, 'device removal detaches slot'); + end; + unregister_hc(registered); + Assert(get_hc_count = 0, 'controller removal releases registration'); + end; + unregister_hc(fixture); + poll_all; + usb_check_hotplug; + Assert(get_hc(0) = nil, 'empty controller list is safe'); + end else Assert(false, 'allocate controller fixture'); + if device <> nil then kfree(void(device)); + if fixture <> nil then kfree(void(fixture)); + if HCList <> nil then LL_Free(HCList); + HCList := savedList; + Assert(lmm_total_free = beforeFree, 'controller fixture releases owned memory'); + arch.platform.cpu.unlock(savedIRQ); + { === HC list management === } Assert(get_hc_count >= 0, 'hc_count non-negative'); @@ -1273,58 +1325,58 @@ begin Config(9) + Interface(9, class=$03, 2 EPs) + Endpoint(7, EP1 IN INT) + Endpoint(7, EP2 OUT BULK) + Interface(9, class=$08, 1 EP) + Endpoint(7, EP3 IN BULK) = 48 bytes total } cfgBuf := Pointer(kalloc(48)); - memset(uint32(cfgBuf), 0, 48); + memset(PtrUInt(cfgBuf), 0, 48); { Config descriptor header at offset 0 } - PUint8(uint32(cfgBuf) + 0)^ := 9; { bLength } - PUint8(uint32(cfgBuf) + 1)^ := USB_DESC_CONFIGURATION; { bDescriptorType } - PUint8(uint32(cfgBuf) + 2)^ := 48; { wTotalLength low } - PUint8(uint32(cfgBuf) + 3)^ := 0; { wTotalLength high } - PUint8(uint32(cfgBuf) + 4)^ := 2; { bNumInterfaces } - PUint8(uint32(cfgBuf) + 5)^ := 1; { bConfigurationValue } + PUint8(PtrUInt(cfgBuf) + 0)^ := 9; { bLength } + PUint8(PtrUInt(cfgBuf) + 1)^ := USB_DESC_CONFIGURATION; { bDescriptorType } + PUint8(PtrUInt(cfgBuf) + 2)^ := 48; { wTotalLength low } + PUint8(PtrUInt(cfgBuf) + 3)^ := 0; { wTotalLength high } + PUint8(PtrUInt(cfgBuf) + 4)^ := 2; { bNumInterfaces } + PUint8(PtrUInt(cfgBuf) + 5)^ := 1; { bConfigurationValue } { Interface 0 at offset 9: class=$03 (HID), 2 endpoints } - PUint8(uint32(cfgBuf) + 9)^ := 9; { bLength } - PUint8(uint32(cfgBuf) + 10)^ := USB_DESC_INTERFACE; - PUint8(uint32(cfgBuf) + 11)^ := 0; { bInterfaceNumber } - PUint8(uint32(cfgBuf) + 13)^ := 2; { bNumEndpoints } - PUint8(uint32(cfgBuf) + 14)^ := USB_CLASS_HID; { bInterfaceClass } - PUint8(uint32(cfgBuf) + 15)^ := $01; { bInterfaceSubClass (boot) } - PUint8(uint32(cfgBuf) + 16)^ := $01; { bInterfaceProtocol (driver.hid.keyboard) } + PUint8(PtrUInt(cfgBuf) + 9)^ := 9; { bLength } + PUint8(PtrUInt(cfgBuf) + 10)^ := USB_DESC_INTERFACE; + PUint8(PtrUInt(cfgBuf) + 11)^ := 0; { bInterfaceNumber } + PUint8(PtrUInt(cfgBuf) + 13)^ := 2; { bNumEndpoints } + PUint8(PtrUInt(cfgBuf) + 14)^ := USB_CLASS_HID; { bInterfaceClass } + PUint8(PtrUInt(cfgBuf) + 15)^ := $01; { bInterfaceSubClass (boot) } + PUint8(PtrUInt(cfgBuf) + 16)^ := $01; { bInterfaceProtocol (driver.hid.keyboard) } { Endpoint at offset 18: EP1 IN Interrupt, maxpacket=8, interval=10 } - PUint8(uint32(cfgBuf) + 18)^ := 7; { bLength } - PUint8(uint32(cfgBuf) + 19)^ := USB_DESC_ENDPOINT; - PUint8(uint32(cfgBuf) + 20)^ := $81; { bEndpointAddress: EP1 IN } - PUint8(uint32(cfgBuf) + 21)^ := USB_EP_ATTR_INTERRUPT; { bmAttributes } - PUint8(uint32(cfgBuf) + 22)^ := 8; { wMaxPacketSize low } - PUint8(uint32(cfgBuf) + 23)^ := 0; { wMaxPacketSize high } - PUint8(uint32(cfgBuf) + 24)^ := 10; { bInterval } + PUint8(PtrUInt(cfgBuf) + 18)^ := 7; { bLength } + PUint8(PtrUInt(cfgBuf) + 19)^ := USB_DESC_ENDPOINT; + PUint8(PtrUInt(cfgBuf) + 20)^ := $81; { bEndpointAddress: EP1 IN } + PUint8(PtrUInt(cfgBuf) + 21)^ := USB_EP_ATTR_INTERRUPT; { bmAttributes } + PUint8(PtrUInt(cfgBuf) + 22)^ := 8; { wMaxPacketSize low } + PUint8(PtrUInt(cfgBuf) + 23)^ := 0; { wMaxPacketSize high } + PUint8(PtrUInt(cfgBuf) + 24)^ := 10; { bInterval } { Endpoint at offset 25: EP2 OUT Bulk, maxpacket=64 } - PUint8(uint32(cfgBuf) + 25)^ := 7; { bLength } - PUint8(uint32(cfgBuf) + 26)^ := USB_DESC_ENDPOINT; - PUint8(uint32(cfgBuf) + 27)^ := $02; { bEndpointAddress: EP2 OUT } - PUint8(uint32(cfgBuf) + 28)^ := USB_EP_ATTR_BULK; { bmAttributes } - PUint8(uint32(cfgBuf) + 29)^ := 64; { wMaxPacketSize low } - PUint8(uint32(cfgBuf) + 30)^ := 0; { wMaxPacketSize high } - PUint8(uint32(cfgBuf) + 31)^ := 0; { bInterval } + PUint8(PtrUInt(cfgBuf) + 25)^ := 7; { bLength } + PUint8(PtrUInt(cfgBuf) + 26)^ := USB_DESC_ENDPOINT; + PUint8(PtrUInt(cfgBuf) + 27)^ := $02; { bEndpointAddress: EP2 OUT } + PUint8(PtrUInt(cfgBuf) + 28)^ := USB_EP_ATTR_BULK; { bmAttributes } + PUint8(PtrUInt(cfgBuf) + 29)^ := 64; { wMaxPacketSize low } + PUint8(PtrUInt(cfgBuf) + 30)^ := 0; { wMaxPacketSize high } + PUint8(PtrUInt(cfgBuf) + 31)^ := 0; { bInterval } { Interface 1 at offset 32: class=$08 (mass storage), 1 endpoint } - PUint8(uint32(cfgBuf) + 32)^ := 9; { bLength } - PUint8(uint32(cfgBuf) + 33)^ := USB_DESC_INTERFACE; - PUint8(uint32(cfgBuf) + 34)^ := 1; { bInterfaceNumber } - PUint8(uint32(cfgBuf) + 36)^ := 1; { bNumEndpoints } - PUint8(uint32(cfgBuf) + 37)^ := USB_CLASS_MASS_STORAGE; { bInterfaceClass } + PUint8(PtrUInt(cfgBuf) + 32)^ := 9; { bLength } + PUint8(PtrUInt(cfgBuf) + 33)^ := USB_DESC_INTERFACE; + PUint8(PtrUInt(cfgBuf) + 34)^ := 1; { bInterfaceNumber } + PUint8(PtrUInt(cfgBuf) + 36)^ := 1; { bNumEndpoints } + PUint8(PtrUInt(cfgBuf) + 37)^ := USB_CLASS_MASS_STORAGE; { bInterfaceClass } { Endpoint at offset 41: EP3 IN Bulk, maxpacket=512 } - PUint8(uint32(cfgBuf) + 41)^ := 7; { bLength } - PUint8(uint32(cfgBuf) + 42)^ := USB_DESC_ENDPOINT; - PUint8(uint32(cfgBuf) + 43)^ := $83; { bEndpointAddress: EP3 IN } - PUint8(uint32(cfgBuf) + 44)^ := USB_EP_ATTR_BULK; { bmAttributes } - PUint8(uint32(cfgBuf) + 45)^ := 0; { wMaxPacketSize low = 512 = $0200 } - PUint8(uint32(cfgBuf) + 46)^ := 2; { wMaxPacketSize high } - PUint8(uint32(cfgBuf) + 47)^ := 0; { bInterval } + PUint8(PtrUInt(cfgBuf) + 41)^ := 7; { bLength } + PUint8(PtrUInt(cfgBuf) + 42)^ := USB_DESC_ENDPOINT; + PUint8(PtrUInt(cfgBuf) + 43)^ := $83; { bEndpointAddress: EP3 IN } + PUint8(PtrUInt(cfgBuf) + 44)^ := USB_EP_ATTR_BULK; { bmAttributes } + PUint8(PtrUInt(cfgBuf) + 45)^ := 0; { wMaxPacketSize low = 512 = $0200 } + PUint8(PtrUInt(cfgBuf) + 46)^ := 2; { wMaxPacketSize high } + PUint8(PtrUInt(cfgBuf) + 47)^ := 0; { bInterval } { --- usb_find_interface (first interface) --- } ifDesc := usb_find_interface(cfgBuf, 48); diff --git a/src/driver/bus/usb/driver.bus.usb.hub.pas b/src/driver/bus/usb/driver.bus.usb.hub.pas index 6a48ffff..ba8866e3 100644 --- a/src/driver/bus/usb/driver.bus.usb.hub.pas +++ b/src/driver/bus/usb/driver.bus.usb.hub.pas @@ -15,7 +15,7 @@ interface uses debug.tracer, io.syslog, - core.util, arch.x86.util, + core.util, arch.platform.cpu, arch.platform.diagnostics, memory.heap, driver.types, driver.mgr, @@ -315,7 +315,7 @@ begin io.syslog.writeintln(depth); { Step 1: Get hub descriptor } - memset(uint32(@hubDesc), 0, sizeof(TUSBHubDescriptor)); + memset(PtrUInt(@hubDesc), 0, sizeof(TUSBHubDescriptor)); if not hub_get_descriptor(dev, @hubDesc, sizeof(TUSBHubDescriptor)) then begin io.syslog.logln('USBHub', 'Failed to get hub descriptor.'); pop_trace; @@ -344,7 +344,7 @@ begin pop_trace; exit; end; - memset(uint32(hub), 0, sizeof(TUSBHubData)); + memset(PtrUInt(hub), 0, sizeof(TUSBHubData)); hub^.Device := dev; hub^.NumPorts := hubDesc.bNbrPorts; hub^.PwrOn2PwrGood := hubDesc.bPwrOn2PwrGood; @@ -378,7 +378,7 @@ begin if HubList <> nil then begin hubEntry := PUSBHubData(LL_Add(HubList)); if hubEntry <> nil then - memcpy(uint32(hub), uint32(hubEntry), sizeof(TUSBHubData)); + memcpy(PtrUInt(hub), PtrUInt(hubEntry), sizeof(TUSBHubData)); end; { Step 6: Scan ports for connected devices } @@ -649,20 +649,8 @@ var end; procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('USBHub', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); + testSummary('USBHub', 'USBHUB_TESTS_PASSED', 'USBHUB_TESTS_FAILED', passed, failed); end; begin diff --git a/src/driver/bus/usb/driver.bus.usb.ohci.pas b/src/driver/bus/usb/driver.bus.usb.ohci.pas index 528d2623..acebdecf 100644 --- a/src/driver/bus/usb/driver.bus.usb.ohci.pas +++ b/src/driver/bus/usb/driver.bus.usb.ohci.pas @@ -31,14 +31,13 @@ uses io.syslog, driver.bus.pci, driver.types, - arch.x86.memory.physical, - arch.x86.memory.virtual, + arch.platform.memory, memory.heap, - core.util, arch.x86.util, + core.util, arch.platform.cpu, arch.platform.diagnostics, driver.mgr, driver.bus.usb.types, driver.bus.usb.core, - arch.x86.isr.mgr, + arch.platform.interrupts, core.ds.lists, core.strings; @@ -223,8 +222,8 @@ type { Software fields - beyond the 16 bytes the HC reads } SWNext : POHCI_ED; { Software linked list for traversal } SWTransfer : Pointer; { Back-pointer to owning TUSBTransfer } - SWPad1 : uint32; - SWPad2 : uint32; + SWPad1 : PtrUInt; + SWPad2 : PtrUInt; end; { General Transfer Descriptor (TD) - 16 bytes, 16-byte aligned } @@ -244,7 +243,7 @@ type { Private data for an driver.bus.usb.ohci host controller instance } POHCI_PrivData = ^TOHCI_PrivData; TOHCI_PrivData = record - MMIOBase : uint32; { MMIO base address (virtual = physical, identity mapped) } + MMIOBase : PtrUInt; { MMIO base address (virtual = physical, identity mapped) } HCCA : POHCI_HCCA; { 256-byte aligned HCCA } EDControl : POHCI_ED; { Head of control ED list } EDBulk : POHCI_ED; { Head of bulk ED list } @@ -258,24 +257,49 @@ type { ========================= MMIO Helpers ========================= } -procedure ohci_write(base : uint32; reg : uint32; val : uint32); +procedure ohci_write(base : PtrUInt; reg : uint32; val : uint32); begin PUint32(base + reg)^ := val; end; -function ohci_read(base : uint32; reg : uint32) : uint32; +function ohci_read(base : PtrUInt; reg : uint32) : uint32; begin ohci_read := PUint32(base + reg)^; end; +{ OHCI exposes only 32-bit DMA addresses. Validate every backing page before + publishing descriptors; a high virtual address is valid when its backing RAM + is below 4 GiB. Zero-length transfers do not need a buffer. } +function ohci_dma_addressable(address: PtrUInt; bytes: uint32): boolean; +var + physical: uint64; + chunk: uint32; +begin + ohci_dma_addressable := false; + if bytes > High(PtrUInt) - address then exit; + while bytes > 0 do begin + physical := arch.platform.memory.physicalAddress(address); + if (physical = 0) or (physical > $FFFFFFFF) then exit; + chunk := 4096 - (address and 4095); + if chunk > bytes then chunk := bytes; + inc(address, chunk); + dec(bytes, chunk); + end; + ohci_dma_addressable := true; +end; + { ========================= ED/TD Construction Helpers ========================= } { Allocate an ED (16-byte aligned) } function ohci_alloc_ed : POHCI_ED; begin ohci_alloc_ed := POHCI_ED(kalloc_aligned(sizeof(TOHCI_ED), OHCI_ED_ALIGN)); - if ohci_alloc_ed <> nil then - memset(uint32(ohci_alloc_ed), 0, sizeof(TOHCI_ED)); + if ohci_alloc_ed <> nil then begin + if not ohci_dma_addressable(PtrUInt(ohci_alloc_ed), 16) then begin + kfree_aligned(ohci_alloc_ed); + ohci_alloc_ed := nil; + end; + end; end; { Free an ED } @@ -289,8 +313,12 @@ end; function ohci_alloc_td : POHCI_TD; begin ohci_alloc_td := POHCI_TD(kalloc_aligned(sizeof(TOHCI_TD), OHCI_TD_ALIGN)); - if ohci_alloc_td <> nil then - memset(uint32(ohci_alloc_td), 0, sizeof(TOHCI_TD)); + if ohci_alloc_td <> nil then begin + if not ohci_dma_addressable(PtrUInt(ohci_alloc_td), 16) then begin + kfree_aligned(ohci_alloc_td); + ohci_alloc_td := nil; + end; + end; end; { Free a TD } @@ -379,7 +407,7 @@ function ohci_port_status(hc : PUSBHCDriver; port : uint8) : uint32; forward; function ohci_reset(hc : PUSBHCDriver) : boolean; var priv : POHCI_PrivData; - base : uint32; + base : PtrUInt; loops : uint32; fmival : uint32; begin @@ -458,7 +486,11 @@ begin pop_trace; exit; end; - memset(uint32(hcca), 0, OHCI_HCCA_SIZE); + if not ohci_dma_addressable(PtrUInt(hcca), OHCI_HCCA_SIZE) then begin + kfree_aligned(hcca); + pop_trace; + exit; + end; priv^.HCCA := hcca; { Allocate dummy/sentinel EDs for each list head } @@ -494,14 +526,14 @@ begin { Fill all 32 HCCA interrupt table entries with our interrupt sentinel ED (physical) } for i := 0 to OHCI_HCCA_INTR_TABLE_SIZE - 1 do - hcca^.HccaInterruptTable[i] := vtop(uint32(priv^.EDInterrupt)); + hcca^.HccaInterruptTable[i] := arch.platform.memory.physicalAddress(PtrUInt(priv^.EDInterrupt)); { Set HC's HCCA pointer (physical) } - ohci_write(priv^.MMIOBase, OHCI_REG_HCCA, vtop(uint32(hcca))); + ohci_write(priv^.MMIOBase, OHCI_REG_HCCA, arch.platform.memory.physicalAddress(PtrUInt(hcca))); { Set control and bulk list heads (physical) } - ohci_write(priv^.MMIOBase, OHCI_REG_CONTROL_HEAD_ED, vtop(uint32(priv^.EDControl))); - ohci_write(priv^.MMIOBase, OHCI_REG_BULK_HEAD_ED, vtop(uint32(priv^.EDBulk))); + ohci_write(priv^.MMIOBase, OHCI_REG_CONTROL_HEAD_ED, arch.platform.memory.physicalAddress(PtrUInt(priv^.EDControl))); + ohci_write(priv^.MMIOBase, OHCI_REG_BULK_HEAD_ED, arch.platform.memory.physicalAddress(PtrUInt(priv^.EDBulk))); io.syslog.logln('driver.bus.usb.ohci', 'Schedule configured.'); pop_trace; @@ -512,7 +544,7 @@ end; function ohci_start(hc : PUSBHCDriver) : boolean; var priv : POHCI_PrivData; - base : uint32; + base : PtrUInt; ctrl : uint32; rha : uint32; loops : uint32; @@ -527,6 +559,12 @@ begin priv := POHCI_PrivData(hc^.PrivData); base := priv^.MMIOBase; + if (priv^.HCCA = nil) or (priv^.EDControl = nil) or + (priv^.EDBulk = nil) or (priv^.EDInterrupt = nil) then begin + pop_trace; + exit; + end; + { Transition to USBOperational, enable control + bulk core.ds.lists } ctrl := OHCI_CTRL_HCFS_OPERATIONAL OR OHCI_CTRL_CLE @@ -620,7 +658,7 @@ end; function ohci_port_reset(hc : PUSBHCDriver; port : uint8) : boolean; var priv : POHCI_PrivData; - base : uint32; + base : PtrUInt; regOfs : uint32; status : uint32; loops : uint32; @@ -688,7 +726,7 @@ begin if (sentinel = nil) or (ed = nil) then exit; ed^.NextED := sentinel^.NextED; ed^.SWNext := sentinel^.SWNext; - sentinel^.NextED := vtop(uint32(ed)); + sentinel^.NextED := arch.platform.memory.physicalAddress(PtrUInt(ed)); sentinel^.SWNext := ed; end; @@ -735,8 +773,8 @@ begin td^.Control := ohci_make_td_control(OHCI_TD_DP_SETUP, OHCI_TD_DT_DATA0, OHCI_TD_DI_NONE, false); - td^.CBP := vtop(uint32(@transfer^.Setup)); - td^.BE := vtop(uint32(@transfer^.Setup) + 7); + td^.CBP := arch.platform.memory.physicalAddress(PtrUInt(@transfer^.Setup)); + td^.BE := arch.platform.memory.physicalAddress(PtrUInt(@transfer^.Setup) + 7); td^.NextTD := 0; td^.SWTransfer := Pointer(transfer); td^.SWNext := nil; @@ -764,9 +802,9 @@ begin td^.Control := ohci_make_td_control(dirPID, toggle, OHCI_TD_DI_NONE, true); - td^.CBP := vtop(uint32(transfer^.Buffer) + offset); + td^.CBP := arch.platform.memory.physicalAddress(PtrUInt(transfer^.Buffer) + offset); if pktLen > 0 then - td^.BE := vtop(uint32(transfer^.Buffer) + offset + uint32(pktLen) - 1) + td^.BE := arch.platform.memory.physicalAddress(PtrUInt(transfer^.Buffer) + offset + uint32(pktLen) - 1) else td^.BE := 0; td^.SWPad := pktLen; { Original data length, excluding SETUP/status } @@ -775,7 +813,7 @@ begin td^.SWNext := nil; { Link previous TD (NextTD = physical) } - prevTD^.NextTD := vtop(uint32(td)); + prevTD^.NextTD := arch.platform.memory.physicalAddress(PtrUInt(td)); prevTD^.SWNext := td; prevTD := td; @@ -807,13 +845,13 @@ begin td^.SWTransfer := Pointer(transfer); td^.SWNext := nil; - prevTD^.NextTD := vtop(uint32(td)); + prevTD^.NextTD := arch.platform.memory.physicalAddress(PtrUInt(td)); prevTD^.SWNext := td; { Allocate a dummy/sentinel TD for the tail pointer (driver.bus.usb.ohci requires HeadP <> TailP to process) } tailTD := ohci_alloc_td; if tailTD = nil then begin ohci_free_td_chain(firstTD); pop_trace; exit; end; - td^.NextTD := vtop(uint32(tailTD)); + td^.NextTD := arch.platform.memory.physicalAddress(PtrUInt(tailTD)); td^.SWNext := tailTD; { === Create ED === } @@ -822,12 +860,12 @@ begin ed^.Control := ohci_make_ed_control(devAddr, 0, OHCI_ED_DIR_FROM_TD, speedBit, maxPkt, false); - ed^.HeadP := vtop(uint32(firstTD)); { First real TD (physical) } - ed^.TailP := vtop(uint32(tailTD)); { Sentinel past last TD (physical) } + ed^.HeadP := arch.platform.memory.physicalAddress(PtrUInt(firstTD)); { First real TD (physical) } + ed^.TailP := arch.platform.memory.physicalAddress(PtrUInt(tailTD)); { Sentinel past last TD (physical) } ed^.NextED := 0; ed^.SWTransfer := Pointer(transfer); - ed^.SWPad1 := uint32(firstTD); { Virtual firstTD for cleanup } - ed^.SWPad2 := uint32(tailTD); { Virtual tailTD for cleanup } + ed^.SWPad1 := PtrUInt(firstTD); { Virtual firstTD for cleanup } + ed^.SWPad2 := PtrUInt(tailTD); { Virtual tailTD for cleanup } { Store ED in transfer for cleanup and poll } transfer^.HCPriv := Pointer(ed); @@ -924,8 +962,8 @@ begin td^.Control := ohci_make_td_control(dirPID, toggle, OHCI_TD_DI_NONE, transfer^.PipeType <> ptBulk); if pktLen > 0 then begin - td^.CBP := vtop(uint32(transfer^.Buffer) + offset); - td^.BE := vtop(uint32(transfer^.Buffer) + offset + uint32(pktLen) - 1) + td^.CBP := arch.platform.memory.physicalAddress(PtrUInt(transfer^.Buffer) + offset); + td^.BE := arch.platform.memory.physicalAddress(PtrUInt(transfer^.Buffer) + offset + uint32(pktLen) - 1) end else begin td^.CBP := 0; td^.BE := 0; @@ -936,7 +974,7 @@ begin td^.SWNext := nil; if prevTD <> nil then begin - prevTD^.NextTD := vtop(uint32(td)); + prevTD^.NextTD := arch.platform.memory.physicalAddress(PtrUInt(td)); prevTD^.SWNext := td; end; prevTD := td; @@ -966,7 +1004,7 @@ begin { Allocate tail sentinel TD } tailTD := ohci_alloc_td; if tailTD = nil then begin ohci_free_td_chain(firstTD); pop_trace; exit; end; - prevTD^.NextTD := vtop(uint32(tailTD)); + prevTD^.NextTD := arch.platform.memory.physicalAddress(PtrUInt(tailTD)); prevTD^.SWNext := tailTD; { Create ED } @@ -975,14 +1013,14 @@ begin ed^.Control := ohci_make_ed_control(devAddr, epNum, edDir, speedBit, maxPkt, false); - ed^.HeadP := vtop(uint32(firstTD)); + ed^.HeadP := arch.platform.memory.physicalAddress(PtrUInt(firstTD)); if transfer^.PipeType = ptBulk then ed^.HeadP := ed^.HeadP or ((uint32(transfer^.Endpoint^.Toggle) and 1) shl 1); - ed^.TailP := vtop(uint32(tailTD)); + ed^.TailP := arch.platform.memory.physicalAddress(PtrUInt(tailTD)); ed^.NextED := 0; ed^.SWTransfer := Pointer(transfer); - ed^.SWPad1 := uint32(firstTD); { Virtual firstTD for cleanup } - ed^.SWPad2 := uint32(tailTD); { Virtual tailTD for cleanup } + ed^.SWPad1 := PtrUInt(firstTD); { Virtual firstTD for cleanup } + ed^.SWPad2 := PtrUInt(tailTD); { Virtual tailTD for cleanup } transfer^.HCPriv := Pointer(ed); transfer^.Status := tsInProgress; @@ -1009,6 +1047,12 @@ begin exit; end; + if not ohci_dma_addressable(PtrUInt(transfer^.Buffer), transfer^.BufferLen) or + not ohci_dma_addressable(PtrUInt(@transfer^.Setup), sizeof(TUSBSetupPacket)) then begin + pop_trace; + exit; + end; + case transfer^.PipeType of ptControl: ohci_submit := ohci_submit_control(hc, transfer); @@ -1071,6 +1115,7 @@ var priv : POHCI_PrivData; intSts : uint32; begin + if OHCIInstanceCount = 0 then exit; for i := 0 to OHCIInstanceCount - 1 do begin hc := OHCIInstances[i]; if hc = nil then continue; @@ -1127,7 +1172,7 @@ end; procedure ohci_poll(hc : PUSBHCDriver); var priv : POHCI_PrivData; - base : uint32; + base : PtrUInt; intSts : uint32; sentinel : POHCI_ED; ed : POHCI_ED; @@ -1236,8 +1281,7 @@ var priv : POHCI_PrivData; hc : TUSBHCDriver; hcEntry : PUSBHCDriver; - mmioBase : uint32; - block : uint32; + mmioBase : PtrUInt; begin push_trace('driver.bus.usb.ohci.load'); load := false; @@ -1272,9 +1316,8 @@ begin end; { Identity-map the MMIO region so we can access it } - block := mmioBase SHR 22; - force_alloc_block(block, 0); - map_page(block, block); + mmioBase := PtrUInt(arch.platform.memory.mapDevice(mmioBase, 4096)); + if mmioBase = 0 then continue; { Enable driver.bus.pci bus mastering } driver.bus.pci.setBusMaster(devices[i].bus, devices[i].slot, devices[i].func, true); @@ -1285,7 +1328,7 @@ begin io.syslog.logln('driver.bus.usb.ohci', 'Failed to allocate private data!'); continue; end; - memset(uint32(priv), 0, sizeof(TOHCI_PrivData)); + memset(PtrUInt(priv), 0, sizeof(TOHCI_PrivData)); priv^.MMIOBase := mmioBase; priv^.PCIBus := devices[i].bus; priv^.PCISlot := devices[i].slot; @@ -1348,7 +1391,8 @@ begin { Register ISR on driver.bus.pci interrupt line } io.syslog.log('driver.bus.usb.ohci', 'Registering ISR on IRQ '); io.syslog.writeintln(devices[i].interrupt_line); - arch.x86.isr.mgr.registerISR(32 + devices[i].interrupt_line, @ohci_isr); + arch.platform.interrupts.registerIRQ(32 + devices[i].interrupt_line, @ohci_isr); + arch.platform.interrupts.routePCI(devices[i].interrupt_line, 32 + devices[i].interrupt_line); { Enable hardware interrupts } ohci_enable_interrupts(hcEntry); @@ -1386,20 +1430,8 @@ var end; procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('driver.bus.usb.ohci', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); + testSummary('OHCI', 'OHCI_TESTS_PASSED', 'OHCI_TESTS_FAILED', passed, failed); end; var @@ -1414,9 +1446,12 @@ begin io.syslog.logln('driver.bus.usb.ohci', 'Unit tests starting...'); { === Structure Sizes === } + Assert(ohci_dma_addressable(0, 0), 'empty transfer requires no DMA buffer'); + Assert(not ohci_dma_addressable(0, 16), 'unmapped DMA buffer rejected'); + Assert(not ohci_dma_addressable(High(PtrUInt) - 7, 16), 'DMA address wrap rejected'); Assert(sizeof(TOHCI_HCCA) = 256, 'sizeof HCCA=256'); - Assert(sizeof(TOHCI_ED) = 32, 'sizeof ED=32'); - Assert(sizeof(TOHCI_TD) = 32, 'sizeof TD=32'); + Assert(PtrUInt(@POHCI_ED(nil)^.SWNext) = 16, 'ED hardware prefix remains 16 bytes'); + Assert(PtrUInt(@POHCI_TD(nil)^.SWNext) = 16, 'TD hardware prefix remains 16 bytes'); { === ED Control Building === } { Addr 0, EP0, direction from TD, full speed, maxPkt 8, general } @@ -1487,7 +1522,11 @@ begin { === ED Allocation & Alignment === } ed := ohci_alloc_ed; Assert(ed <> nil, 'alloc_ed not nil'); - Assert((uint32(ed) AND $0F) = 0, 'alloc_ed 16-byte aligned'); + Assert((PtrUInt(ed) AND $0F) = 0, 'alloc_ed 16-byte aligned'); + if ed <> nil then begin + ed^.SWPad1 := PtrUInt(ed); + Assert(POHCI_ED(ed^.SWPad1) = ed, 'ED software pointer retains native width'); + end; Assert(ed^.Control = 0, 'alloc_ed zeroed control'); Assert(ed^.TailP = 0, 'alloc_ed zeroed tailp'); Assert(ed^.HeadP = 0, 'alloc_ed zeroed headp'); @@ -1497,7 +1536,7 @@ begin { === TD Allocation & Alignment === } td := ohci_alloc_td; Assert(td <> nil, 'alloc_td not nil'); - Assert((uint32(td) AND $0F) = 0, 'alloc_td 16-byte aligned'); + Assert((PtrUInt(td) AND $0F) = 0, 'alloc_td 16-byte aligned'); Assert(td^.Control = 0, 'alloc_td zeroed control'); Assert(td^.CBP = 0, 'alloc_td zeroed cbp'); Assert(td^.NextTD = 0, 'alloc_td zeroed nexttd'); @@ -1519,7 +1558,7 @@ begin Assert(OHCI_HCCA_ALIGN = 256, 'HCCA_ALIGN=256'); hcca := POHCI_HCCA(kalloc_aligned(OHCI_HCCA_SIZE, OHCI_HCCA_ALIGN)); Assert(hcca <> nil, 'hcca alloc not nil'); - Assert((uint32(hcca) AND $FF) = 0, 'hcca 256-byte aligned'); + Assert((PtrUInt(hcca) AND $FF) = 0, 'hcca 256-byte aligned'); kfree_aligned(hcca); { === Register offset constants === } diff --git a/src/driver/bus/usb/driver.bus.usb.types.pas b/src/driver/bus/usb/driver.bus.usb.types.pas index cc54b8e2..002b7b6d 100644 --- a/src/driver/bus/usb/driver.bus.usb.types.pas +++ b/src/driver/bus/usb/driver.bus.usb.types.pas @@ -23,7 +23,7 @@ unit driver.bus.usb.types; interface uses - core.util, arch.x86.util, + core.util, arch.platform.cpu, arch.platform.diagnostics, memory.heap, driver.types, core.ds.lists, @@ -229,6 +229,7 @@ type { ---- Forward Declarations ---- } PUSBDevice = ^TUSBDevice; + PPUSBDevice = ^PUSBDevice; PUSBHCDriver = ^TUSBHCDriver; PUSBTransfer = ^TUSBTransfer; @@ -286,7 +287,7 @@ type HCType : uint8; { USB_HC_UHCI / driver.bus.usb.ohci / driver.bus.usb.ehci / driver.bus.usb.xhci } NumPorts : uint8; PCIDev : TPCI_Device; { Owning driver.bus.pci device } - BaseAddr : uint32; { I/O base (driver.bus.usb.uhci) or MMIO base (driver.bus.usb.ohci/driver.bus.usb.ehci/driver.bus.usb.xhci) } + BaseAddr : PtrUInt; { I/O base (driver.bus.usb.uhci) or MMIO base (driver.bus.usb.ohci/driver.bus.usb.ehci/driver.bus.usb.xhci) } PrivData : Pointer; { HC-specific state } Devices : PLinkedListBase; { List of PUSBDevice attached } NextAddress : uint8; { Next available driver.bus.usb address (1-127) } @@ -341,37 +342,31 @@ implementation { ========================= Alignment Helpers ========================= } function kalloc_aligned(size : uint32; alignment : uint32) : Pointer; +type + PAllocation = ^PtrUInt; var - raw : uint32; - aligned : uint32; - overhead : uint32; + raw, aligned, overhead: PtrUInt; begin - push_trace('driver.bus.usb.types.kalloc_aligned'); - if alignment < 4 then alignment := 4; - overhead := alignment + sizeof(uint32); - raw := uint32(kalloc(size + overhead)); - if raw = 0 then begin - kalloc_aligned := nil; - pop_trace; - exit; - end; - aligned := (raw + sizeof(uint32) + alignment - 1) AND (NOT (alignment - 1)); - { Store original raw pointer just before the aligned address } - PUint32(aligned - 4)^ := raw; - kalloc_aligned := Pointer(aligned); - pop_trace; + kalloc_aligned := nil; + if (size = 0) or (alignment = 0) or + ((alignment and (alignment - 1)) <> 0) then exit; + if alignment < sizeof(PtrUInt) then alignment := sizeof(PtrUInt); + overhead := PtrUInt(alignment) - 1 + sizeof(PtrUInt); + if PtrUInt(size) > High(PtrUInt) - overhead then exit; + raw := PtrUInt(kalloc(PtrUInt(size) + overhead)); + if raw = 0 then exit; + aligned := (raw + sizeof(PtrUInt) + alignment - 1) and + not (PtrUInt(alignment) - 1); + PAllocation(aligned - sizeof(PtrUInt))^ := raw; + kalloc_aligned := pointer(aligned); end; procedure kfree_aligned(p : Pointer); -var - raw : uint32; +type + PAllocation = ^PtrUInt; begin - push_trace('driver.bus.usb.types.kfree_aligned'); - if p <> nil then begin - raw := PUint32(uint32(p) - 4)^; - kfree(void(raw)); - end; - pop_trace; + if p <> nil then + kfree(void(PAllocation(PtrUInt(p) - sizeof(PtrUInt))^)); end; { ========================= Setup Packet Builder ========================= } @@ -457,6 +452,7 @@ var passed, failed : uint32; p : Pointer; setup : TUSBSetupPacket; + beforeFree: SizeUInt; procedure Assert(condition : boolean; testName : pchar); var @@ -473,20 +469,8 @@ var end; procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('USBTYPES', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); + testSummary('USBTYPES', 'USBTYPES_TESTS_PASSED', 'USBTYPES_TESTS_FAILED', passed, failed); end; begin @@ -502,35 +486,41 @@ begin Assert(sizeof(TUSBSetupPacket) = 8, 'sizeof SetupPacket=8'); Assert(sizeof(TUSBHubDescriptor) = 7, 'sizeof HubDescriptor=7'); + beforeFree := lmm_total_free; + Assert(kalloc_aligned(0, 16) = nil, 'zero allocation rejected'); + Assert(kalloc_aligned(16, 0) = nil, 'zero alignment rejected'); + Assert(kalloc_aligned(16, 24) = nil, 'non-power-of-two alignment rejected'); { === kalloc_aligned / kfree_aligned === } { 16-byte alignment } p := kalloc_aligned(32, 16); Assert(p <> nil, 'kalloc_aligned(32,16) not nil'); - Assert((uint32(p) AND $0F) = 0, 'kalloc_aligned(32,16) aligned to 16'); + Assert((PtrUInt(p) AND $0F) = 0, 'kalloc_aligned(32,16) aligned to 16'); kfree_aligned(p); { 256-byte alignment (HCCA) } p := kalloc_aligned(256, 256); Assert(p <> nil, 'kalloc_aligned(256,256) not nil'); - Assert((uint32(p) AND $FF) = 0, 'kalloc_aligned(256,256) aligned to 256'); + Assert((PtrUInt(p) AND $FF) = 0, 'kalloc_aligned(256,256) aligned to 256'); kfree_aligned(p); { 4096-byte alignment (frame list) } p := kalloc_aligned(4096, 4096); Assert(p <> nil, 'kalloc_aligned(4096,4096) not nil'); - Assert((uint32(p) AND $FFF) = 0, 'kalloc_aligned(4096,4096) aligned to 4096'); + Assert((PtrUInt(p) AND $FFF) = 0, 'kalloc_aligned(4096,4096) aligned to 4096'); kfree_aligned(p); { 32-byte alignment (qTD) } p := kalloc_aligned(32, 32); Assert(p <> nil, 'kalloc_aligned(32,32) not nil'); - Assert((uint32(p) AND $1F) = 0, 'kalloc_aligned(32,32) aligned to 32'); + Assert((PtrUInt(p) AND $1F) = 0, 'kalloc_aligned(32,32) aligned to 32'); kfree_aligned(p); { kfree_aligned(nil) should not crash } kfree_aligned(nil); Assert(true, 'kfree_aligned(nil) no crash'); + Assert(lmm_total_free = beforeFree, 'aligned allocations release native base pointers'); + { === make_setup_packet === } setup := make_setup_packet($80, USB_REQ_GET_DESCRIPTOR, $0100, $0000, 18); Assert(setup.bmRequestType = $80, 'setup bmRequestType=$80'); diff --git a/src/driver/driver.mgr.pas b/src/driver/driver.mgr.pas index 323a42f9..68f75ebe 100644 --- a/src/driver/driver.mgr.pas +++ b/src/driver/driver.mgr.pas @@ -22,8 +22,7 @@ unit driver.mgr; interface uses - boot.mgr, - io.syslog, io.stdio, core.util, arch.x86.util, core.strings, memory.heap, debug.tracer; + io.syslog, io.stdio, core.util, arch.platform.cpu, core.strings, memory.heap, debug.tracer; const idANY = $FFFFFFFF; @@ -424,7 +423,4 @@ begin pop_trace; end; -initialization - boot.mgr.registerBoot('driver.mgr', @init, 'Driver Manager Initialization', BOOT_MGR_BARRIER_MID); - -end. \ No newline at end of file +end. diff --git a/src/driver/hid/driver.hid.mouse.pas b/src/driver/hid/driver.hid.mouse.pas index e88702ff..7b912173 100644 --- a/src/driver/hid/driver.hid.mouse.pas +++ b/src/driver/hid/driver.hid.mouse.pas @@ -12,9 +12,6 @@ unit driver.hid.mouse; interface -uses - driver.video; - type TMouseEventType = ( MOUSE_MOVE, @@ -36,6 +33,7 @@ function getMouseRMB: boolean; function getMouseScroll: sint32; { Setters - called by driver.hid.mouse drivers (PS/2, driver.bus.usb) } +procedure setBounds(width, height: uint32); procedure setMousePos(x, y: sint32); procedure setMouseLMB(down: boolean); procedure setMouseRMB(down: boolean); @@ -53,6 +51,7 @@ const MAX_MOUSE_HOOKS = 8; var + maxW, maxH : sint32; MouseX : sint32 = 0; MouseY : sint32 = 0; MouseLMB : boolean = false; @@ -87,12 +86,17 @@ begin ScrollAccum := 0; end; +procedure setBounds(width, height: uint32); +begin + if width > $7FFFFFFF then width := $7FFFFFFF; + if height > $7FFFFFFF then height := $7FFFFFFF; + maxW := sint32(width); + maxH := sint32(height); + setMousePos(MouseX, MouseY); +end; + procedure setMousePos(x, y: sint32); -var - maxW, maxH : sint32; begin - maxW := sint32(driver.video.frontBufferWidth); - maxH := sint32(driver.video.frontBufferHeight); if x < 0 then x := 0; if y < 0 then y := 0; if (maxW > 0) and (x >= maxW) then x := maxW - 1; diff --git a/src/driver/hid/ps2/driver.hid.ps2.keyboard.pas b/src/driver/hid/ps2/driver.hid.ps2.keyboard.pas index 98ce4b39..099ab201 100644 --- a/src/driver/hid/ps2/driver.hid.ps2.keyboard.pas +++ b/src/driver/hid/ps2/driver.hid.ps2.keyboard.pas @@ -12,18 +12,17 @@ unit driver.hid.ps2.keyboard; interface uses - boot.mgr, driver.hid.keyboard, io.syslog, - core.util, arch.x86.util, - arch.x86.isr.ps2keyboard; + core.util, arch.platform.cpu, + arch.platform.interrupts, arch.common.legacyirq; var - key_matrix : array [1..256] of TKeyInfo; - key_matrix_shift : array [1..256] of TKeyInfo; + key_matrix : array [0..255] of TKeyInfo; + key_matrix_shift : array [0..255] of TKeyInfo; procedure init(); -procedure init_layout(keyboard_layout : array of TKeyInfo); +procedure init_layout(const keyboard_layout : array of TKeyInfo); procedure disable; procedure lang_USA(); @@ -32,14 +31,16 @@ implementation uses driver.mgr; -procedure callback(scan_code : void); +var enabled: boolean = false; + +procedure callback(scan_code : uint8); var info : TKeyInfo; raw : uint8; baseCode : uint8; isBreak : boolean; begin - raw := uint8(scan_code); + raw := scan_code; { In PS/2 scancode set 1, bit 7 set = key release (break code) } isBreak := (raw AND $80) <> 0; @@ -71,26 +72,36 @@ begin end; end; +procedure keyboardIRQ; +var + code: uint8; +begin + if (inb($64) and $21) <> 1 then exit; + code := inb($60); + if enabled then callback(code); +end; + function load(ptr : void) : boolean; begin - arch.x86.isr.ps2keyboard.register(); - arch.x86.isr.ps2keyboard.hook(uint32(@callback)); + enabled := true; + arch.platform.interrupts.registerIRQ(33, @keyboardIRQ); + arch.common.legacyirq.enableLine(1); io.syslog.logln('PS/2 KEYBOARD', 'LOADED.'); load := true; end; procedure disable; begin - arch.x86.isr.ps2keyboard.unhook(uint32(@callback)); + enabled := false; io.syslog.logln('PS/2 KEYBOARD', 'Disabled (driver.bus.usb driver.hid.keyboard active).'); end; -procedure init_layout(keyboard_layout : array of TKeyInfo); +procedure init_layout(const keyboard_layout : array of TKeyInfo); var devid : TDeviceIdentifier; begin io.syslog.logln('PS/2 KEYBOARD', 'INIT BEGIN.'); - if keyboard_layout[1].key_code = 0 then lang_USA(); + if (High(keyboard_layout) < 1) or (keyboard_layout[1].key_code = 0) then lang_USA(); devid.bus := biUnknown; devid.id0 := 0; devid.id1 := 0; @@ -354,7 +365,4 @@ begin key_matrix_shift[53].key_code := $3F; end; -initialization - boot.mgr.registerBoot('driver.hid.ps2.keyboard', @init, 'PS/2 Keyboard Driver', BOOT_MGR_BARRIER_DEVICE); - end. diff --git a/src/driver/hid/ps2/driver.hid.ps2.mouse.pas b/src/driver/hid/ps2/driver.hid.ps2.mouse.pas index 71bae687..7feaa429 100644 --- a/src/driver/hid/ps2/driver.hid.ps2.mouse.pas +++ b/src/driver/hid/ps2/driver.hid.ps2.mouse.pas @@ -11,15 +11,14 @@ unit driver.hid.ps2.mouse; interface uses - boot.mgr, debug.tracer, driver.hid.mouse, io.syslog, driver.video, - core.util, arch.x86.util, + core.util, arch.platform.cpu, memory.heap, core.strings, - arch.x86.isr.mgr, + arch.platform.interrupts, arch.common.legacyirq, driver.mgr; type @@ -114,6 +113,7 @@ end; procedure main(); var i : integer; + bytesRead: uint32; b : byte; packet : TMousePacket; x, y, f : byte; @@ -124,7 +124,9 @@ begin When it returns true, data is ready — read it directly with inb($60). Do NOT call mouse_read here: it uses mouse_wait_long (100k iterations) and would spin-block the ISR if the next packet byte hasn't arrived yet. } - while mouse_wait(0) do begin + bytesRead := 0; + while (bytesRead < 32) and ((inb($64) and $21) = $21) do begin + inc(bytesRead); b := inb($60); if Cycle = 0 then begin if (b AND $08) = $08 then begin @@ -213,8 +215,10 @@ var status : uint8; devid_byte : uint8; tmp : uint8; + irqFlags: PtrUInt; begin push_trace('driver.hid.ps2.mouse.load'); + irqFlags := arch.platform.cpu.lock; { Disable both PS/2 ports while configuring } mouse_wait_long(1); @@ -290,10 +294,12 @@ begin mouse_write($F4); mouse_read(); - arch.x86.isr.mgr.registerISR(44, @Main); + arch.platform.interrupts.registerIRQ(44, @Main); + arch.common.legacyirq.enableLine(12); io.syslog.logln('PS/2 MOUSE', 'LOADED.'); io.syslog.log('PS/2 MOUSE', 'Memory: '); - io.syslog.writehexln(uint32(@current)); + io.syslog.writehexln(PtrUInt(@current)); + arch.platform.cpu.unlock(irqFlags); load := true; pop_trace; end; @@ -320,7 +326,4 @@ begin pop_trace; end; -initialization - boot.mgr.registerBoot('driver.hid.ps2.mouse', @init, 'PS/2 Mouse Driver', BOOT_MGR_BARRIER_DEVICE); - end. diff --git a/src/driver/hid/usb/driver.hid.usb.keyboard.pas b/src/driver/hid/usb/driver.hid.usb.keyboard.pas index ac7dffdc..de25aee6 100644 --- a/src/driver/hid/usb/driver.hid.usb.keyboard.pas +++ b/src/driver/hid/usb/driver.hid.usb.keyboard.pas @@ -12,18 +12,19 @@ unit driver.hid.usb.keyboard; interface uses - boot.mgr, driver.bus.usb.types, driver.bus.usb.core, driver.hid.keyboard, - driver.hid.ps2.keyboard, driver.mgr, memory.heap, core.ds.lists, debug.tracer, io.syslog, core.strings, - core.util, arch.x86.util; + core.util, arch.platform.cpu, arch.platform.diagnostics; + +{ Target startup may disable a legacy keyboard when USB becomes active. } +var onAttach: procedure = nil; procedure init; procedure poll_keyboards; @@ -400,7 +401,7 @@ begin end; { Save current report as previous for next comparison } - memcpy(uint32(@kb^.ReportBuf[0]), uint32(@kb^.PrevReport[0]), KB_REPORT_SIZE); + memcpy(PtrUInt(@kb^.ReportBuf[0]), PtrUInt(@kb^.PrevReport[0]), KB_REPORT_SIZE); end; { ========================= Disconnect Handler ========================= } @@ -573,7 +574,7 @@ begin io.syslog.writestringln(').'); { Disable PS/2 driver.hid.keyboard to prevent duplicate input from driver.bus.usb legacy emulation } - driver.hid.ps2.keyboard.disable; + if onAttach <> nil then onAttach; load := true; pop_trace; @@ -632,20 +633,8 @@ var end; procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('USBKeyboard', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); + testSummary('USBKeyboard', 'USBKEYBOARD_TESTS_PASSED', 'USBKEYBOARD_TESTS_FAILED', passed, failed); end; var @@ -754,7 +743,4 @@ begin PrintSummary; end; -initialization - boot.mgr.registerBoot('driver.hid.usb.keyboard', @init, 'USB Keyboard Driver', BOOT_MGR_BARRIER_DEVICE); - end. diff --git a/src/driver/hid/usb/driver.hid.usb.mouse.pas b/src/driver/hid/usb/driver.hid.usb.mouse.pas index b887b954..c568cdfe 100644 --- a/src/driver/hid/usb/driver.hid.usb.mouse.pas +++ b/src/driver/hid/usb/driver.hid.usb.mouse.pas @@ -13,7 +13,6 @@ unit driver.hid.usb.mouse; interface uses - boot.mgr, driver.bus.usb.types, driver.bus.usb.core, driver.hid.mouse, @@ -23,7 +22,7 @@ uses debug.tracer, io.syslog, core.strings, - core.util, arch.x86.util; + core.util, arch.platform.cpu, arch.platform.diagnostics; procedure init; procedure poll_mice; @@ -82,7 +81,7 @@ function mouse_delta_x(report : Pointer) : sint32; var raw : uint8; begin - raw := PUint8(uint32(report) + 1)^; + raw := PUint8(PtrUInt(report) + 1)^; if raw < 128 then mouse_delta_x := sint32(raw) else @@ -94,7 +93,7 @@ function mouse_delta_y(report : Pointer) : sint32; var raw : uint8; begin - raw := PUint8(uint32(report) + 2)^; + raw := PUint8(PtrUInt(report) + 2)^; if raw < 128 then mouse_delta_y := sint32(raw) else @@ -108,7 +107,7 @@ var begin mouse_scroll := 0; if reportLen < 4 then exit; - raw := PUint8(uint32(report) + 3)^; + raw := PUint8(PtrUInt(report) + 3)^; if raw < 128 then mouse_scroll := sint32(raw) else @@ -417,20 +416,8 @@ var end; procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('USBMouse', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); + testSummary('USBMouse', 'USBMOUSE_TESTS_PASSED', 'USBMOUSE_TESTS_FAILED', passed, failed); end; var @@ -555,7 +542,4 @@ begin PrintSummary; end; -initialization - boot.mgr.registerBoot('driver.hid.usb.mouse', @Init, 'USB Mouse Driver', BOOT_MGR_BARRIER_DEVICE); - end. diff --git a/src/driver/io/driver.io.serial.pas b/src/driver/io/driver.io.serial.pas index fa67c44e..4bff1fa7 100644 --- a/src/driver/io/driver.io.serial.pas +++ b/src/driver/io/driver.io.serial.pas @@ -22,8 +22,7 @@ unit driver.io.serial; interface uses - boot.mgr, - core.util, arch.x86.util, arch.x86.isr.mgr, core.strings; + arch.platform.cpu, core.strings; const COM1 = $3F8; @@ -45,33 +44,14 @@ implementation procedure soutb(port : uint16; val : uint8); begin - asm - PUSH EAX - PUSH EDX - MOV DX, port - MOV AL, val - OUT DX, AL - POP EDX - POP EAX - end; - io_wait; + arch.platform.cpu.outb(port, val); + arch.platform.cpu.outb($80, 0); end; function sinb(port : uint16) : uint8; -var - tmp : uint8; begin - asm - PUSH EAX - PUSH EDX - MOV DX, port - IN AL, DX - MOV tmp, AL - POP EDX - POP EAX - end; - sinb := tmp; - io_wait; + sinb := arch.platform.cpu.inb(port); + arch.platform.cpu.outb($80, 0); end; procedure IRQ_Hook(); @@ -202,7 +182,4 @@ begin sendHex:= true; end; -initialization - boot.mgr.registerBoot('driver.io.serial', @init, 'Serial Interface', 'immediate'); - -end. \ No newline at end of file +end. diff --git a/src/driver/net/dev/driver.net.dev.e1000.pas b/src/driver/net/dev/driver.net.dev.e1000.pas index dfd47739..5e92c389 100644 --- a/src/driver/net/dev/driver.net.dev.e1000.pas +++ b/src/driver/net/dev/driver.net.dev.e1000.pas @@ -22,22 +22,20 @@ unit driver.net.dev.e1000; interface uses - boot.mgr, debug.tracer, io.syslog, io.stdio, core.strings, - arch.x86.memory.virtual, + arch.platform.memory, memory.heap, driver.mgr, driver.types, - core.util, arch.x86.util, - arch.x86.idt, + core.util, arch.platform.cpu, driver.bus.pci, driver.net, driver.net.types, driver.net.util, - arch.x86.isr.mgr; + arch.platform.interrupts; const INTEL_VEND = $8086; @@ -165,7 +163,7 @@ var card_type : TCardType; bar_type : uint8; io_base : uint16; - mem_base : uint64; + mem_base : PtrUInt; eeprom_exists : boolean; mac : array[0..5] of uint8; rx_descs : array[0..E1000_NUM_RX_DESC-1] of PE1000_rx_desc; @@ -173,7 +171,8 @@ var rx_buffs : array[0..E1000_NUM_RX_DESC-1] of puint8; rx_curr : uint16; tx_curr : uint16; - mem_aloc : boolean = false; + dmaAllocation: void; + dmaBase: PtrUInt; procedure writeCommand(p_address : uint16; p_value : uint32); var @@ -225,24 +224,24 @@ end; function EEPROMRead( address : uint8 ) : uint32; var - data : uint16; - tmp : uint32; + tmp, mask, attempt: uint32; begin - tmp:= 0; + EEPROMRead := $FFFFFFFF; if (eeprom_exists) then begin writeCommand( REG_EEPROM, 1 OR (uint32(address) SHL 8) ); - while (tmp AND (1 SHL 4)) = 0 do begin - tmp:= readCommand(REG_EEPROM); //Might be wrong? - end; + mask := 1 shl 4; end else begin writeCommand( REG_EEPROM, 1 OR (uint32(address) SHL 2) ); - while (tmp AND (1 SHL 1)) = 0 do begin - tmp:= readCommand(REG_EEPROM); //Might be wrong? + mask := 1 shl 1; + end; + for attempt := 0 to 9999 do begin + tmp := readCommand(REG_EEPROM); + if tmp and mask <> 0 then begin + EEPROMRead := tmp shr 16; + exit; end; end; - data:= uint16( (tmp SHR 16) AND ($FFFF) ); - EEPROMRead:= data; end; function readMACAddress() : boolean; @@ -258,19 +257,31 @@ begin res:= true; if (eeprom_exists) then begin temp:= EEPROMRead(0); + if temp = $FFFFFFFF then begin + readMACAddress := false; + exit; + end; mac[0]:= temp AND $FF; mac[1]:= temp SHR 8; temp:= EEPROMRead(1); + if temp = $FFFFFFFF then begin + readMACAddress := false; + exit; + end; mac[2]:= temp AND $FF; mac[3]:= temp SHR 8; temp:= EEPROMRead(2); + if temp = $FFFFFFFF then begin + readMACAddress := false; + exit; + end; mac[4]:= temp AND $FF; mac[5]:= temp SHR 8; end else begin mem_base_mac_8:= puint8(mem_base + $5400); mem_base_mac_32:= puint32(mem_base + $5400); if (mem_base_mac_32[0] <> 0) then begin - for i:=0 to 6 do begin + for i:=0 to 5 do begin mac[i]:= mem_base_mac_8[i]; end; end else begin @@ -289,89 +300,59 @@ begin writeCommand(REG_CTRL, val OR ECTRL_SLU); end; -procedure rxinit(); +{ Each DMA object fits within one aligned 4 KiB page. The heap allocation + remains owned by the loaded driver; physical pages need not be adjacent. } +procedure rxinit; var - ptr : puint8; - outptr : puint8; - descs : PE1000_rx_desc; - i : uint32; - + physical: uint64; + descs: PE1000_rx_desc; + i: uint32; begin - ptr:= puint8(kalloc(sizeof(TE1000_rx_desc) * E1000_NUM_RX_DESC + 16)); - descs:= PE1000_rx_desc(ptr); - for i:=0 to E1000_NUM_RX_DESC do begin - rx_descs[i]:= @descs[i];//PE1000_rx_desc(uint32(descs) + i*16); - rx_buffs[i]:= puint8(kalloc(8192 + 16)); - rx_descs[i]^.address:= uint64(vtop(uint32(rx_buffs[i]))); - rx_descs[i]^.status:= 0; + descs := PE1000_rx_desc(dmaBase); + for i := 0 to E1000_NUM_RX_DESC - 1 do begin + rx_descs[i] := @descs[i]; + rx_buffs[i] := puint8(dmaBase + (2 + i) * 4096); + rx_descs[i]^.address := physicalAddress(PtrUInt(rx_buffs[i])); + rx_descs[i]^.status := 0; end; - - outptr:= puint8(vtop(uint32(ptr)));//puint8(uint32(ptr) - KERNEL_VIRTUAL_BASE); - - io.syslog.log('driver.net.dev.e1000 Driver', 'RX VMem: '); - io.syslog.writehexln(uint32(ptr)); - io.syslog.log('driver.net.dev.e1000 Driver', 'RX Mem: '); - io.syslog.writehexln(uint32(outptr)); - - writeCommand(REG_TXDESCLO, uint32(uint64(outptr) SHR 32)); - writeCommand(REG_TXDESCHI, uint32(uint64(outptr) AND $FFFFFFFF)); - - writeCommand(REG_RXDESCLO, uint32(outptr)); - writeCommand(REG_RXDESCHI, 0); - + physical := physicalAddress(dmaBase); + writeCommand(REG_RXDESCLO, uint32(physical)); + writeCommand(REG_RXDESCHI, uint32(physical shr 32)); writeCommand(REG_RXDESCLEN, E1000_NUM_RX_DESC * sizeof(TE1000_rx_desc)); - writeCommand(REG_RXDESCHEAD, 0); - writeCommand(REG_RXDESCTAIL, E1000_NUM_RX_DESC-1); - rx_curr:= 0; - - writeCommand(REG_RCTRL, RCTL_EN OR RCTL_SBP OR RCTL_UPE OR RCTL_MPE OR RCTL_LBM_NONE OR RTCL_RDMTS_HALF OR RCTL_BAM OR RCTL_SECRC OR RCTL_BSIZE_2048); + writeCommand(REG_RXDESCTAIL, E1000_NUM_RX_DESC - 1); + rx_curr := 0; + writeCommand(REG_RCTRL, RCTL_EN or RCTL_BAM or RCTL_SECRC or RCTL_BSIZE_2048); end; -procedure txinit(); +procedure txinit; var - ptr : puint8; - outptr : puint8; - descs : PE1000_tx_desc; - i : uint32; - + physical: uint64; + descs: PE1000_tx_desc; + i: uint32; begin - ptr:= puint8(kalloc(sizeof(TE1000_tx_desc) * (E1000_NUM_TX_DESC + 16))); - descs:= PE1000_tx_desc(ptr); - for i:=0 to E1000_NUM_TX_DESC do begin - tx_descs[i]:= @descs[i];//PE1000_tx_desc(uint32(descs + i*16)); - tx_descs[i]^.address:= 0; - tx_descs[i]^.cmd:= 0; - tx_descs[i]^.status:= TSTA_DD; + descs := PE1000_tx_desc(dmaBase + 4096); + for i := 0 to E1000_NUM_TX_DESC - 1 do begin + tx_descs[i] := @descs[i]; + tx_descs[i]^.address := physicalAddress(dmaBase + + (2 + E1000_NUM_RX_DESC + i) * 4096); + tx_descs[i]^.status := TSTA_DD; end; - - outptr:= puint8(vtop(uint32(ptr))); //puint8(uint32(ptr) - KERNEL_VIRTUAL_BASE); - - io.syslog.writehexln(uint32(ptr)); - io.syslog.writehexln(uint32(outptr)); - - writeCommand(REG_TXDESCHI, 0); - writeCommand(REG_TXDESCLO, uint32(outptr)); - + physical := physicalAddress(PtrUInt(descs)); + writeCommand(REG_TXDESCLO, uint32(physical)); + writeCommand(REG_TXDESCHI, uint32(physical shr 32)); writeCommand(REG_TXDESCLEN, E1000_NUM_TX_DESC * sizeof(TE1000_tx_desc)); - - writeCommand( REG_TXDESCHEAD, 0 ); - writeCommand( REG_TXDESCTAIL, 0 ); - tx_curr:= 0; - writeCommand(REG_TCTRL, TCTL_EN OR TCTL_PSP OR (15 SHL TCTL_CT_SHIFT) OR (64 SHL TCTL_COLD_SHIFT) OR TCTL_RTLC); - - //The following is needed for I217 & 82577LM - if (card_type = ct82577LM) OR (card_type = ctI217) then begin - writeCommand(REG_TCTRL, $3003F0FA); - writeCommand(REG_TIPG, $0060200A); - end; + writeCommand(REG_TXDESCHEAD, 0); + writeCommand(REG_TXDESCTAIL, 0); + tx_curr := 0; + writeCommand(REG_TCTRL, TCTL_EN or TCTL_PSP or + (15 shl TCTL_CT_SHIFT) or (64 shl TCTL_COLD_SHIFT) or TCTL_RTLC); + writeCommand(REG_TIPG, $0060200A); end; - procedure enableInturrupt(); begin - writeCommand(REG_IMASK, $1F6DC); - writeCommand(REG_IMASK, $FF AND NOT(4)); readCommand($C0); + writeCommand(REG_IMASK, $84); end; procedure handleReceive(); @@ -383,13 +364,18 @@ var i : uint16; begin - while (rx_descs[rx_curr]^.status AND $1) > 0 do begin + i := 0; + while ((rx_descs[rx_curr]^.status and $1) <> 0) and + (i < E1000_NUM_RX_DESC) do begin + inc(i); got_packet:= true; buf:= rx_buffs[rx_curr]; len:= rx_descs[rx_curr]^.length; //Inject Packet into Network Stack - driver.net.recv(void(buf), len); + if ((rx_descs[rx_curr]^.status and $2) <> 0) and + (rx_descs[rx_curr]^.errors = 0) and (len >= 14) and (len <= 2048) then + driver.net.recv(void(buf), len); rx_descs[rx_curr]^.status:= 0; old_cur:= rx_curr; @@ -425,8 +411,7 @@ begin if (Status AND $80) > 0 then begin handleReceive(); end; - //Clear the INT on the Device First by using write-1 - writeCommand(REG_IMASK, 1); + { Reading ICR above acknowledges the device interrupt. } end; procedure console_command_mac(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); @@ -512,6 +497,7 @@ var begin push_trace('driver.net.dev.e1000.load'); + load := false; io.syslog.logln('driver.net.dev.e1000 Driver', 'Load Start.'); @@ -519,12 +505,13 @@ begin PCI_Info:= PPCI_Device(ptr); bar_type:= PCI_Info^.address0 AND $00000001; - io_base:= PCI_Info^.address0 AND $FFFFFFF0; - mem_base:= PCI_INFO^.address0 AND $FFFFFFFC; - - { !!!!! Dirty way to alloc the pages, needs fixing !!!!! } - kpalloc(io_base); - kpalloc(mem_base); + io_base := PCI_Info^.address0 and $FFFFFFFC; + if bar_type = 0 then begin + mem_base := PtrUInt(mapDevice(PCI_Info^.address0 and $FFFFFFF0, $20000)); + if mem_base = 0 then exit; + end; + if PCI_Info^.interrupt_line >= 16 then exit; + writeCommand($D8, $FFFFFFFF); bus:= PCI_Info^.bus; slot:= PCI_Info^.slot; @@ -548,18 +535,20 @@ begin startLink(); - for i:=0 to $80 do begin + for i:=0 to $7F do begin writeCommand($5200 + i*4, 0); end; - driver.net.registerNetworkCard(@sendPacket, getMACAddress()); - - arch.x86.isr.mgr.registerISR(32 + PCI_Info^.interrupt_line, @fire); - enableInturrupt(); - + dmaAllocation := kalloc((2 + E1000_NUM_RX_DESC + E1000_NUM_TX_DESC) * 4096 + 4095); + if dmaAllocation = nil then exit; + dmaBase := (PtrUInt(dmaAllocation) + 4095) and not PtrUInt(4095); rxinit(); txinit(); - + loaded := true; + driver.net.registerNetworkCard(@sendPacket, getMACAddress()); + arch.platform.interrupts.registerIRQ(32 + PCI_Info^.interrupt_line, @fire); + arch.platform.interrupts.routePCI(PCI_Info^.interrupt_line, 32 + PCI_Info^.interrupt_line); + enableInturrupt(); load:= true; if load then registercommand('E1000', @terminal_command_e1000status, 'driver.net.dev.e1000 Information.'); @@ -627,26 +616,22 @@ end; function sendPacket(p_data : void; p_len : uint16) : sint32; var - old_cur : uint8; - timeout : uint32; - + flags, buffer: PtrUInt; begin - tx_descs[tx_curr]^.address:= uint32(vtop(uint32(p_data))); - tx_descs[tx_curr]^.length:= p_len; - tx_descs[tx_curr]^.cmd:= CMD_EOP OR CMD_IFCS OR CMD_RS OR CMD_RPS; - tx_descs[tx_curr]^.status:= 0; - old_cur:= tx_curr; - tx_curr:= (tx_curr + 1) MOD E1000_NUM_TX_DESC; - writeCommand(REG_TXDESCTAIL, tx_curr); - timeout:= 1000000; - while ((tx_descs[old_cur]^.status AND $FF) = 0) and (timeout > 0) do begin - dec(timeout); + sendPacket := 1; + if (not loaded) or (p_data = nil) or (p_len = 0) or (p_len > 2048) then exit; + flags := arch.platform.cpu.lock; + if (tx_descs[tx_curr]^.status and TSTA_DD) <> 0 then begin + buffer := dmaBase + (2 + E1000_NUM_RX_DESC + tx_curr) * 4096; + memcpy(PtrUInt(p_data), buffer, p_len); + tx_descs[tx_curr]^.length := p_len; + tx_descs[tx_curr]^.cmd := CMD_EOP or CMD_IFCS or CMD_RS; + tx_descs[tx_curr]^.status := 0; + tx_curr := (tx_curr + 1) mod E1000_NUM_TX_DESC; + writeCommand(REG_TXDESCTAIL, tx_curr); + sendPacket := 0; end; - sendPacket:= 1; - if timeout > 0 then sendPacket:= 0; + arch.platform.cpu.unlock(flags); end; -Initialization - //boot.mgr.registerBoot('driver.net.dev.e1000', @Init, 'E1000 Network Card Driver', BOOT_MGR_BARRIER_DEVICE) - end. diff --git a/src/driver/net/driver.net.pas b/src/driver/net/driver.net.pas index 426856fb..bb65f6c6 100644 --- a/src/driver/net/driver.net.pas +++ b/src/driver/net/driver.net.pas @@ -22,7 +22,6 @@ unit driver.net; interface uses - boot.mgr, debug.tracer, driver.net.types, driver.net.util, io.syslog, @@ -40,7 +39,7 @@ procedure writeToLogLn(str : pchar); implementation uses - driver.net.dev.e1000, //dev + io.stdio, driver.net.dev.e1000, //dev driver.net.proto.eth2, //L2 driver.net.proto.arp, driver.net.proto.ipv4, //L3 driver.net.proto.icmp, driver.net.proto.tcp, driver.net.proto.udp, //L4 @@ -141,10 +140,14 @@ begin driver.net.proto.udp.register; //l5 driver.net.proto.dhcp.register; + driver.net.dev.e1000.init; + io.stdio.registerCommand('ARP', @driver.net.proto.arp.terminal_command_arp, 'Get ARP Table.'); + io.stdio.registerCommand('IFCONFIG', @driver.net.proto.ipv4.terminal_command_ifconfig, 'Configure Network Settings.'); + io.stdio.registerCommand('TCPCONNECT', @driver.net.proto.tcp.terminal_command_tcpconnect, 'Connect to a TCP host and send Hello World.'); + io.stdio.registerCommand('TCPLISTEN', @driver.net.proto.tcp.terminal_command_tcplisten, 'Listen on a TCP port and log received data.'); + io.stdio.registerCommand('TCPHTTP', @driver.net.proto.tcp.terminal_command_tcphttp, 'Send HTTP GET to a host IP (port 80 default).'); pop_trace; end; -initialization - boot.mgr.registerBoot('driver.net', @Init, 'Net Driver', BOOT_MGR_BARRIER_LATE) -end. \ No newline at end of file +end. diff --git a/src/driver/net/driver.net.util.pas b/src/driver/net/driver.net.util.pas index e391e51f..1bd54656 100644 --- a/src/driver/net/driver.net.util.pas +++ b/src/driver/net/driver.net.util.pas @@ -22,7 +22,7 @@ unit driver.net.util; interface uses - debug.tracer, core.util, arch.x86.util, driver.net.types, io.stdio, memory.heap, core.ds.lists, core.strings; + debug.tracer, core.util, arch.platform.cpu, driver.net.types, io.stdio, memory.heap, core.ds.lists, core.strings; procedure copyMAC(src : puint8; dst : puint8); procedure copyIPv4(src : puint8; dst : puint8); @@ -41,9 +41,21 @@ function verifyChecksum(p_data : puint16; p_len : uint16) : boolean; function sameSubnetIPv4(ip1, ip2, netmask : puint8) : boolean; procedure contextMACSwitch(p_context : PPacketContext); procedure contextIPv4Switch(p_context : PPacketContext); +function networkTicks: uint32; implementation +uses arch.platform.interrupts, arch.common.legacyirq; + +function networkTicks: uint32; +var + ticks: uint64; +begin + ticks := arch.platform.interrupts.timerTicks; + networkTicks := uint32((ticks div PIT_INPUT_HZ) * PIT_DIVISOR * 1024 + + ((ticks mod PIT_INPUT_HZ) * PIT_DIVISOR * 1024) div PIT_INPUT_HZ); +end; + function sameSubnetIPv4(ip1, ip2, netmask : puint8) : boolean; var _ip1, _ip2, _netmask : puint32; @@ -232,7 +244,7 @@ function newPacketContext : PPacketContext; begin push_trace('driver.net.util.newPacketContext'); newPacketContext:= PPacketContext(kalloc(sizeof(TPacketContext))); - memset(uint32(newPacketContext), 0, sizeof(TPacketContext)); + memset(PtrUInt(newPacketContext), 0, sizeof(TPacketContext)); pop_trace; end; @@ -267,4 +279,4 @@ begin pop_trace; end; -end. \ No newline at end of file +end. diff --git a/src/driver/net/proto/driver.net.proto.arp.pas b/src/driver/net/proto/driver.net.proto.arp.pas index 50baaf36..558962ea 100644 --- a/src/driver/net/proto/driver.net.proto.arp.pas +++ b/src/driver/net/proto/driver.net.proto.arp.pas @@ -23,7 +23,7 @@ interface uses debug.tracer, memory.heap, - core.util, arch.x86.util, core.ds.lists, + core.util, arch.platform.cpu, core.ds.lists, driver.net, driver.net.types, driver.net.util, driver.net.proto.eth2, driver.net.proto.ipv4, io.stdio; @@ -212,6 +212,7 @@ var begin push_trace('driver.net.proto.arp.recv'); + if (p_data = nil) or (p_len < sizeof(TARPHeader)) then exit; { Get our converted Header } Header:= PARPHeader(p_data); AHeader.Hardware_Type:= (Header^.Hardware_Type_Hi SHL 8) + Header^.Hardware_Type_Lo; @@ -345,4 +346,4 @@ begin pop_trace; end; -end. \ No newline at end of file +end. diff --git a/src/driver/net/proto/driver.net.proto.dhcp.pas b/src/driver/net/proto/driver.net.proto.dhcp.pas index 4e55f466..45f1c624 100644 --- a/src/driver/net/proto/driver.net.proto.dhcp.pas +++ b/src/driver/net/proto/driver.net.proto.dhcp.pas @@ -24,7 +24,7 @@ interface uses memory.heap, io.syslog, driver.net.types, driver.net.util, driver.net.proto.udp, driver.net, - core.util, arch.x86.util, core.rand, core.ds.lists, debug.tracer, driver.net.proto.ipv4, driver.net.proto.arp; + core.util, arch.platform.cpu, core.rand, core.ds.lists, debug.tracer, driver.net.proto.ipv4, driver.net.proto.arp; type TDHCPOptions = PLinkedListBase; @@ -95,10 +95,10 @@ begin CopyIPv4(@NULL_IP[0], @result^.Relay_Agent_IP[0]); MAC:= getMAC; CopyMAC(MAC, @result^.Client_MAC[0]); - memset(uint32(@result^.Padding[0]), 0, 10); - memset(uint32(@result^.Server_Hostname[0]), 0, 64); - memset(uint32(@result^.Boot_File[0]), 0, 128); - memcpy(uint32(@DHCP_MAGIC[0]), uint32(@result^.Magic_Cookie[0]), 4); + memset(PtrUInt(@result^.Padding[0]), 0, 10); + memset(PtrUInt(@result^.Server_Hostname[0]), 0, 64); + memset(PtrUInt(@result^.Boot_File[0]), 0, 128); + memcpy(PtrUInt(@DHCP_MAGIC[0]), PtrUInt(@result^.Magic_Cookie[0]), 4); createHeader:= result; end; @@ -115,7 +115,7 @@ begin Option^.Reverse_Endian:= SwapEndian; if Length > 0 then begin Option^.Value:= kalloc(Length); - memcpy(uint32(Data), uint32(Option^.Value), Length); + memcpy(PtrUInt(Data), PtrUInt(Option^.Value), Length); end else begin Option^.Value:= nil; end; @@ -292,7 +292,7 @@ begin { Copy the header } NewHeader:= PDHCPHeader(NewBuffer); - memcpy(uint32(Header), uint32(NewHeader), sizeof(TDHCPHeader)); + memcpy(PtrUInt(Header), PtrUInt(NewHeader), sizeof(TDHCPHeader)); { Write Options } buffer:= puint8(NewBuffer); @@ -322,7 +322,7 @@ begin read32^:= getEndianCorrectValue32(Option); end; else begin - memcpy(uint32(Option^.Value), uint32(buffer), Option^.Size); + memcpy(PtrUInt(Option^.Value), PtrUInt(buffer), Option^.Size); end; end; inc(buffer, Option^.Size); @@ -354,8 +354,8 @@ begin { Get the beginning & end of the Options buffer } HeaderSize:= sizeOf(TDHCPHeader); - bufferEnd:= puint8(uint32(p_data) + p_len); - bufferStart:= puint8(uint32(p_data) + headerSize); + bufferEnd:= puint8(PtrUInt(p_data) + p_len); + bufferStart:= puint8(PtrUInt(p_data) + headerSize); { Buffer is our iterator } buffer:= bufferStart; @@ -363,13 +363,14 @@ begin HaveOp:= false; HaveLen:= false; - while (uint32(buffer) < uint32(bufferEnd)) do begin + while (PtrUInt(buffer) < PtrUInt(bufferEnd)) do begin { If we have a length, read the value (bytes[length]) If we have an Opcode read the length (bytes[1]) If we have neither, read the opcode (bytes[1]) } if HaveLen then begin + if Length > PtrUInt(bufferEnd) - PtrUInt(buffer) then exit; { Get a new option LinkedList 'object' and store everything inside } Option:= newOption(DHCPOptions, Opcode, void(buffer), Length, not FlipExclude^[ord(Opcode)]); if Length = 2 then begin @@ -445,7 +446,7 @@ begin else begin cfgopt:= kalloc(Option^.Size); Configuration^.Options[ord(Option^.Opcode)]:= cfgopt; - memcpy(uint32(Option^.Value), uint32(cfgopt), Option^.Size); + memcpy(PtrUInt(Option^.Value), PtrUInt(cfgopt), Option^.Size); end; end; end; @@ -566,6 +567,7 @@ var begin debug.tracer.push_trace('driver.net.proto.dhcp.processPacket.enter'); + if (p_data = nil) or (p_len < sizeof(TDHCPHeader)) then exit; writeToLogLn(' L5/DHCP: processPacket'); io.syslog.logln('DHCP','processPacket'); @@ -795,4 +797,4 @@ begin io.syslog.logln('DHCP', 'Register end.'); end; -end. \ No newline at end of file +end. diff --git a/src/driver/net/proto/driver.net.proto.eth2.pas b/src/driver/net/proto/driver.net.proto.eth2.pas index ef779f22..0009207a 100644 --- a/src/driver/net/proto/driver.net.proto.eth2.pas +++ b/src/driver/net/proto/driver.net.proto.eth2.pas @@ -22,7 +22,7 @@ unit driver.net.proto.eth2; interface uses - memory.heap, core.util, arch.x86.util, + memory.heap, core.util, arch.platform.cpu, debug.tracer, driver.net.types, driver.net.util, driver.net, @@ -79,9 +79,9 @@ begin copyMAC(@p_context^.MAC.Destination[0], @hdr.dst[0]); hdr.EthTypeHi:= eth_type SHR 8; hdr.EthTypeLo:= eth_type AND $FF; - memcpy(uint32(@hdr), uint32(buffer), sizeof(TEthernetHeader)); - memcpy(uint32(p_data), uint32(buffer)+sizeof(TEthernetHeader), p_len); - //FCS:= puint32((uint32(buffer) + size) - 4); + memcpy(PtrUInt(@hdr), PtrUInt(buffer), sizeof(TEthernetHeader)); + memcpy(PtrUInt(p_data), PtrUInt(buffer)+sizeof(TEthernetHeader), p_len); + //FCS:= puint32((PtrUInt(buffer) + size) - 4); //FCS^:= crc32(puint8(buffer), size - 4); driver.net.send(buffer, size); kfree(buffer); @@ -96,6 +96,7 @@ var begin push_trace('driver.net.proto.eth2.recv'); + if (p_data = nil) or (p_len < sizeof(TEthernetHeader)) then exit; buf:= puint8(p_data); Header:= PEthernetHeader(buf); @@ -134,4 +135,4 @@ begin pop_trace; end; -end. \ No newline at end of file +end. diff --git a/src/driver/net/proto/driver.net.proto.icmp.pas b/src/driver/net/proto/driver.net.proto.icmp.pas index 76929881..91f3898c 100644 --- a/src/driver/net/proto/driver.net.proto.icmp.pas +++ b/src/driver/net/proto/driver.net.proto.icmp.pas @@ -22,9 +22,8 @@ unit driver.net.proto.icmp; interface uses - arch.x86.bda, memory.heap, debug.tracer, - driver.net, driver.net.types, driver.net.util, driver.net.proto.ipv4, driver.net.proto.arp, core.util, arch.x86.util; + driver.net, driver.net.types, driver.net.util, driver.net.proto.ipv4, driver.net.proto.arp, core.util, arch.platform.cpu; type TARPErrorCode = (aecFailedToResolveHost, aecNoRouteToHost, aecTimeout, aecTTLExpired); @@ -38,6 +37,7 @@ type end; procedure register; +procedure cancelRequests(userData: void); procedure sendICMPRequest(ip : puint8; Sequence : uint16; TTL : uint8; OnRep : TARPReplyCallback; OnErr : TARPErrorCallback; userData : void); implementation @@ -45,6 +45,23 @@ implementation var Handlers : Array[0..255] of TARPHandler; +procedure cancelRequests(userData: void); +var + i: uint32; + flags: PtrUInt; +begin + flags := arch.platform.cpu.lock; + for i := 1 to 255 do begin + if Handlers[i].UserData = userData then begin + Handlers[i].Active := false; + Handlers[i].UserData := nil; + Handlers[i].OnReply := nil; + Handlers[i].OnError := nil; + end; + end; + arch.platform.cpu.unlock(flags); +end; + function nextInactiveHandler : uint8; var i : uint8; @@ -74,15 +91,23 @@ var Buffer : void; CHK : uint16; Size : uint32; + flags : PtrUInt; begin push_trace('driver.net.proto.icmp.sendICMPRequest'); writeToLogLn(' L4/ICMP: sendICMPRequest'); + flags := arch.platform.cpu.lock; handle:= nextInactiveHandler; + if handle = 0 then begin + arch.platform.cpu.unlock(flags); + if OnErr <> nil then OnErr(nil, aecTimeout, userData); + exit; + end; Handlers[handle].Active:= true; Handlers[handle].OnReply:= OnRep; Handlers[handle].OnError:= OnErr; Handlers[handle].UserData:= userData; + arch.platform.cpu.unlock(flags); if SameSubnetIPv4(ip, @getIPv4Config^.Address[0], @getIPv4Config^.Netmask[0]) then begin dest_mac:= driver.net.proto.arp.resolveIP(ip); end else begin @@ -109,7 +134,7 @@ begin Header^.Identifier:= handle; Header^.Sequence:= Sequence; - memcpy(uint32(@ICMP_DATA_GENERIC[0]), uint32(Buffer) + sizeof(TICMPHeader), sizeof(ICMP_DATA_GENERIC)); + memcpy(PtrUInt(@ICMP_DATA_GENERIC[0]), PtrUInt(Buffer) + sizeof(TICMPHeader), sizeof(ICMP_DATA_GENERIC)); CHK:= calculateChecksum(puint16(Buffer), Size); @@ -117,6 +142,7 @@ begin Header^.ICMP_CHK_Lo:= CHK SHR 8; driver.net.proto.ipv4.send(Buffer, size, context); + kfree(Buffer); freePacketContext(context); end; @@ -135,6 +161,7 @@ var begin push_trace('driver.net.proto.icmp.recv'); + if (p_data = nil) or (p_len < sizeof(TICMPHeader)) then exit; writeToLogLn(' L4/ICMP: recv'); Header:= PICMPHeader(p_data); //writehexlnWND(Header^.ICMP_Type, getTerminalHWND); @@ -153,6 +180,7 @@ begin driver.net.proto.ipv4.send(p_data, p_len, p_context); end; $00:begin //Reply + if Header^.Identifier > 255 then exit; Handle:= Header^.Identifier; if (Handle > 0) and (Handle < 256) then begin If Handlers[Handle].Active then begin @@ -183,4 +211,4 @@ begin driver.net.proto.ipv4.registerProtocol($01, @recv); end; -end. \ No newline at end of file +end. diff --git a/src/driver/net/proto/driver.net.proto.ipv4.pas b/src/driver/net/proto/driver.net.proto.ipv4.pas index cf4dfff9..ec2c8a95 100644 --- a/src/driver/net/proto/driver.net.proto.ipv4.pas +++ b/src/driver/net/proto/driver.net.proto.ipv4.pas @@ -23,7 +23,7 @@ interface uses debug.tracer, memory.heap, - core.util, arch.x86.util, core.strings, + core.util, arch.platform.cpu, core.strings, driver.net, driver.net.types, driver.net.util, core.ds.lists, driver.net.proto.eth2, io.stdio; @@ -84,8 +84,8 @@ begin Header.HDR_CHK_Hi:= CHK AND $FF;//CHK SHR 8; Header.HDR_CHK_Lo:= CHK SHR 8;//CHK AND $FF; Buffer:= kalloc(Len); - memcpy(uint32(@Header), uint32(Buffer), Header.header_len * 4); - memcpy(uint32(p_data), uint32(Buffer) + (Header.header_len * 4), p_len); + memcpy(PtrUInt(@Header), PtrUInt(Buffer), Header.header_len * 4); + memcpy(PtrUInt(p_data), PtrUInt(Buffer) + (Header.header_len * 4), p_len); driver.net.proto.eth2.send(Buffer, (Header.header_len * 4) + p_len, $0800, p_context); kfree(Buffer); pop_trace; @@ -101,11 +101,15 @@ var begin push_trace('driver.net.proto.ipv4.recv'); + if (p_data = nil) or (p_len < 20) then exit; Header:= PIPV4Header(p_data); AHeader.version:= Header^.version; AHeader.header_len:= Header^.header_len; AHeader.ToS:= Header^.ToS; AHeader.total_len:= (Header^.total_len_Hi SHL 8) + Header^.total_len_Lo; + if (AHeader.version <> 4) or (AHeader.header_len < 5) or + (AHeader.total_len < AHeader.header_len * 4) or + (AHeader.total_len > p_len) then exit; AHeader.identifier:= (Header^.identifier_Hi SHL 8) + Header^.identifier_Lo; AHeader.Flags.RS:= false; AHeader.Flags.DF:= (Header^.Flags AND $2) > 0; @@ -122,7 +126,7 @@ begin buf:= puint8(p_data); buf:= buf + (AHeader.header_len * 4); - len:= p_len - (AHeader.header_len * 4); + len:= AHeader.total_len - (AHeader.header_len * 4); copyIPv4(@AHeader.Src[0], @p_context^.IP.Source[0]); copyIPv4(@AHeader.Dst[0], @p_context^.IP.Destination[0]); @@ -225,4 +229,4 @@ begin pop_trace; end; -end. \ No newline at end of file +end. diff --git a/src/driver/net/proto/driver.net.proto.tcp.pas b/src/driver/net/proto/driver.net.proto.tcp.pas index d64e5448..759bde32 100644 --- a/src/driver/net/proto/driver.net.proto.tcp.pas +++ b/src/driver/net/proto/driver.net.proto.tcp.pas @@ -40,8 +40,8 @@ procedure terminal_command_tcphttp(params : PParamList; stdin_buf, stdout_buf, s implementation uses - memory.heap, core.util, arch.x86.util, io.syslog, core.rand, core.ds.lists, - driver.net, arch.x86.isr.tmr0, arch.x86.bda, + memory.heap, core.util, arch.platform.cpu, io.syslog, core.rand, core.ds.lists, + driver.net, arch.platform.interrupts, arch.common.legacyirq, core.strings, driver.net.proto.arp, proc.mgr, proc.types; @@ -79,6 +79,10 @@ const var Connections : PDList; Registered : Boolean = false; + TimerFraction: uint32; + +type + PPtrUInt = ^PtrUInt; { ============================================================================ } { Utility: TCP Flag Helpers } @@ -120,16 +124,16 @@ begin pseudoSize := pseudoSize + 1; pseudoBuf := kalloc(pseudoSize); - memset(uint32(pseudoBuf), 0, pseudoSize); + memset(PtrUInt(pseudoBuf), 0, pseudoSize); pseudoHdr := PTCPPseudoHeader(pseudoBuf); - memcpy(uint32(srcIP), uint32(@pseudoHdr^.Source_IP), 4); - memcpy(uint32(dstIP), uint32(@pseudoHdr^.Destination_IP), 4); + memcpy(PtrUInt(srcIP), PtrUInt(@pseudoHdr^.Source_IP), 4); + memcpy(PtrUInt(dstIP), PtrUInt(@pseudoHdr^.Destination_IP), 4); pseudoHdr^.Reserved := 0; pseudoHdr^.Protocol := TCP_PROTOCOL_ID; pseudoHdr^.TCP_Length := switchendian16(tcpLen); - memcpy(uint32(tcpData), uint32(pseudoBuf) + sizeof(TTCPPseudoHeader), tcpLen); + memcpy(PtrUInt(tcpData), PtrUInt(pseudoBuf) + sizeof(TTCPPseudoHeader), tcpLen); { Compute one's complement sum } sum := 0; @@ -160,7 +164,7 @@ var begin push_trace('driver.net.proto.tcp.CreateTCB'); tcb := PTCB(kalloc(sizeof(TTCB))); - memset(uint32(tcb), 0, sizeof(TTCB)); + memset(PtrUInt(tcb), 0, sizeof(TTCB)); tcb^.State := tssClosed; tcb^.RCV_WND := TCP_DEFAULT_WINDOW; tcb^.SendBufSize := TCP_DEFAULT_WINDOW; @@ -299,7 +303,7 @@ begin for i := 0 to DL_Size(Connections) - 1 do begin p := DL_Get(Connections, i); if p <> nil then begin - tcb := PTCB(puint32(p)^); + tcb := PTCB(PPtrUInt(p)^); if tcb <> nil then begin if tcb^.Active then begin if (tcb^.LocalPort = localPort) and @@ -326,7 +330,7 @@ begin for i := 0 to DL_Size(Connections) - 1 do begin p := DL_Get(Connections, i); if p <> nil then begin - tcb := PTCB(puint32(p)^); + tcb := PTCB(PPtrUInt(p)^); if tcb <> nil then begin if tcb^.Active and (tcb^.State = tssListen) and (tcb^.LocalPort = localPort) then begin FindListenTCB := tcb; @@ -339,13 +343,13 @@ end; procedure AddTCB(tcb : PTCB); var - p : puint32; + p : PPtrUInt; begin push_trace('driver.net.proto.tcp.AddTCB'); if Connections = nil then - Connections := DL_New(sizeof(uint32)); - p := puint32(DL_Add(Connections)); - p^ := uint32(tcb); + Connections := DL_New(sizeof(PtrUInt)); + p := PPtrUInt(DL_Add(Connections)); + p^ := PtrUInt(tcb); end; procedure RemoveTCB(tcb : PTCB); @@ -359,7 +363,7 @@ begin for i := 0 to DL_Size(Connections) - 1 do begin p := DL_Get(Connections, i); if p <> nil then begin - cur := PTCB(puint32(p)^); + cur := PTCB(PPtrUInt(p)^); if cur = tcb then begin DL_Delete(Connections, i); exit; @@ -406,7 +410,7 @@ begin for i := 0 to DL_Size(Connections) - 1 do begin p := DL_Get(Connections, i); if p <> nil then begin - tcb := PTCB(puint32(p)^); + tcb := PTCB(PPtrUInt(p)^); if (tcb <> nil) and tcb^.Active and (tcb^.LocalPort = localPort) and (tcb^.State = tssSynReceived) then @@ -429,7 +433,7 @@ begin for i := 0 to DL_Size(Connections) - 1 do begin p := DL_Get(Connections, i); if p <> nil then begin - tcb := PTCB(puint32(p)^); + tcb := PTCB(PPtrUInt(p)^); if (tcb <> nil) and tcb^.Active and (tcb^.LocalPort = localPort) and (tcb^.State = tssEstablished) and @@ -522,7 +526,7 @@ begin if not tcb^.OOOSegments[i].Valid then begin tcb^.OOOSegments[i].SeqNum := seqNum; tcb^.OOOSegments[i].Data := kalloc(payloadLen); - memcpy(uint32(payload), uint32(tcb^.OOOSegments[i].Data), payloadLen); + memcpy(PtrUInt(payload), PtrUInt(tcb^.OOOSegments[i].Data), payloadLen); tcb^.OOOSegments[i].DataLen := payloadLen; tcb^.OOOSegments[i].Valid := true; exit; @@ -541,7 +545,7 @@ begin kfree(tcb^.OOOSegments[oldIdx].Data); tcb^.OOOSegments[oldIdx].SeqNum := seqNum; tcb^.OOOSegments[oldIdx].Data := kalloc(payloadLen); - memcpy(uint32(payload), uint32(tcb^.OOOSegments[oldIdx].Data), payloadLen); + memcpy(PtrUInt(payload), PtrUInt(tcb^.OOOSegments[oldIdx].Data), payloadLen); tcb^.OOOSegments[oldIdx].DataLen := payloadLen; tcb^.OOOSegments[oldIdx].Valid := true; end; @@ -566,8 +570,8 @@ begin if copyLen > (tcb^.RecvBufSize - tcb^.RecvBufLen) then copyLen := tcb^.RecvBufSize - tcb^.RecvBufLen; if copyLen > 0 then begin - memcpy(uint32(tcb^.OOOSegments[i].Data), - uint32(tcb^.RecvBuf) + tcb^.RecvBufLen, copyLen); + memcpy(PtrUInt(tcb^.OOOSegments[i].Data), + PtrUInt(tcb^.RecvBuf) + tcb^.RecvBufLen, copyLen); tcb^.RecvBufLen := tcb^.RecvBufLen + copyLen; tcb^.RCV_NXT := tcb^.RCV_NXT + copyLen; tcb^.RCV_WND := tcb^.RecvBufSize - tcb^.RecvBufLen; @@ -611,7 +615,7 @@ begin totalLen := hdrLen + payloadLen; buffer := kalloc(totalLen); - memset(uint32(buffer), 0, totalLen); + memset(PtrUInt(buffer), 0, totalLen); hdr := PTCPHeader(buffer); hdr^.SrcPort := switchendian16(tcb^.LocalPort); @@ -623,7 +627,7 @@ begin { Data offset = 6 (24 bytes), includes MSS option } hdr^.DataOff_Flags := switchendian16(MakeDataOffFlags(6, flags)); { Write MSS option after 20-byte header: Kind=2, Len=4, MSS } - opts := puint8(uint32(buffer) + sizeof(TTCPHeader)); + opts := puint8(PtrUInt(buffer) + sizeof(TTCPHeader)); opts[0] := TCP_MSS_OPT_KIND; opts[1] := TCP_MSS_OPT_LEN; opts[2] := uint8(TCP_DEFAULT_MSS SHR 8); @@ -637,7 +641,7 @@ begin hdr^.UrgentPtr := 0; if (payload <> nil) and (payloadLen > 0) then - memcpy(uint32(payload), uint32(buffer) + hdrLen, payloadLen); + memcpy(PtrUInt(payload), PtrUInt(buffer) + hdrLen, payloadLen); { Calculate checksum } chk := CalculateChecksum(buffer, totalLen, @tcb^.LocalIP[0], @tcb^.RemoteIP[0]); @@ -666,7 +670,7 @@ begin tcb^.RTTMeasuring := true; tcb^.RTTSeqNum := tcb^.SND_NXT + payloadLen; if isSYN then tcb^.RTTSeqNum := tcb^.SND_NXT + 1; - tcb^.RTTStartTime := Counters.c32; + tcb^.RTTStartTime := networkTicks; end; { Reset keep-alive timer on any send } @@ -681,11 +685,11 @@ begin if ((flags AND (TCP_FLAG_SYN OR TCP_FLAG_FIN)) <> 0) or (payloadLen > 0) then begin if tcb^.RetransBuf <> nil then kfree(tcb^.RetransBuf); tcb^.RetransBuf := kalloc(totalLen); - memcpy(uint32(buffer), uint32(tcb^.RetransBuf), totalLen); + memcpy(PtrUInt(buffer), PtrUInt(tcb^.RetransBuf), totalLen); tcb^.RetransBufLen := totalLen; tcb^.RetransTimer := tcb^.RTO; { Use adaptive RTO } tcb^.RetransCount := 0; - tcb^.LastSendTime := Counters.c32; + tcb^.LastSendTime := networkTicks; end; freePacketContext(context); @@ -701,7 +705,7 @@ var begin push_trace('driver.net.proto.tcp.SendRST'); buffer := kalloc(sizeof(TTCPHeader)); - memset(uint32(buffer), 0, sizeof(TTCPHeader)); + memset(PtrUInt(buffer), 0, sizeof(TTCPHeader)); hdr.SrcPort := switchendian16(localPort); hdr.DstPort := switchendian16(remotePort); @@ -712,7 +716,7 @@ begin hdr.Checksum := 0; hdr.UrgentPtr := 0; - memcpy(uint32(@hdr), uint32(buffer), sizeof(TTCPHeader)); + memcpy(PtrUInt(@hdr), PtrUInt(buffer), sizeof(TTCPHeader)); chk := CalculateChecksum(buffer, sizeof(TTCPHeader), localIP, remoteIP); PTCPHeader(buffer)^.Checksum := chk; @@ -760,7 +764,7 @@ begin { Parse options pointer } if dataOff > 5 then begin - optsPtr := puint8(uint32(hdr) + sizeof(TTCPHeader)); + optsPtr := puint8(PtrUInt(hdr) + sizeof(TTCPHeader)); optsLen := (dataOff - 5) * 4; end else begin optsPtr := nil; @@ -797,7 +801,7 @@ begin { RTT measurement complete } if tcb^.RTTMeasuring then begin - rttSample := Counters.c32 - tcb^.RTTStartTime; + rttSample := networkTicks - tcb^.RTTStartTime; if rttSample > 0 then UpdateRTT(tcb, rttSample); tcb^.RTTMeasuring := false; @@ -854,7 +858,7 @@ begin { RTT measurement } if tcb^.RTTMeasuring then begin - rttSample := Counters.c32 - tcb^.RTTStartTime; + rttSample := networkTicks - tcb^.RTTStartTime; if rttSample > 0 then UpdateRTT(tcb, rttSample); tcb^.RTTMeasuring := false; @@ -890,7 +894,7 @@ begin { RTT measurement: complete if ACK covers timed segment } if tcb^.RTTMeasuring and (ackNum >= tcb^.RTTSeqNum) then begin - rttSample := Counters.c32 - tcb^.RTTStartTime; + rttSample := networkTicks - tcb^.RTTStartTime; if rttSample > 0 then UpdateRTT(tcb, rttSample); tcb^.RTTMeasuring := false; @@ -939,8 +943,8 @@ begin tcb^.SND_NXT := tcb^.SND_NXT + copyLen; { Shift remaining data in send buffer } if copyLen < tcb^.SendBufLen then begin - memcpy(uint32(tcb^.SendBuf) + copyLen, - uint32(tcb^.SendBuf), + memcpy(PtrUInt(tcb^.SendBuf) + copyLen, + PtrUInt(tcb^.SendBuf), tcb^.SendBufLen - copyLen); end; tcb^.SendBufLen := tcb^.SendBufLen - copyLen; @@ -963,7 +967,7 @@ begin copyLen := tcb^.RecvBufSize - tcb^.RecvBufLen; if copyLen > 0 then begin - memcpy(uint32(payload), uint32(tcb^.RecvBuf) + tcb^.RecvBufLen, copyLen); + memcpy(PtrUInt(payload), PtrUInt(tcb^.RecvBuf) + tcb^.RecvBufLen, copyLen); tcb^.RecvBufLen := tcb^.RecvBufLen + copyLen; tcb^.RCV_NXT := tcb^.RCV_NXT + copyLen; tcb^.RCV_WND := tcb^.RecvBufSize - tcb^.RecvBufLen; @@ -1164,7 +1168,7 @@ begin { Parse MSS option from SYN } if dataOff > 5 then begin - optsPtr := puint8(uint32(p_data) + sizeof(TTCPHeader)); + optsPtr := puint8(PtrUInt(p_data) + sizeof(TTCPHeader)); optsLen := (dataOff - 5) * 4; mss := ParseMSSOption(optsPtr, optsLen); if mss > 0 then @@ -1217,6 +1221,9 @@ var count : uint32; effMSS : uint16; begin + TimerFraction := TimerFraction + PIT_DIVISOR * 1024; + if TimerFraction < PIT_INPUT_HZ then exit; + TimerFraction := TimerFraction - PIT_INPUT_HZ; if Connections = nil then exit; count := DL_Size(Connections); if count = 0 then exit; @@ -1225,7 +1232,7 @@ begin while i < count do begin p := DL_Get(Connections, i); if p <> nil then begin - tcb := PTCB(puint32(p)^); + tcb := PTCB(PPtrUInt(p)^); if (tcb <> nil) and tcb^.Active then begin { Delayed ACK timer } @@ -1422,7 +1429,7 @@ begin if tcb = nil then exit; copyIPv4(@getIPv4Config^.Address[0], @tcb^.LocalIP[0]); - memset(uint32(@tcb^.RemoteIP[0]), 0, 4); + memset(PtrUInt(@tcb^.RemoteIP[0]), 0, 4); tcb^.LocalPort := context^.LocalPort; tcb^.RemotePort := 0; tcb^.State := tssListen; @@ -1503,7 +1510,7 @@ begin if copyLen > (tcb^.SendBufSize - tcb^.SendBufLen) then copyLen := tcb^.SendBufSize - tcb^.SendBufLen; if copyLen > 0 then begin - memcpy(uint32(p_data), uint32(tcb^.SendBuf) + tcb^.SendBufLen, copyLen); + memcpy(PtrUInt(p_data), PtrUInt(tcb^.SendBuf) + tcb^.SendBufLen, copyLen); tcb^.SendBufLen := tcb^.SendBufLen + copyLen; end; send := tteOK; @@ -1523,7 +1530,7 @@ begin if copyLen > (tcb^.SendBufSize - tcb^.SendBufLen) then copyLen := tcb^.SendBufSize - tcb^.SendBufLen; if copyLen > 0 then begin - memcpy(uint32(p_data), uint32(tcb^.SendBuf) + tcb^.SendBufLen, copyLen); + memcpy(PtrUInt(p_data), PtrUInt(tcb^.SendBuf) + tcb^.SendBufLen, copyLen); tcb^.SendBufLen := tcb^.SendBufLen + copyLen; end; send := tteOK; @@ -1867,7 +1874,7 @@ begin port := uint16(stringToInt(getParam(1, params))); { Store host string for HTTP Host header } - memset(uint32(@TCPHTTP_Host[0]), 0, 64); + memset(PtrUInt(@TCPHTTP_Host[0]), 0, 64); i := 0; while (ip_str[i] <> #0) and (i < 63) do begin TCPHTTP_Host[i] := ip_str[i]; @@ -1900,12 +1907,12 @@ begin push_trace('driver.net.proto.tcp.register'); writeToLogLn(' L4/TCP: register'); if not Registered then begin - Connections := DL_New(sizeof(uint32)); + Connections := DL_New(sizeof(PtrUInt)); driver.net.proto.ipv4.registerProtocol(TCP_PROTOCOL_ID, @ProcessPacket); - arch.x86.isr.tmr0.hook(uint32(@TimerTick)); + arch.platform.interrupts.registerTimer(@TimerTick); Registered := true; io.syslog.logln('TCP', 'TCP registered.'); end; end; -end. \ No newline at end of file +end. diff --git a/src/driver/net/proto/driver.net.proto.udp.pas b/src/driver/net/proto/driver.net.proto.udp.pas index 3f1ab4ed..b3719fd5 100644 --- a/src/driver/net/proto/driver.net.proto.udp.pas +++ b/src/driver/net/proto/driver.net.proto.udp.pas @@ -25,7 +25,7 @@ uses memory.heap, driver.net.types, driver.net.util, driver.net.proto.ipv4, driver.net, - debug.tracer, core.util, arch.x86.util; + debug.tracer, core.util, arch.platform.cpu; procedure register(); function bind(bindContext : PUDPBindContext) : TUDPError; @@ -83,7 +83,7 @@ begin pseudoHeader^.UDP_Source:= pseduoSrc; pseudoHeader^.UDP_Destination:= udpContext^.DstPort; pseudoHeader^.UDP_Length:= size; - memcpy(uint32(p_data), uint32(pseudoBuffer) + SizeOf(TUDPPseudoHeader), p_len); + memcpy(PtrUInt(p_data), PtrUInt(pseudoBuffer) + SizeOf(TUDPPseudoHeader), p_len); if nullend then pseudoBuffer8[pseudoSize-1]:= 0; pseudoHeader^.Source_IP:= switchendian32(pseudoHeader^.Source_IP); @@ -136,8 +136,8 @@ begin hdr^.Checksum:= switchendian16(checksum); hdr^.Length:= switchendian16(size); - memcpy(uint32(hdr), uint32(buffer), sizeof(TUDPHeader)); - memcpy(uint32(p_data), uint32(buffer) + sizeof(TUDPHeader), p_len); + memcpy(PtrUInt(hdr), PtrUInt(buffer), sizeof(TUDPHeader)); + memcpy(PtrUInt(p_data), PtrUInt(buffer) + sizeof(TUDPHeader), p_len); udpContext^.context^.Protocol.L4:= $11; @@ -208,7 +208,10 @@ var begin push_trace('driver.net.proto.udp.ProcessPacket'); + if (p_data = nil) or (p_len < sizeof(TUDPHeader)) then exit; header:= PUDPHeader(p_data); + size := switchendian16(header^.Length); + if (size < sizeof(TUDPHeader)) or (size > p_len) then exit; if Ports[switchendian16(header^.DstPort)] <> nil then begin context:= PUDPPacketContext(kalloc(sizeof(TUDPPacketContext))); context^.PacketContext:= p_context; @@ -272,4 +275,4 @@ begin driver.net.proto.ipv4.registerProtocol($11, @ProcessPacket); end; -end. \ No newline at end of file +end. diff --git a/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas b/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas index fd1fff29..e32c32e6 100644 --- a/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas +++ b/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas @@ -13,9 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -{ +{ Drivers->Storage->driver.storage.ctl.ahci->driver.storage.ctl.ahci - driver.storage.ctl.ahci Driver. - + @author(Aaron Hance ) } unit driver.storage.ctl.ahci; @@ -23,21 +23,19 @@ unit driver.storage.ctl.ahci; interface uses - boot.mgr, driver.storage.ctl.ahci.types, driver.mgr, driver.types, driver.storage.ctl.ide.types, - arch.x86.isr.ioapic, - arch.x86.isr.mgr, + arch.platform.interrupts, core.ds.lists, memory.heap, driver.bus.pci, driver.storage.mgr, driver.storage.types, io.syslog, - core.util, arch.x86.util, - arch.x86.memory.virtual; + core.util, arch.platform.cpu, + arch.platform.memory; var ahciControllers : PDList; @@ -69,7 +67,10 @@ function send_read_capacity(device : PAHCI_Device; sectorCount : puint32; blockS implementation -uses arch.x86.bda, arch.x86.isr.tmr0, core.panic, io.stdio, core.strings; +uses arch.platform.process, arch.platform.diagnostics, io.stdio, core.strings; + +type + PControllerSlot = ^PAHCI_Controller; const AHCI_ENABLE_NCQ = true; @@ -80,6 +81,8 @@ const var watchdogDivider : uint32 = 0; +function build_prdt(table: PHBA_CMD_TABLE; address: PtrUInt; bytes: uint32; var entries: uint32; dma64: boolean): TError; forward; + procedure cmd_ahcitest(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); forward; procedure ahci_watchdog(data : void); forward; procedure fail_controller(controller : PAHCI_Controller; error : TError); forward; @@ -105,7 +108,7 @@ begin devID.id4:= idANY; devID.ex:= nil; driver.mgr.register_driver('ATA/PI driver.storage.ctl.ahci Driver', @devID, @load); - arch.x86.isr.tmr0.hook(uint32(@ahci_watchdog)); + arch.platform.interrupts.registerTimer(@ahci_watchdog); io.stdio.registerCommand('AHCITEST',@cmd_ahcitest,'Run AHCI tests using simulated registers'); end; @@ -118,7 +121,7 @@ begin repeat if (reg^ and mask) = expected then begin wait_register := true; exit; end; if n >= attempts then break; - psleep(16); Inc(n); + core.util.waitTicks(16); Inc(n); until false; wait_register := false; end; @@ -149,7 +152,7 @@ begin port^.int_enable := 0; port^.cmd := port^.cmd or $16; { FRE, power-on and spin-up } port^.sata_ctrl := (port^.sata_ctrl and $FFFFFFF0) or $1; - psleep(16); { >= 1 ms COMRESET assertion at the ~8 kHz PIT rate } + core.util.waitTicks(16); { >= 1 ms COMRESET assertion at the ~8 kHz PIT rate } port^.sata_ctrl := port^.sata_ctrl and $FFFFFFF0; if not wait_register(@port^.sata_status,$F,3,500) then exit; if not wait_register(@port^.tfd,$88,0,2500) then exit; @@ -189,7 +192,9 @@ begin port := @controller^.mio^.ports[i]; device := PAHCI_Device(@controller^.devices[i]); - device^.port := port; device^.owner := controller; + device^.port := port; + device^.owner := controller; + device^.supports64BitDMA := (controller^.mio^.capabilites and $80000000) <> 0; port^.int_enable := 0; if not stop_port(port) then begin log_port_failure(i,port,'cannot stop engine'); continue; end; @@ -202,46 +207,80 @@ begin //allocate memory for the command list and ensure it is aligned to 1024 bytes // Need 32 command headers. Allocate extra (alignment-1) bytes for alignment. listAlloc := kalloc(sizeof(THBA_CMD_HEADER) * 32 + 1023); + if listAlloc = nil then continue; cmd_list_base := listAlloc; - cmd_list_base := puint32((uint32(cmd_list_base) + 1023) and $FFFFFC00); - memset(uint32(cmd_list_base), 0, sizeof(THBA_CMD_HEADER) * 32); + cmd_list_base := puint32((PtrUInt(cmd_list_base) + 1023) and not PtrUInt(1023)); //set the command list base address - port^.cmdl_basel := vtop(uint32(cmd_list_base)); //todo set virtual address in device - port^.cmdl_baseu := 0; + port^.cmdl_basel := arch.platform.memory.physicalAddress(PtrUInt(cmd_list_base)); //todo set virtual address in device + port^.cmdl_baseu := arch.platform.memory.physicalAddress(PtrUInt(cmd_list_base)) shr 32; device^.command_list := PHBA_CMD_HEADER(cmd_list_base); //allocate memory for the FIS and ensure it is aligned to 256 bytes fisAlloc := kalloc(sizeof(THBA_FIS) + 255); + if fisAlloc = nil then begin + port^.cmdl_basel := 0; + port^.cmdl_baseu := 0; + kfree(listAlloc); + continue; + end; fis_base := fisAlloc; - fis_base := puint32((uint32(fis_base) + 255) and $FFFFFF00); + fis_base := puint32((PtrUInt(fis_base) + 255) and not PtrUInt(255)); //set the FIS base address - port^.fis_basel := vtop(uint32(fis_base)); - port^.fis_baseu := 0; + port^.fis_basel := arch.platform.memory.physicalAddress(PtrUInt(fis_base)); + port^.fis_baseu := arch.platform.memory.physicalAddress(PtrUInt(fis_base)) shr 32; - memset(uint32(fis_base), 0, sizeof(THBA_FIS)); device^.fis := PHBA_FIS(fis_base); //todo check how many simultaneous commands are supported - //allocate memory for the command table and ensure it is aligned to 128 bytes - // Need 32 command tables. Allocate extra (alignment-1) bytes for alignment. - tableAlloc := kalloc(sizeof(THBA_CMD_TABLE) * 32 + 127); + // Each command table must fit inside one physical page. + tableAlloc := kalloc(4096 * 32 + 4095); + if tableAlloc = nil then begin + port^.cmdl_basel := 0; + port^.cmdl_baseu := 0; + port^.fis_basel := 0; + port^.fis_baseu := 0; + kfree(listAlloc); + kfree(fisAlloc); + continue; + end; cmd_table_base := tableAlloc; - cmd_table_base := puint32((uint32(cmd_table_base) + 127) and $FFFFFF80); - memset(uint32(cmd_table_base), 0, sizeof(THBA_CMD_TABLE) * 32); + cmd_table_base := puint32((PtrUInt(cmd_table_base) + 4095) and not PtrUInt(4095)); device^.command_table := PHBA_CMD_TABLE(cmd_table_base); //set the command table base address and setup command table for ii:=0 to 31 do begin //set command header locations - command_header := PHBA_CMD_HEADER(uint32(cmd_list_base) + (ii * sizeof(THBA_CMD_HEADER))); + command_header := PHBA_CMD_HEADER(PtrUInt(cmd_list_base) + (ii * sizeof(THBA_CMD_HEADER))); command_header^.prdtl := 32; - command_header^.cmd_table_base := vtop(uint32(cmd_table_base) + (ii * sizeof(THBA_CMD_TABLE))); - command_header^.cmd_table_baseu := 0; + command_header^.cmd_table_base := arch.platform.memory.physicalAddress(PtrUInt(cmd_table_base) + (ii * 4096)); + command_header^.cmd_table_baseu := arch.platform.memory.physicalAddress(PtrUInt(cmd_table_base) + ii * 4096) shr 32; + end; + + if not device^.supports64BitDMA then begin + registered := (port^.cmdl_baseu <> 0) or (port^.fis_baseu <> 0); + for ii := 0 to 31 do begin + command_header := PHBA_CMD_HEADER(PtrUInt(cmd_list_base) + ii * sizeof(THBA_CMD_HEADER)); + if command_header^.cmd_table_baseu <> 0 then registered := true; + end; + if registered then begin + port^.cmdl_basel := 0; + port^.cmdl_baseu := 0; + port^.fis_basel := 0; + port^.fis_baseu := 0; + device^.command_list := nil; + device^.fis := nil; + device^.command_table := nil; + kfree(listAlloc); + kfree(fisAlloc); + kfree(tableAlloc); + io.syslog.logln('AHCI', 'Controller cannot address allocated DMA structures.'); + continue; + end; end; registered := false; @@ -251,7 +290,7 @@ begin signatureWait := 0; while (puint32(@port^.signature)^ = 0) or (puint32(@port^.signature)^ = $FFFFFFFF) do begin if signatureWait >= 500 then break; - psleep(16); Inc(signatureWait); + core.util.waitTicks(16); Inc(signatureWait); end; case puint32(@port^.signature)^ of SATA_SIG_ATA: begin device^.device_type := SATA; registered := identify_device(controller,i,false); end; @@ -293,7 +332,7 @@ var storageDev : PStorage_Device; storageDev_sectorSize : uint32; ncqDepth : uint32; - slotCount : uint32; + slotCount, entries : uint32; ncqSupported : boolean; begin identify_device := false; @@ -306,10 +345,11 @@ begin //clear any pending interrupts device^.port^.int_status := $FFFFFFFF; device^.port^.int_enable := $0; { disabled during IDENTIFY polling } - + // Allocate a 512-byte DMA buffer for the IDENTIFY d driver.storage.ctl.ide.ata buffer := kalloc(512); - memset(uint32(buffer), 0, 512); + if buffer = nil then exit; + memset(PtrUInt(buffer), 0, 512); // Use command slot 0 for the IDENTIFY command. cmd_header := device^.command_list; // Assuming slot 0 is at the beginning. @@ -321,10 +361,12 @@ begin // Setup the command table (using slot 0) - cmd_table := device^.command_table; - cmd_table^.prdt[0].dba := vtop(uint32(buffer)); - cmd_table^.prdt[0].dbc := 511; // 512 bytes (0-based count) - cmd_table^.prdt[0].int := 1; // Interrupt on completion + cmd_table := device^.command_table; + if build_prdt(cmd_table, PtrUInt(buffer), 512, entries, device^.supports64BitDMA) <> eNone then begin + kfree(buffer); + exit; + end; + cmd_header^.prdtl := entries; // if isATAPI then begin // // ATAPI Identify Device command @@ -333,7 +375,7 @@ begin // Construct the Command FIS in the command table's CFIS area fis := PHBA_FIS_REG_H2D(@device^.command_table^.cmd_fis); - memset(uint32(fis), 0, sizeof(THBA_FIS_REG_H2D)); + memset(PtrUInt(fis), 0, sizeof(THBA_FIS_REG_H2D)); fis^.fis_type := uint8(FIS_TYPE_REG_H2D); fis^.c := $1; if isATAPI then begin @@ -341,7 +383,7 @@ begin end else begin fis^.command := ATA_CMD_IDENTIFY; end; - fis^.device := $40; + fis^.device := $40; if not wait_register(@device^.port^.tfd,$88,0,2500) then begin log_port_failure(portIndex,device^.port,'busy before IDENTIFY'); kfree(buffer); exit; @@ -362,7 +404,7 @@ begin device^.port^.int_enable := AHCI_IRQ_MASK; b8 := puint16(buffer); - memcpy(uint32(buffer), uint32(@device^.ata_info[0]), 512); + memcpy(PtrUInt(buffer), PtrUInt(@device^.ata_info[0]), 512); sec_count := (b8[61] shl 16) or b8[60]; ncqDepth := (b8[75] and $1F) + 1; ncqSupported := ((b8[76] and (1 shl 8)) <> 0) and (not isATAPI) and AHCI_ENABLE_NCQ and ((controller^.mio^.capabilites and (1 shl 30)) <> 0); @@ -387,14 +429,14 @@ begin end else begin storageDev_sectorSize := 512; end; - + { Register this device with the storage manager. ATAPI devices (optical drives) are always registered even with 0 sectors (no media). } if (sec_count > 0) or isATAPI then begin storageDev := PStorage_Device(kalloc(sizeof(TStorage_Device))); - memset(uint32(storageDev), 0, sizeof(TStorage_Device)); + memset(PtrUInt(storageDev), 0, sizeof(TStorage_Device)); - storageDev^.controllerId0 := uint32(device); { store PAHCI_Device for callback wrappers } + storageDev^.controllerId0 := PtrUInt(device); { store PAHCI_Device for callback wrappers } storageDev^.sectorSize := storageDev_sectorSize; storageDev^.maxSectorCount := sec_count; if not isATAPI then storageDev^.mergeMaxSectors := 65536; @@ -465,7 +507,9 @@ end; // end; function load(ptr : void) : boolean; -var +var + irqFlags: PtrUInt; + device : PPCI_Device; controller : PAHCI_Controller; i : uint32; @@ -484,7 +528,7 @@ begin device := PPCI_Device(ptr); driver.bus.pci.enableDevice(device^.bus, device^.slot, device^.func); - psleep(5); + core.util.waitTicks(5); int_no := device^.interrupt_line + 32; load := false; @@ -492,13 +536,17 @@ begin if controller = nil then exit; controller^.pci_device := PPCI_Device(device); - + // Perform BIOS/OS handoff (if the bit in the extended capabilities is set) // Reset controller //get the base address of the controller - page_base := kpalloc(device^.address5); // TODO MEM memory manager need to be improved - base := PHBA_Memory(device^.address5); + page_base := arch.platform.memory.mapDevice(device^.address5 and $FFFFFFF0, sizeof(THBA_Memory)); // TODO MEM memory manager need to be improved + base := PHBA_Memory(page_base); + if base = nil then begin + kfree(void(controller)); + exit; + end; controller^.mio := base; if (base^.capabilites2 and 1) <> 0 then begin @@ -515,11 +563,11 @@ begin kfree(void(controller)); exit; end; { Publish a stable heap pointer, never an interior pointer into a growing DList. } - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; DL_Add(ahciControllers); DL_Set(ahciControllers,DL_Size(ahciControllers)-1,puint32(@controller)); - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); - registerISR(int_no, @ahci_isr); + arch.platform.interrupts.registerIRQ(int_no, @ahci_isr); io.syslog.writestring('driver.storage.ctl.ahci: IRQ '); io.syslog.writeint(device^.interrupt_line); @@ -531,8 +579,7 @@ begin driver.bus.pci interrupts may be routed through the IOAPIC instead of the legacy 8259 PIC — without programming the IOAPIC redirection entry the interrupt never reaches the CPU. } - arch.x86.isr.ioapic.init(); - arch.x86.isr.ioapic.route_pci_irq(device^.interrupt_line, int_no); + arch.platform.interrupts.routePCI(device^.interrupt_line, int_no); //enable driver.storage.ctl.ahci mode base^.global_ctrl := base^.global_ctrl or AHCI_CONTROLLER_MODE; @@ -548,7 +595,7 @@ begin end; function now_tick : uint32; -begin now_tick := puint32(@Counters.c32)^; end; +begin now_tick := arch.platform.process.timerTicks; end; { Only complete commands whose DMA ownership has ended. Callback re-entry may submit more work, so consume a fixed mask rather than rescanning new slots. } @@ -579,7 +626,7 @@ begin driver.bus.pci.requestConfig(pci^.bus,pci^.slot,pci^.func,1); command := inl($CFC); if (command <> $FFFFFFFF) and ((command and 4) <> 0) then - core.panic.panic('AHCI DMA cannot be stopped','PCI bus mastering could not be disabled',nil); + arch.platform.diagnostics.fatal('AHCI DMA cannot be stopped','PCI bus mastering could not be disabled'); controller^.mio^.global_ctrl := controller^.mio^.global_ctrl and not uint32(2); { Reject all new work before any callback drains a queued request. } for j := 0 to 31 do controller^.devices[j].offline := true; @@ -648,7 +695,7 @@ var i,j,ctrlIS,portIS : uint32; controller : PAHCI_Controller; device : PAHCI_De begin if (ahciControllers = nil) or (DL_Size(ahciControllers) = 0) then exit; for i := 0 to DL_Size(ahciControllers)-1 do begin - controller := PAHCI_Controller(puint32(DL_Get(ahciControllers,i))^); + controller := PControllerSlot(DL_Get(ahciControllers,i))^; if controller^.offline then continue; ctrlIS := controller^.mio^.int_status; if ctrlIS = 0 then continue; @@ -671,7 +718,7 @@ begin if (ahciControllers = nil) or (DL_Size(ahciControllers) = 0) then exit; now := now_tick; for i := 0 to DL_Size(ahciControllers)-1 do begin - controller := PAHCI_Controller(puint32(DL_Get(ahciControllers,i))^); + controller := PControllerSlot(DL_Get(ahciControllers,i))^; if controller^.offline then continue; for j := 0 to 31 do if controller^.devices[j].slotCount <> 0 then service_port(@controller^.devices[j],now,controller^.devices[j].port^.int_status); @@ -680,11 +727,13 @@ end; { Atomic reserve: software ownership precedes descriptor construction. } function find_cmd_slot(device : PAHCI_Device) : uint32; -var flags, slot, active, count : uint32; +var + irqFlags: PtrUInt; + flags, slot, active, count : uint32; begin find_cmd_slot := $FFFFFFFF; if (device = nil) or (device^.port = nil) then exit; - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; if not device^.offline then begin active := device^.port^.cmd_issue or device^.port^.sata_active; count := device^.slotCount; if count > 32 then count := 32; @@ -696,33 +745,60 @@ begin end; end; end; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); end; { Split at the kernel's 4 MiB virtual mapping boundaries. Every segment gets its own physical translation, including buffers with an unaligned start. } -function build_prdt(table : PHBA_CMD_TABLE; address, bytes : uint32; var entries : uint32) : TError; -var remaining, chunk : uint32; +function build_prdt(table: PHBA_CMD_TABLE; address: PtrUInt; bytes: uint32; var entries: uint32; dma64: boolean): TError; +var + remaining, chunk, previousBytes: uint32; + physical, previous: uint64; begin - build_prdt := eInvalidArgument; entries := 0; - if (bytes = 0) or ((address and 1) <> 0) or ((bytes and 1) <> 0) or (bytes > $FFFFFFFF-address) then exit; + build_prdt := eInvalidArgument; + entries := 0; + if (bytes = 0) or ((address and 1) <> 0) or ((bytes and 1) <> 0) or + (bytes > High(PtrUInt) - address) then exit; remaining := bytes; while remaining > 0 do begin - if entries = 32 then exit; - chunk := $400000-(address and $3FFFFF); if chunk > remaining then chunk := remaining; - table^.prdt[entries].dba := vtop(address); table^.prdt[entries].dbau := 0; - table^.prdt[entries].rsv0 := 0; table^.prdt[entries].reserved := 0; - table^.prdt[entries].dbc := chunk-1; table^.prdt[entries].int := 0; - Inc(entries); address := address+chunk; remaining := remaining-chunk; + physical := arch.platform.memory.physicalAddress(address); + if physical = 0 then exit; + if not dma64 and (physical > $FFFFFFFF) then exit; + chunk := 4096 - (address and 4095); + if chunk > remaining then chunk := remaining; + previousBytes := 0; + if (entries > 0) and ((address and $3FFFFF) <> 0) then begin + previous := uint64(table^.prdt[entries - 1].dba) or + (uint64(table^.prdt[entries - 1].dbau) shl 32); + previousBytes := table^.prdt[entries - 1].dbc + 1; + if (previous + previousBytes <> physical) or + (previousBytes + chunk > $400000) then previousBytes := 0; + end; + if previousBytes <> 0 then + table^.prdt[entries - 1].dbc := previousBytes + chunk - 1 + else begin + if entries = 32 then exit; + table^.prdt[entries].dba := uint32(physical); + table^.prdt[entries].dbau := uint32(physical shr 32); + table^.prdt[entries].rsv0 := 0; + table^.prdt[entries].reserved := 0; + table^.prdt[entries].dbc := chunk - 1; + table^.prdt[entries].int := 0; + inc(entries); + end; + inc(address, chunk); + dec(remaining, chunk); end; - table^.prdt[entries-1].int := 1; + table^.prdt[entries - 1].int := 1; build_prdt := eNone; end; { mode: 0 SATA read, 1 SATA write, 2 ATAPI READ(12), 3 READ CAPACITY. } function submit_command(device : PAHCI_Device; lba : uint64; bytes : uint32; buffer : puint32; mode : uint32; completion : TIOCompletion; userdata : puint32) : TError; -var slot, entries, sectors, flags : uint32; header : PHBA_CMD_HEADER; table : PHBA_CMD_TABLE; +var + irqFlags: PtrUInt; + slot, entries, sectors, flags : uint32; header : PHBA_CMD_HEADER; table : PHBA_CMD_TABLE; fis : PHBA_FIS_REG_H2D; err : TError; begin submit_command := eInvalidArgument; @@ -743,14 +819,14 @@ begin end; slot := find_cmd_slot(device); if slot = $FFFFFFFF then begin submit_command := eNoFreeSlot; exit; end; - header := @device^.command_list[slot]; table := @device^.command_table[slot]; + header := @device^.command_list[slot]; table := PHBA_CMD_TABLE(PtrUInt(device^.command_table) + slot * 4096); { Preserve the command-table pointer but reset every per-command header flag. } puint32(header)^ := 0; header^.prdbc := 0; header^.cmd_fis_length := sizeof(THBA_FIS_REG_H2D) div 4; if mode = 1 then header^.wrt := 1; if mode >= 2 then header^.atapi := 1; - memset(uint32(table),0,128); - err := build_prdt(table,uint32(buffer),bytes,entries); + memset(PtrUInt(table),0,128); + err := build_prdt(table,PtrUInt(buffer),bytes,entries,device^.supports64BitDMA); if err = eNone then begin header^.prdtl := entries; fis := PHBA_FIS_REG_H2D(@table^.cmd_fis); fis^.fis_type := uint8(FIS_TYPE_REG_H2D); fis^.c := 1; @@ -779,7 +855,7 @@ begin end; end; end; - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; if err = eNone then begin if device^.offline then err := eDeviceNotReady else if (device^.port^.tfd and $88) <> 0 then err := eDeviceNotReady; @@ -791,7 +867,7 @@ begin if (mode < 2) and device^.supportsNCQ then device^.port^.sata_active := uint32(1) shl slot; device^.port^.cmd_issue := uint32(1) shl slot; end else device^.pending[slot].inUse := false; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); submit_command := err; end; @@ -809,16 +885,18 @@ var state : PCapacityWait; begin state := PCapacityWait(userdata); state^.Error := error; puint32(@state^.Done)^ := 1; end; function send_read_capacity(device : PAHCI_Device; sectorCount : puint32; blockSize : puint32) : boolean; -var buffer : puint32; state : TCapacityWait; rawLBA, rawBlkSz : uint32; err : TError; +var flags: PtrUInt; buffer : puint32; state : TCapacityWait; rawLBA, rawBlkSz : uint32; err : TError; begin send_read_capacity := false; sectorCount^ := 0; blockSize^ := 0; - if not INTE then exit; { init/task context only } + flags := arch.platform.cpu.lock; + arch.platform.cpu.unlock(flags); + if (flags and $200) = 0 then exit; { init/task context only } buffer := kalloc(512); if buffer = nil then exit; - memset(uint32(buffer),0,512); state.Done := 0; state.Error := eNone; + memset(PtrUInt(buffer),0,512); state.Done := 0; state.Error := eNone; err := submit_command(device,0,8,buffer,3,@capacity_done,puint32(@state)); if err = eNone then begin { The watchdog ends ownership only after stopping DMA on error/timeout. } - while puint32(@state.Done)^ = 0 do asm hlt end; + while puint32(@state.Done)^ = 0 do arch.platform.cpu.idle; err := state.Error; end; if err = eNone then begin @@ -924,7 +1002,7 @@ end; procedure run_ahci_tests(stdout_buf : POutBuf); var device : PAHCI_Device; port : PHBA_Port; table : PHBA_CMD_TABLE; - buffer : pointer; aligned, entries, first, second, passed, failed : uint32; + buffer : pointer; aligned: PtrUInt; entries, first, second, passed, failed, i, total : uint32; result : TError; completed : TTestCompletion; msg, number : pchar; procedure Assert(ok : boolean; name : pchar); begin @@ -937,8 +1015,8 @@ var device : PAHCI_Device; port : PHBA_Port; table : PHBA_CMD_TABLE; end; procedure reset_fake; begin - memset(uint32(port),0,sizeof(THBA_Port)); - memset(uint32(@device^.pending),0,sizeof(device^.pending)); + memset(PtrUInt(port),0,sizeof(THBA_Port)); + memset(PtrUInt(@device^.pending),0,sizeof(device^.pending)); device^.offline := false; device^.recovering := false; device^.supportsNCQ := true; completed.Count := 0; completed.Error := eNone; end; @@ -947,20 +1025,24 @@ begin device := PAHCI_Device(kalloc(sizeof(TAHCI_Device))); port := PHBA_Port(kalloc(sizeof(THBA_Port))); device^.port := port; device^.command_list := PHBA_CMD_HEADER(kalloc(32*sizeof(THBA_CMD_HEADER))); - device^.command_table := PHBA_CMD_TABLE(kalloc(32*sizeof(THBA_CMD_TABLE))); + device^.command_table := PHBA_CMD_TABLE(kalloc(32*4096)); device^.slotCount := 32; table := device^.command_table; - buffer := kalloc($800000); aligned := (uint32(buffer)+$3FFFFF) and $FFC00000; + buffer := kalloc($800000); aligned := (PtrUInt(buffer)+$3FFFFF) and not PtrUInt($3FFFFF); + device^.supports64BitDMA := true; reset_fake; - Assert(build_prdt(table,aligned,$400000,entries)=eNone,'exact 4 MiB accepted'); - Assert((entries=1) and (table^.prdt[0].dbc=$3FFFFF),'exact 4 MiB has one nonempty PRD'); - Assert(build_prdt(table,aligned-512,1024,entries)=eNone,'cross-page transfer accepted'); + Assert(build_prdt(table,aligned,$400000,entries,true)=eNone,'exact 4 MiB accepted'); + total := 0; + if entries > 0 then + for i := 0 to entries - 1 do inc(total, table^.prdt[i].dbc + 1); + Assert((entries > 0) and (total = $400000), 'PRDs cover the complete transfer'); + Assert(build_prdt(table,aligned-512,1024,entries,true)=eNone,'cross-page transfer accepted'); Assert((entries=2) and (table^.prdt[0].dbc=511) and (table^.prdt[1].dbc=511),'cross-page segments bounded'); - Assert((table^.prdt[0].dba=vtop(aligned-512)) and (table^.prdt[1].dba=vtop(aligned)),'each page translated independently'); + Assert(((uint64(table^.prdt[0].dba) or (uint64(table^.prdt[0].dbau) shl 32))=arch.platform.memory.physicalAddress(aligned-512)) and ((uint64(table^.prdt[1].dba) or (uint64(table^.prdt[1].dbau) shl 32))=arch.platform.memory.physicalAddress(aligned)),'each page translated independently'); Assert((table^.prdt[0].int=0) and (table^.prdt[1].int=1),'only final PRD interrupts'); - Assert(build_prdt(table,aligned,0,entries)=eInvalidArgument,'zero length rejected'); - Assert(build_prdt(table,aligned+1,512,entries)=eInvalidArgument,'odd address rejected'); - Assert(build_prdt(table,aligned,513,entries)=eInvalidArgument,'odd length rejected'); - Assert(build_prdt(table,$FFFFFF00,512,entries)=eInvalidArgument,'address wrap rejected'); + Assert(build_prdt(table,aligned,0,entries,true)=eInvalidArgument,'zero length rejected'); + Assert(build_prdt(table,aligned+1,512,entries,true)=eInvalidArgument,'odd address rejected'); + Assert(build_prdt(table,aligned,513,entries,true)=eInvalidArgument,'odd length rejected'); + Assert(build_prdt(table,High(PtrUInt)-$FF,512,entries,true)=eInvalidArgument,'address wrap rejected'); first := find_cmd_slot(device); second := find_cmd_slot(device); Assert((first=0) and (second=1),'reservation excludes concurrent builder'); service_port(device,now_tick,AHCI_FATAL_MASK); @@ -1013,7 +1095,7 @@ begin number := intToString(passed); msg := stringConcat(number,' passed, '); kfree(void(number)); number := intToString(failed); buffer := msg; msg := stringConcat(msg,number); kfree(buffer); kfree(void(number)); buffer := msg; msg := stringConcat(msg,' failed'); kfree(buffer); - io.syslog.logln('AHCITEST',msg); + arch.platform.diagnostics.testSummary('AHCI', 'AHCI_TESTS_PASSED', 'AHCI_TESTS_FAILED', passed, failed); if stdout_buf <> nil then io.stdio.bufWriteStrLn(stdout_buf,msg); kfree(void(msg)); end; @@ -1023,7 +1105,4 @@ begin run_ahci_tests(nil); end; procedure cmd_ahcitest(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); begin run_ahci_tests(stdout_buf); end; -Initialization - boot.mgr.registerBoot('driver.storage.ctl.ahci', @init, 'AHCI Controller', 'driver.storage.fs.*'); - end. diff --git a/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.types.pas b/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.types.pas index 54b5204d..9e203d88 100644 --- a/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.types.pas +++ b/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.types.pas @@ -28,8 +28,7 @@ uses core.ds.lists, memory.heap, driver.bus.pci, - core.util, arch.x86.util, - arch.x86.memory.virtual; + core.util; const AHCI_CONTROLLER_MODE = $80000000; @@ -278,6 +277,7 @@ type device_type : TDeviceType; ata_info : TIdentResponse; supportsNCQ : boolean; + supports64BitDMA: boolean; queueDepth : uint32; { safe active depth for this device } slotCount : uint32; { controller slot count from CAP.NCS + 1 } command_list : PHBA_CMD_HEADER; diff --git a/src/driver/storage/ctl/driver.storage.ctl.ram.pas b/src/driver/storage/ctl/driver.storage.ctl.ram.pas index fc50021b..8e12ec92 100644 --- a/src/driver/storage/ctl/driver.storage.ctl.ram.pas +++ b/src/driver/storage/ctl/driver.storage.ctl.ram.pas @@ -36,7 +36,7 @@ procedure init; implementation uses - core.strings, memory.heap, core.ds.lists, core.util, arch.x86.util; + core.strings, memory.heap, core.ds.lists, core.util, arch.platform.cpu; const MAX_RAM_FILES = 32; @@ -53,6 +53,7 @@ var Files : array[0..MAX_RAM_FILES-1] of TRAMFile; FileCount : uint32; RAMVolume : TStorage_Volume; + RAMDevice : PStorage_Device; RAMFS : TFilesystem; { ---- Internal helpers ---- } @@ -75,7 +76,7 @@ end; function stripLeadingSlash(path : pchar) : pchar; begin if (path <> nil) and (path[0] = '/') then - stripLeadingSlash := pchar(uint32(path) + 1) + stripLeadingSlash := pchar(PtrUInt(path) + 1) else stripLeadingSlash := path; end; @@ -103,7 +104,7 @@ begin avail := Files[idx].Size - offset; if byteCount < avail then copyLen := byteCount else copyLen := avail; - memcpy(uint32(Files[idx].Data) + offset, uint32(buffer), copyLen); + memcpy(PtrUInt(Files[idx].Data) + offset, PtrUInt(buffer), copyLen); rd_ReadOffset := copyLen; end; @@ -193,8 +194,9 @@ begin if (data = nil) or (size = 0) then exit; buf := puint8(kalloc(size)); if buf = nil then exit; - memcpy(uint32(data), uint32(buf), size); + memcpy(PtrUInt(data), PtrUInt(buf), size); storeFileCopy := storeFile(name, buf, size); + if not storeFileCopy then kfree(void(buf)); end; procedure removeFile(name : pchar; freeBuf : boolean); @@ -255,7 +257,7 @@ begin FileCount := 0; { Set up a minimal filesystem descriptor with our callbacks } - memset(uint32(@RAMFS), 0, sizeof(TFilesystem)); + memset(PtrUInt(@RAMFS), 0, sizeof(TFilesystem)); RAMFS.sName := 'ramfs'; RAMFS.readDirCallback := @rd_ReadDir; RAMFS.readOffsetCallback := @rd_ReadOffset; @@ -263,8 +265,14 @@ begin RAMFS.identifyCallback := @rd_Identify; { Set up a virtual volume backed by our filesystem } - memset(uint32(@RAMVolume), 0, sizeof(TStorage_Volume)); + memset(PtrUInt(@RAMVolume), 0, sizeof(TStorage_Volume)); RAMVolume.filesystem := @RAMFS; + RAMDevice := PStorage_Device(kalloc(sizeof(TStorage_Device))); + if RAMDevice = nil then exit; + RAMDevice^.controller := ControllerRAM; + RAMDevice^.sectorSize := 512; + RAMVolume.device := RAMDevice; + RAMVolume.sectorSize := 512; { Mount into VFS at /disk/ram } driver.storage.vfs.mountVolume('/disk/ram', @RAMVolume); diff --git a/src/driver/storage/ctl/driver.storage.ctl.usb.pas b/src/driver/storage/ctl/driver.storage.ctl.usb.pas index c02e00f9..8db4a530 100644 --- a/src/driver/storage/ctl/driver.storage.ctl.usb.pas +++ b/src/driver/storage/ctl/driver.storage.ctl.usb.pas @@ -28,10 +28,10 @@ procedure UnitTest; implementation -uses boot.mgr, driver.mgr, memory.heap, driver.storage.types, +uses arch.platform.diagnostics, arch.common.legacyirq, driver.mgr, memory.heap, driver.storage.types, driver.storage.mgr, driver.bus.usb.core, driver.bus.usb.types, - proc.mgr, proc.types, arch.x86.bda, arch.x86.isr.tmr0, - core.util, arch.x86.util, io.syslog, io.stdio, core.strings, + proc.mgr, proc.types, arch.platform.process, + core.util, arch.platform.cpu, io.syslog, io.stdio, core.strings, driver.storage.vfs, driver.storage.vol.mgr, core.ds.lists; const @@ -51,6 +51,7 @@ type Status : uint8; end; PCSW = ^TCSW; + PVolumeSlot = ^PStorage_Volume; PMSDevice = ^TMSDevice; TMSDevice = record Next : PMSDevice; @@ -108,16 +109,16 @@ begin end; procedure poll_controller(c : PMSDevice); -var flags : uint32; +var flags : PtrUInt; begin { Poll and its descriptor cleanup must not be preempted by another poll. } - asm pushfd; cli; pop flags end; + flags := arch.platform.cpu.lock; if c^.USB^.HC^.fnPoll <> nil then c^.USB^.HC^.fnPoll(c^.USB^.HC); - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(flags); end; function run_transfer(c : PMSDevice) : TUSBTransferStatus; -var started, spins, flags : uint32; submitted : boolean; +var started, spins : uint32; flags : PtrUInt; submitted : boolean; begin run_transfer := tsTimeout; if c^.Offline then exit; @@ -127,23 +128,23 @@ begin c^.InFlight := true; { fnSubmit can configure xHCI endpoints and wait for command completion; never call it under the storage manager's IRQ-disabled dispatch lock. } - asm pushfd; pop flags end; - if c^.USB^.HC^.HCType <> USB_HC_XHCI then asm cli end; + flags := arch.platform.cpu.lock; + if c^.USB^.HC^.HCType = USB_HC_XHCI then arch.platform.cpu.unlock(flags); if c^.Offline then submitted := false else submitted := c^.USB^.HC^.fnSubmit(c^.USB^.HC, @c^.Transfer); - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(flags); if not submitted then begin c^.InFlight := false; exit; end; - started := puint32(@Counters.c32)^; + started := arch.platform.process.timerTicks; spins := 0; while transfer_pending(c) do begin poll_controller(c); if not transfer_pending(c) then break; inc(spins); if (c^.Original = nil) or - (puint32(@Counters.c32)^ - started >= TRANSFER_TICKS) or + (arch.platform.process.timerTicks - started >= TRANSFER_TICKS) or (spins >= 10000000) then begin { No HC cancellation contract exists yet. Fail closed and retain the private transfer/buffers until completion; NEVER free live DMA. } @@ -152,8 +153,9 @@ begin io.syslog.logln('USBMSC', 'Transfer timed out/disconnected; device offline, DMA quarantined.'); exit; end; - asm pushfd; pop flags end; - if (flags and $200) <> 0 then asm hlt end; + flags := arch.platform.cpu.lock; + arch.platform.cpu.unlock(flags); + if (flags and $200) <> 0 then arch.platform.cpu.idle; end; c^.InFlight := false; run_transfer := c^.Transfer.Status; @@ -216,7 +218,7 @@ end; procedure command(c : PMSDevice; opcode, length : uint8; bytes : uint32; input : boolean); begin - memset(uint32(@c^.CBW), 0, sizeof(TCBW)); + memset(PtrUInt(@c^.CBW), 0, sizeof(TCBW)); inc(c^.Tag); c^.CBW.Signature := CBW_SIGNATURE; c^.CBW.Tag := c^.Tag; @@ -242,7 +244,7 @@ begin while offset < c^.CBW.DataLength do begin count := c^.CBW.DataLength - offset; { One bounded data transfer per command, at most one page of DMA. } - status := bulk(c, ep, pointer(uint32(c^.Buffer) + offset), count, actual); + status := bulk(c, ep, pointer(PtrUInt(c^.Buffer) + offset), count, actual); if status = tsStall then begin if not clear_halt(c, ep) then begin recover(c); exit; end; break; @@ -252,7 +254,7 @@ begin if actual < count then break; end; transferred := offset; - memset(uint32(@c^.CSW), 0, sizeof(TCSW)); + memset(PtrUInt(@c^.CSW), 0, sizeof(TCSW)); status := bulk(c, c^.InEP, @c^.CSW, sizeof(TCSW), actual); if status = tsStall then begin if clear_halt(c, c^.InEP) then @@ -273,7 +275,7 @@ begin sense := eIOError; command(c, $03, 6, 18, true); c^.CBW.Command[4] := 18; - memset(uint32(c^.Buffer), 0, 18); + memset(PtrUInt(c^.Buffer), 0, 18); if transport(c, actual) <> 0 then exit; if actual < 14 then exit; if (puint8(c^.Buffer)[0] and $7E) <> $70 then exit; @@ -344,13 +346,13 @@ begin command(c, $00, 6, 0, false); error := execute(c, 0); if error <> eDeviceNotReady then break; - psleep((PIT_INPUT_HZ div PIT_DIVISOR) div 5); + core.util.waitTicks((PIT_INPUT_HZ div PIT_DIVISOR) div 5); end; if error <> eNone then exit; command(c, $25, 10, 8, true); if execute(c, 8) <> eNone then exit; sectors := be32(puint8(c^.Buffer)); - sectorSize := be32(puint8(uint32(c^.Buffer) + 4)); + sectorSize := be32(puint8(PtrUInt(c^.Buffer) + 4)); if not valid_capacity(sectors, sectorSize) then exit; inc(sectors); probe := true; @@ -363,7 +365,7 @@ begin if r^.LBA >= d^.maxSectorCount then exit; if r^.SectorCount > d^.maxSectorCount - r^.LBA then exit; if r^.SectorCount > $FFFFFFFF div d^.sectorSize then exit; - if uint32(r^.Buffer) > $FFFFFFFF - r^.SectorCount * d^.sectorSize then exit; + if PtrUInt(r^.Buffer) > High(PtrUInt) - r^.SectorCount * d^.sectorSize then exit; valid_request := true; end; @@ -407,7 +409,7 @@ begin if r^.RequestType = ioRead then command(c, $28, 10, bytes, true) else begin command(c, $2A, 10, bytes, false); - memcpy(uint32(r^.Buffer) + offset, uint32(c^.Buffer), bytes); + memcpy(PtrUInt(r^.Buffer) + offset, PtrUInt(c^.Buffer), bytes); end; put32(@c^.CBW.Command[2], r^.LBA + doneBlocks); c^.CBW.Command[7] := blocks shr 8; @@ -415,7 +417,7 @@ begin error := execute(c, bytes); if error = eNone then begin if r^.RequestType = ioRead then - memcpy(uint32(c^.Buffer), uint32(r^.Buffer) + offset, bytes); + memcpy(PtrUInt(c^.Buffer), PtrUInt(r^.Buffer) + offset, bytes); inc(doneBlocks, blocks); inc(offset, bytes); end; end; @@ -431,9 +433,9 @@ begin end; procedure disconnect(dev : PUSBDevice); -var c : PMSDevice; flags : uint32; +var c : PMSDevice; flags : PtrUInt; begin - asm pushfd; cli; pop flags end; + flags := arch.platform.cpu.lock; c := Devices; while c <> nil do begin if c^.Original = dev then begin @@ -444,7 +446,7 @@ begin end; c := c^.Next; end; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(flags); io.syslog.logln('USBMSC', 'Disk disconnected; further I/O rejected.'); end; @@ -459,7 +461,7 @@ begin if c^.Disk^.volumes = nil then exit; i := 0; while i < DL_Size(c^.Disk^.volumes) do begin - v := PStorage_Volume(puint32(DL_Get(c^.Disk^.volumes, i))^); + v := PVolumeSlot(DL_Get(c^.Disk^.volumes, i))^; if (v <> nil) and ((v^.lifecycleFlags and STORAGE_VOLUME_FLAG_MOUNTED) <> 0) then begin if driver.storage.vol.mgr.volume_is_busy(v) then detach_volumes := false else if not driver.storage.vfs.InvalidateVolume(v) then detach_volumes := false; @@ -469,7 +471,7 @@ begin end; procedure worker_loop(ctx : PProcessContext); -var c, next, prev, scan : PMSDevice; flags : uint32; +var c, next, prev, scan : PMSDevice; flags : PtrUInt; begin while true do begin c := Devices; @@ -490,12 +492,12 @@ begin if (c^.Original = nil) and c^.VolumesDetached then begin { Keep the manager's removed-device/volume tombstones stable; release every class-driver allocation once DMA is finished. } - asm pushfd; cli; pop flags end; + flags := arch.platform.cpu.lock; prev := nil; scan := Devices; while (scan <> nil) and (scan <> c) do begin prev := scan; scan := scan^.Next; end; if prev = nil then Devices := c^.Next else prev^.Next := c^.Next; if c^.Disk <> nil then c^.Disk^.controllerId0 := 0; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(flags); kfree(void(c^.USB)); kfree_aligned(c); end; end; @@ -506,16 +508,16 @@ begin end; procedure attach_worker(ctx : PProcessContext); -var c : PMSDevice; d : PStorage_Device; sectors, sectorSize, i, flags : uint32; +var c : PMSDevice; d : PStorage_Device; sectors, sectorSize, i : uint32; flags : PtrUInt; claimed : boolean; begin c := PMSDevice(ctx^.Local); { xHCI command submission and manager registration are not reentrant. } repeat - asm pushfd; cli; pop flags end; + flags := arch.platform.cpu.lock; claimed := not AttachBusy; if claimed then AttachBusy := true; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(flags); if not claimed then proc.mgr.proc_sleep_ms(1); until claimed; d := PStorage_Device(kalloc(sizeof(TStorage_Device))); @@ -529,13 +531,13 @@ begin io.syslog.logln('USBMSC', 'Probe failed or unsupported disk geometry.'); exit; end; - memset(uint32(d), 0, sizeof(TStorage_Device)); + memset(PtrUInt(d), 0, sizeof(TStorage_Device)); d^.volumes := DL_New(sizeof(pointer)); if d^.volumes = nil then begin kfree(void(d)); c^.Offline := true; c^.Ready := true; c^.Attaching := false; AttachBusy := false; exit; end; d^.controller := ControllerUSB; - d^.controllerId0 := uint32(c); + d^.controllerId0 := PtrUInt(c); d^.writable := true; d^.sectorSize := sectorSize; d^.maxSectorCount := sectors; @@ -547,7 +549,7 @@ begin { Manager copies the record; never keep the temporary descriptor. } i := 0; while i < driver.storage.mgr.get_device_count do begin - if driver.storage.mgr.get_device(i)^.controllerId0 = uint32(c) then begin + if driver.storage.mgr.get_device(i)^.controllerId0 = PtrUInt(c) then begin c^.Disk := driver.storage.mgr.get_device(i); break; end; inc(i); @@ -560,7 +562,8 @@ end; function load(ptr : void) : boolean; var dev : PUSBDevice; c : PMSDevice; - i, flags : uint32; + i : uint32; + flags : PtrUInt; begin load := false; dev := PUSBDevice(ptr); @@ -575,14 +578,14 @@ begin end; c := PMSDevice(kalloc_aligned(sizeof(TMSDevice), 4096)); if c = nil then exit; - memset(uint32(c), 0, sizeof(TMSDevice)); + memset(PtrUInt(c), 0, sizeof(TMSDevice)); c^.USB := PUSBDevice(kalloc(sizeof(TUSBDevice))); c^.Buffer := kalloc_aligned(BUFFER_SIZE, 4096); if (c^.USB = nil) or (c^.Buffer = nil) then begin if c^.USB <> nil then kfree(void(c^.USB)); kfree_aligned(c^.Buffer); kfree_aligned(c); exit; end; - memcpy(uint32(dev), uint32(c^.USB), sizeof(TUSBDevice)); + memcpy(PtrUInt(dev), PtrUInt(c^.USB), sizeof(TUSBDevice)); c^.Original := dev; c^.Attaching := true; for i := 0 to 1 do begin @@ -601,10 +604,10 @@ begin c^.ControlEP.Direction := dirSetup; c^.ControlEP.MaxPacket := dev^.MaxPacket0; { Publish before probing so disconnect can invalidate the private context. } - asm pushfd; cli; pop flags end; + flags := arch.platform.cpu.lock; c^.Next := Devices; Devices := c; dev^.fnDisconnect := @disconnect; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(flags); { The scheduler starts only after boot.mgr.run. Probe and register from a task so partition discovery can wait for the persistent I/O worker. } if proc.mgr.create('usb.attach', @attach_worker, void(c), 1) = nil then begin @@ -652,11 +655,11 @@ begin end; 1: begin if m^.C.CBW.Flags = $80 then begin - memset(uint32(t^.Buffer), $A5, t^.BufferLen); + memset(PtrUInt(t^.Buffer), $A5, t^.BufferLen); case m^.C.CBW.Command[0] of $12: puint8(t^.Buffer)[0] := 0; - $25: begin put32(puint8(t^.Buffer), 8191); put32(puint8(uint32(t^.Buffer)+4), 512); end; - $03: begin memset(uint32(t^.Buffer),0,t^.BufferLen); puint8(t^.Buffer)[0]:=$70; + $25: begin put32(puint8(t^.Buffer), 8191); put32(puint8(PtrUInt(t^.Buffer)+4), 512); end; + $03: begin memset(PtrUInt(t^.Buffer),0,t^.BufferLen); puint8(t^.Buffer)[0]:=$70; if m^.SenseKey = 0 then puint8(t^.Buffer)[2]:=7 else puint8(t^.Buffer)[2]:=m^.SenseKey; end; end; end else begin @@ -720,7 +723,7 @@ begin m := PMock(kalloc(sizeof(TMock))); Assert(m <> nil, 'mock allocation'); if m <> nil then begin - memset(uint32(m), 0, sizeof(TMock)); + memset(PtrUInt(m), 0, sizeof(TMock)); m^.HC.fnSubmit := @mock_submit; m^.HC.HCType := USB_HC_EHCI; m^.HC.PrivData := m; m^.USB.HC := @m^.HC; m^.USB.InterfaceNumber := 3; m^.USB.Endpoints[0].Address := 1; m^.USB.Endpoints[0].Direction := dirIn; @@ -734,7 +737,7 @@ begin Assert(probe(@m^.C, sectors, size) and (sectors = 8192) and (size = 512), 'probe SCSI disk'); command(@m^.C, $28, 10, 512, true); Assert((execute(@m^.C,512) = eNone) and (puint8(m^.C.Buffer)[511] = $A5), 'read data phase'); - command(@m^.C, $2A, 10, 512, false); memset(uint32(m^.C.Buffer),$5A,512); + command(@m^.C, $2A, 10, 512, false); memset(PtrUInt(m^.C.Buffer),$5A,512); Assert((execute(@m^.C,512) = eNone) and m^.GoodWrite, 'write data phase'); m^.Fault := 3; command(@m^.C, $2A, 10, 512, false); Assert(execute(@m^.C,512) = eReadOnly, 'command failure requests sense'); @@ -773,14 +776,13 @@ begin Assert(not valid_request(@m^.Disk,@m^.Request), 'zero-sector request rejected'); m^.Request.LBA := $FFFFFFFE; m^.Request.SectorCount := 4; Assert(not valid_request(@m^.Disk,@m^.Request), 'wrapped LBA rejected'); - m^.Request.LBA := 0; m^.Request.SectorCount := 1; m^.Request.Buffer := pointer($FFFFFF00); + m^.Request.LBA := 0; m^.Request.SectorCount := 1; m^.Request.Buffer := pointer(High(PtrUInt) - $FF); Assert(not valid_request(@m^.Disk,@m^.Request), 'wrapped buffer rejected'); kfree_aligned(m^.C.Buffer); end; kfree(void(m)); end; - io.syslog.log('USBMSC', 'Tests passed: '); io.syslog.writeint(passed); - io.syslog.writestring(' failed: '); io.syslog.writeintln(failed); + testSummary('USBMSC', 'USBMSC_TESTS_PASSED', 'USBMSC_TESTS_FAILED', passed, failed); end; procedure test_command(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); @@ -800,6 +802,4 @@ begin driver.mgr.register_driver('USB Mass Storage', @id, @load); end; -initialization - boot.mgr.registerBoot('driver.storage.ctl.usb', @init, 'USB Mass Storage Driver', BOOT_MGR_BARRIER_DEVICE); end. diff --git a/src/driver/storage/driver.storage.fdtable.pas b/src/driver/storage/driver.storage.fdtable.pas index 50430d40..7b1305e0 100644 --- a/src/driver/storage/driver.storage.fdtable.pas +++ b/src/driver/storage/driver.storage.fdtable.pas @@ -1,4 +1,5 @@ // Copyright 2021 Aaron Hance +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,15 +29,18 @@ interface uses memory.heap, driver.storage.types, - core.util, arch.x86.util; + memory.raw, arch.platform.cpu; const MAX_FDS = 64; type + TFDIdleHook = procedure; PFileDescriptor = ^TFileDescriptor; TFileDescriptor = record InUse : boolean; + Closing : boolean; + WaitClose : boolean; AsyncRefs : uint32; { in-flight async ops using this descriptor } Volume : PStorage_Volume; Directory : pchar; { directory path within the volume } @@ -53,6 +57,7 @@ type PFDTable = ^TFDTable; TFDTable = record Entries : array[0..MAX_FDS-1] of TFileDescriptor; + Closing, Freeing: boolean; end; { Allocate and zero-initialise a new FD table on the heap. @@ -61,8 +66,11 @@ type function fd_table_new : PFDTable; { Free all open descriptors in the table, then free the table itself. - Called when a process is reaped. Safe to pass nil. } + Called when a process is reaped. Safe to pass nil. If begin_close fails, + ownership remains with the caller until async users release their references. } procedure fd_table_free(table : PFDTable); +{ Atomically prevent new references before detaching an owner. False means busy. } +function fd_table_begin_close(table: PFDTable): boolean; { Find the first free slot in the table. Returns 1-based handle (1..MAX_FDS) on success, 0 if table is full. } @@ -88,57 +96,113 @@ procedure fd_close_all(table : PFDTable); Called when a volume is removed to prevent dangling pointers. } procedure fd_close_for_volume(table : PFDTable; vol : PStorage_Volume); +procedure UnitTest; +{ Hook only wakes a worker; called with IRQs excluded, never performs cleanup. } +procedure fd_set_idle_hook(hook: TFDIdleHook); + implementation +uses debug.early; +var + idleHook: TFDIdleHook; + +procedure fd_set_idle_hook(hook: TFDIdleHook); +var + flags: PtrUInt; +begin + flags := arch.platform.cpu.lock; + idleHook := hook; + arch.platform.cpu.unlock(flags); +end; function fd_table_new : PFDTable; -var - tbl : PFDTable; begin - tbl := PFDTable(kalloc(SizeOf(TFDTable))); - if tbl <> nil then - memset(uint32(tbl), 0, SizeOf(TFDTable)); - fd_table_new := tbl; + fd_table_new := PFDTable(kalloc(SizeOf(TFDTable))); end; type TFSPrivateClose = procedure(data : pointer); -procedure fd_free_entry(entry : PFileDescriptor); +procedure releaseEntry(entry: PFileDescriptor); +var + data: pointer; + hook: TFSPrivateClose; + flags: PtrUInt; + wake: boolean; begin - if entry = nil then exit; - if not entry^.InUse then exit; - - { Free FS-level per-file metadata first } - if (entry^.FSPrivate <> nil) and (entry^.FSCloseHook <> nil) then - TFSPrivateClose(entry^.FSCloseHook)(entry^.FSPrivate); + { Entry is already claimed under IRQ exclusion. Never publish it for reuse + while its close callback can still re-enter the descriptor API. } + data := entry^.FSPrivate; + hook := TFSPrivateClose(entry^.FSCloseHook); entry^.FSPrivate := nil; entry^.FSCloseHook := nil; - - if entry^.Directory <> nil then begin - kfree(void(entry^.Directory)); - entry^.Directory := nil; - end; - if entry^.FileName <> nil then begin - kfree(void(entry^.FileName)); - entry^.FileName := nil; - end; - if entry^.VFSDir <> nil then begin - kfree(void(entry^.VFSDir)); - entry^.VFSDir := nil; - end; - - entry^.InUse := false; - entry^.AsyncRefs := 0; - entry^.DataSize := 0; - entry^.DeviceOps := nil; - entry^.DeviceData := nil; + if (data <> nil) and assigned(hook) then hook(data); + if entry^.Directory <> nil then kfree(void(entry^.Directory)); + if entry^.FileName <> nil then kfree(void(entry^.FileName)); + if entry^.VFSDir <> nil then kfree(void(entry^.VFSDir)); + flags := arch.platform.cpu.lock; + wake := entry^.WaitClose; + memory.raw.fill(pointer(entry), 0, sizeof(TFileDescriptor)); + if wake and assigned(idleHook) then idleHook; + arch.platform.cpu.unlock(flags); end; -procedure fd_table_free(table : PFDTable); +function fd_free_entry(entry: PFileDescriptor): boolean; +var + flags: PtrUInt; +begin + fd_free_entry := false; + if entry = nil then exit; + flags := arch.platform.cpu.lock; + if (not entry^.InUse) or entry^.Closing or (entry^.AsyncRefs <> 0) then begin + arch.platform.cpu.unlock(flags); + exit; + end; + entry^.Closing := true; + arch.platform.cpu.unlock(flags); + releaseEntry(entry); + fd_free_entry := true; +end; + +function fd_table_begin_close(table: PFDTable): boolean; +var + flags: PtrUInt; + i: uint32; +begin + fd_table_begin_close := true; + if table = nil then exit; + flags := arch.platform.cpu.lock; + if not table^.Closing then begin + for i := 0 to MAX_FDS - 1 do + if table^.Entries[i].Closing or (table^.Entries[i].AsyncRefs <> 0) then begin + table^.Entries[i].WaitClose := true; + fd_table_begin_close := false; + end; + if not fd_table_begin_close then begin + arch.platform.cpu.unlock(flags); + exit; + end; + table^.Closing := true; + for i := 0 to MAX_FDS - 1 do table^.Entries[i].Closing := true; + end; + arch.platform.cpu.unlock(flags); +end; + +procedure fd_table_free(table: PFDTable); +var + flags: PtrUInt; + i: uint32; begin if table = nil then exit; - fd_close_all(table); - kfree(puint32(table)); + flags := arch.platform.cpu.lock; + if table^.Freeing or not fd_table_begin_close(table) then begin + arch.platform.cpu.unlock(flags); + exit; + end; + table^.Freeing := true; + arch.platform.cpu.unlock(flags); + for i := 0 to MAX_FDS - 1 do + if table^.Entries[i].InUse then releaseEntry(@table^.Entries[i]); + kfree(void(table)); end; function fd_alloc(table : PFDTable) : uint32; @@ -147,8 +211,9 @@ var begin fd_alloc := 0; if table = nil then exit; + if table^.Closing then exit; for i := 0 to MAX_FDS - 1 do begin - if not table^.Entries[i].InUse then begin + if (not table^.Entries[i].InUse) and (not table^.Entries[i].Closing) then begin fd_alloc := i + 1; { 1-based } exit; end; @@ -160,36 +225,43 @@ begin fd_get := nil; if table = nil then exit; if (handle = 0) or (handle > MAX_FDS) then exit; + if table^.Closing or table^.Entries[handle - 1].Closing then exit; if not table^.Entries[handle - 1].InUse then exit; fd_get := @table^.Entries[handle - 1]; end; function fd_begin_async(entry : PFileDescriptor) : boolean; +var + flags: PtrUInt; begin fd_begin_async := false; if entry = nil then exit; - asm pushf; cli end; - if entry^.InUse then begin + flags := arch.platform.cpu.lock; + if entry^.InUse and not entry^.Closing and (entry^.AsyncRefs < High(uint32)) then begin entry^.AsyncRefs := entry^.AsyncRefs + 1; fd_begin_async := true; end; - asm popf end; + arch.platform.cpu.unlock(flags); end; procedure fd_end_async(entry : PFileDescriptor); +var + flags: PtrUInt; begin if entry = nil then exit; - asm pushf; cli end; - if entry^.AsyncRefs > 0 then - entry^.AsyncRefs := entry^.AsyncRefs - 1; - asm popf end; + flags := arch.platform.cpu.lock; + if entry^.AsyncRefs > 0 then begin + dec(entry^.AsyncRefs); + if (entry^.AsyncRefs = 0) and entry^.WaitClose and assigned(idleHook) then idleHook; + end; + arch.platform.cpu.unlock(flags); end; function fd_has_async(entry : PFileDescriptor) : boolean; begin fd_has_async := false; if entry = nil then exit; - fd_has_async := entry^.AsyncRefs > 0; + fd_has_async := PUInt32(@entry^.AsyncRefs)^ > 0; end; function fd_close(table : PFDTable; handle : uint32) : boolean; @@ -197,9 +269,9 @@ begin fd_close := false; if table = nil then exit; if (handle = 0) or (handle > MAX_FDS) then exit; + if table^.Closing or table^.Entries[handle - 1].Closing then exit; if not table^.Entries[handle - 1].InUse then exit; - fd_free_entry(@table^.Entries[handle - 1]); - fd_close := true; + fd_close := fd_free_entry(@table^.Entries[handle - 1]); end; procedure fd_close_all(table : PFDTable); @@ -225,4 +297,82 @@ begin end; end; +var + testTable: PFDTable; + testHandle, testClosed: uint32; + testReentry: boolean; + +procedure testClose(data: pointer); +begin + inc(testClosed); + testReentry := not fd_close(testTable, testHandle); + kfree(void(data)); +end; + +procedure UnitTest; +var + table: PFDTable; + entry: PFileDescriptor; + i, passed, failed: uint32; + beforeFree: SizeUInt; + slotsValid: boolean; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then begin + inc(passed); + puts('PASS: '); + end + else begin + inc(failed); + puts('FAIL: '); + end; + puts(name); + puts(#13#10); + end; +begin + passed := 0; + failed := 0; + beforeFree := lmm_total_free; + table := fd_table_new; + testTable := table; + testHandle := 1; + testClosed := 0; + testReentry := false; + Assert((fd_get(table, 0) = nil) and (fd_get(table, MAX_FDS + 1) = nil), 'FD handle boundaries'); + slotsValid := true; + for i := 0 to MAX_FDS - 1 do begin + if fd_alloc(table) <> i + 1 then slotsValid := false; + table^.Entries[i].InUse := true; + end; + Assert(slotsValid, 'FD bounded slot allocation'); + Assert(fd_alloc(table) = 0, 'FD full table rejects allocation'); + entry := fd_get(table, 1); + entry^.Directory := pchar(kalloc(3)); + entry^.FileName := pchar(kalloc(5)); + entry^.VFSDir := pchar(kalloc(7)); + entry^.FSPrivate := pointer(kalloc(9)); + entry^.FSCloseHook := @testClose; + Assert(fd_begin_async(entry), 'FD acquires async reference'); + Assert(not fd_close(table, 1), 'FD close retains active async storage'); + Assert(not fd_table_begin_close(table), 'FD table teardown waits for async references'); + fd_end_async(entry); + Assert(not fd_has_async(entry), 'FD completion releases async reference'); + Assert(fd_close(table, 1), 'FD closes after async completion'); + Assert((testClosed = 1) and testReentry, 'FD callback reentry cannot close twice'); + Assert(fd_get(table, 1) = nil, 'FD closed handle unavailable'); + Assert(fd_alloc(table) = 1, 'FD closed slot reusable'); + entry := @table^.Entries[0]; + entry^.InUse := true; + entry^.AsyncRefs := High(uint32); + Assert(not fd_begin_async(entry), 'FD async reference overflow rejected'); + entry^.AsyncRefs := 0; + Assert(fd_table_begin_close(table), 'FD table teardown reserves all slots'); + Assert((fd_alloc(table) = 0) and not fd_begin_async(entry), 'FD closing table rejects new users'); + fd_table_free(table); + Assert(lmm_total_free = beforeFree, 'FD table strings and metadata released'); + field('FD_TESTS_PASSED', passed); + field('FD_TESTS_FAILED', failed); +end; + end. diff --git a/src/driver/storage/driver.storage.filedispatch.pas b/src/driver/storage/driver.storage.filedispatch.pas index 8c99a8ac..6c07f345 100644 --- a/src/driver/storage/driver.storage.filedispatch.pas +++ b/src/driver/storage/driver.storage.filedispatch.pas @@ -62,7 +62,7 @@ implementation uses driver.storage.vfs, driver.storage.types, memory.heap, core.strings, - core.strings.helpers, debug.tracer, io.syslog, core.util, arch.x86.util; + core.strings.helpers, debug.tracer, io.syslog, core.util, arch.platform.cpu; type THandlerEntry = record @@ -124,7 +124,7 @@ begin Handlers[i].Used := true; Handlers[i].Kind := hkMagic; Handlers[i].MagicLen := magicLen; - memcpy(uint32(magic), uint32(@Handlers[i].Magic[0]), magicLen); + memcpy(PtrUInt(magic), PtrUInt(@Handlers[i].Magic[0]), magicLen); copyName(@Handlers[i].Name[0], name, 16); Handlers[i].Handler := handler; inc(HandlerCount); @@ -149,7 +149,7 @@ begin if not Handlers[i].Used then begin Handlers[i].Used := true; Handlers[i].Kind := hkExtension; - memcpy(uint32(extList), uint32(@Handlers[i].ExtList[0]), len); + memcpy(PtrUInt(extList), PtrUInt(@Handlers[i].ExtList[0]), len); Handlers[i].ExtList[len] := #0; copyName(@Handlers[i].Name[0], name, 16); Handlers[i].Handler := handler; @@ -224,7 +224,7 @@ begin fh := driver.storage.vfs.OpenFile(absPath, omRead, @err); if fh = 0 then exit; - memset(uint32(@headerBuf[0]), 0, MAX_MAGIC_LEN); + memset(PtrUInt(@headerBuf[0]), 0, MAX_MAGIC_LEN); bytesRead := driver.storage.vfs.ReadFile(fh, 0, @headerBuf[0], MAX_MAGIC_LEN); driver.storage.vfs.CloseFile(fh); diff --git a/src/driver/storage/driver.storage.iorequest.pas b/src/driver/storage/driver.storage.iorequest.pas index 293b2e11..3464ef44 100644 --- a/src/driver/storage/driver.storage.iorequest.pas +++ b/src/driver/storage/driver.storage.iorequest.pas @@ -25,10 +25,14 @@ interface uses memory.heap, + arch.platform.cpu, + arch.platform.diagnostics, driver.storage.types; { Allocate a new TIORequest from the fixed request pool and initialise its core fields. Caller, State, Error and ByteCount are zeroed (caller fills as needed). } +procedure UnitTest; + function ioreq_alloc(reqType : TIORequestType; device : PStorage_Device; lba : uint32; @@ -47,7 +51,6 @@ implementation const IOREQ_POOL_CAPACITY = 512; - IOREQ_REQ_OFFSET = sizeof(pointer); type PIORequestPoolNode = ^TIORequestPoolNode; @@ -64,7 +67,7 @@ var procedure ioreq_pool_init(); var i : uint32; - base : uint32; + base : PtrUInt; node : PIORequestPoolNode; begin if ioreqPoolReady then exit; @@ -73,7 +76,7 @@ begin if ioreqPoolBuf = nil then exit; ioreqFreeList := nil; - base := uint32(ioreqPoolBuf); + base := PtrUInt(ioreqPoolBuf); if IOREQ_POOL_CAPACITY > 0 then for i := 0 to IOREQ_POOL_CAPACITY - 1 do begin node := PIORequestPoolNode(base + (i * sizeof(TIORequestPoolNode))); @@ -86,37 +89,31 @@ end; function ioreq_take() : PIORequest; var - node : PIORequestPoolNode; + node: PIORequestPoolNode; + flags: PtrUInt; begin - if not ioreqPoolReady then - ioreq_pool_init(); - if not ioreqPoolReady then begin - ioreq_take := nil; - exit; - end; - - asm pushf; cli end; + ioreq_take := nil; + flags := arch.platform.cpu.lock; + if not ioreqPoolReady then ioreq_pool_init; node := ioreqFreeList; - if node <> nil then + if node <> nil then begin ioreqFreeList := node^.Next; - asm popf end; - - if node = nil then - ioreq_take := nil - else ioreq_take := @node^.Req; + end; + arch.platform.cpu.unlock(flags); end; procedure ioreq_release(req : PIORequest); var - node : PIORequestPoolNode; + node: PIORequestPoolNode; + flags: PtrUInt; begin if req = nil then exit; - node := PIORequestPoolNode(uint32(req) - IOREQ_REQ_OFFSET); - asm pushf; cli end; + node := PIORequestPoolNode(PtrUInt(req) - PtrUInt(@PIORequestPoolNode(nil)^.Req)); + flags := arch.platform.cpu.lock; node^.Next := ioreqFreeList; ioreqFreeList := node; - asm popf end; + arch.platform.cpu.unlock(flags); end; function ioreq_alloc(reqType : TIORequestType; @@ -134,7 +131,8 @@ begin end; req^.MergeNext := nil; req^.OriginalSectors := sectors; - req^.QueuedTick := 0; req^.DispatchTick := 0; + req^.QueuedTick := 0; + req^.DispatchTick := 0; req^.RequestType := reqType; req^.State := iosPending; req^.Device := device; @@ -159,6 +157,8 @@ function ioreq_copy(src : PIORequest) : PIORequest; var dst : PIORequest; begin + ioreq_copy := nil; + if src = nil then exit; dst := ioreq_take(); if dst = nil then begin ioreq_copy := nil; @@ -166,7 +166,8 @@ begin end; dst^.MergeNext := nil; dst^.OriginalSectors := src^.SectorCount; - dst^.QueuedTick := 0; dst^.DispatchTick := 0; + dst^.QueuedTick := 0; + dst^.DispatchTick := 0; dst^.RequestType := src^.RequestType; dst^.State := src^.State; dst^.Device := src^.Device; @@ -182,4 +183,66 @@ begin ioreq_copy := dst; end; +procedure testCallback(error: TError; data: pointer); +begin + if error = eNone then inc(PUInt32(data)^); +end; + +procedure UnitTest; +var + head, req, next, copyReq: PIORequest; + payload: PUInt32; + count, passed, failed: uint32; + flags: PtrUInt; + + procedure Assert(condition: boolean); + begin + if condition then + inc(passed) + else + inc(failed); + end; +begin + passed := 0; + failed := 0; + flags := arch.platform.cpu.lock; + head := nil; + count := 0; + repeat + req := ioreq_alloc(ioRead, nil, count, 1, nil); + if req = nil then break; + req^.MergeNext := head; + head := req; + inc(count); + until count = IOREQ_POOL_CAPACITY; + Assert((count > 0) and (ioreq_alloc(ioRead, nil, 0, 1, nil) = nil)); + while head <> nil do begin + next := head^.MergeNext; + ioreq_free(head); + head := next; + end; + + payload := PUInt32(kalloc(sizeof(uint32))); + req := ioreq_alloc(ioRead, nil, 123, 2, payload); + if (payload <> nil) and (req <> nil) then begin + req^.Callback := @testCallback; + req^.CallbackData := payload; + copyReq := ioreq_copy(req); + if copyReq <> nil then begin + Assert((copyReq <> req) and (copyReq^.Buffer = payload) and + (copyReq^.LBA = 123) and (copyReq^.OriginalSectors = 2)); + copyReq^.Callback(eNone, copyReq^.CallbackData); + Assert(payload^ = 1); + ioreq_free(copyReq); + end else + inc(failed); + end else + inc(failed); + ioreq_free(req); + kfree(void(payload)); + Assert(ioreq_copy(nil) = nil); + arch.platform.cpu.unlock(flags); + testSummary('IOREQ', 'IOREQ_TESTS_PASSED', 'IOREQ_TESTS_FAILED', passed, failed); +end; + end. diff --git a/src/driver/storage/driver.storage.metatest.pas b/src/driver/storage/driver.storage.metatest.pas index 0b7a4ad8..b9dc24fd 100644 --- a/src/driver/storage/driver.storage.metatest.pas +++ b/src/driver/storage/driver.storage.metatest.pas @@ -1,11 +1,12 @@ unit driver.storage.metatest; interface +procedure RunTests(scheduled: boolean); procedure UnitTest; procedure init; implementation -uses boot.mgr, io.stdio, io.syslog, memory.heap, core.util, core.ds.cfifo, +uses driver.storage.vfs, driver.storage.fs.mgr, driver.storage.fs.fat32, arch.platform.diagnostics, io.stdio, io.syslog, memory.heap, core.util, core.ds.cfifo, driver.storage.types, driver.storage.mgr, driver.storage.vol.mbr, driver.storage.vol.mgr, driver.storage.fs.fat32.types, driver.storage.fs.fat32.vol, driver.storage.fs.fat32.labels, core.strings; @@ -39,8 +40,8 @@ begin if request^.RequestType = ioWrite then begin Inc(m^.Writes); if m^.Writes = m^.FailWrite then begin complete_io(request, false, eIOError); exit; end; - memcpy(uint32(request^.Buffer), uint32(m^.Data)+offset, size); - end else memcpy(uint32(m^.Data)+offset, uint32(request^.Buffer), size); + memcpy(PtrUInt(request^.Buffer), PtrUInt(m^.Data)+offset, size); + end else memcpy(PtrUInt(m^.Data)+offset, PtrUInt(request^.Buffer), size); request^.ByteCount := size; complete_io(request, true, eNone); end; @@ -51,7 +52,7 @@ begin m := PMock(data); Inc(m^.Callbacks); m^.Error := error; end; { Only heap-backed synthetic storage is used. Never registers a hardware device, never accesses a host/guest disk, and every fake request completes inline. } -procedure UnitTest; +procedure RunTests(scheduled: boolean); var device : PStorage_Device; volume, added : PStorage_Volume; @@ -67,6 +68,80 @@ var begin if ok then Inc(passed) else begin Inc(failed); io.syslog.logln('STORMETA FAIL', name); end; end; + procedure FileRoundTrip(name: pchar); + var + fs, sourceFS: PFilesystem; + handle: TFileHandle; + error: TError; + data: puint8; + j: uint32; + matches: boolean; + begin + sourceFS := find_filesystem_by_name(name); + Assert(sourceFS <> nil, 'round-trip filesystem registered'); + if sourceFS = nil then exit; + fs := PFilesystem(kalloc(sizeof(TFilesystem))); + Assert(fs <> nil, 'round-trip filesystem descriptor'); + if fs = nil then exit; + memcpy(PtrUInt(sourceFS), PtrUInt(fs), sizeof(TFilesystem)); + { Early boot has no scheduler yet. Exercise the synchronous hooks on + a private descriptor; scheduled runs retain the normal async hooks. } + if not scheduled then begin + fs^.readOffsetAsyncCallback := nil; + fs^.writeOffsetAsyncCallback := nil; + end; + FAT32ReleaseVolumeState(volume); + memset(PtrUInt(volume), 0, sizeof(TStorage_Volume)); + volume^.device := device; + volume^.sectorSize := 512; + volume^.sectorCount := 4096; + memset(PtrUInt(mock^.Data), 0, 4096 * 512); + volume^.filesystem := fs; + params.Flags := FS_FORMAT_PARAM_CLUSTER_SIZE; + params.ClusterSize := 2; + error := fs^.createCallback(volume, 4096, 0, @params); + Assert(error = eNone, 'round-trip format'); + if error <> eNone then begin + kfree(void(fs)); + exit; + end; + Assert(fs^.identifyCallback(volume), 'round-trip identify'); + if mountVolume('/__storage_roundtrip__', volume) <> pvRegistered then begin + Assert(false, 'round-trip mount'); + InvalidateVolume(volume); + kfree(void(fs)); + exit; + end; + data := puint8(kalloc(8192)); + Assert(data <> nil, 'round-trip buffer'); + if data <> nil then begin + for j := 0 to 8191 do data[j] := (j * 17 + 3) and $FF; + handle := OpenFile('/__storage_roundtrip__/ROUND.BIN', omCreate, @error); + Assert(error = eNone, 'round-trip create'); + if error = eNone then begin + Assert(WriteFile(handle, 0, data, 8192) = 8192, 'write across filesystem blocks'); + Assert(CloseFile(handle) = eNone, 'close written file'); + handle := OpenFile('/__storage_roundtrip__/ROUND.BIN', omRead, @error); + Assert(error = eNone, 'reopen written file'); + if error = eNone then begin + memset(PtrUInt(data), 0, 8192); + Assert(ReadFile(handle, 37, data, 4096) = 4096, 'unaligned cross-block read'); + matches := true; + for j := 0 to 4095 do + if data[j] <> (((j + 37) * 17 + 3) and $FF) then matches := false; + Assert(matches, 'read-back bytes match written pattern'); + Assert(FileSizeFromHandle(handle) = 8192, 'reopened file size persists'); + Assert(CloseFile(handle) = eNone, 'close read file'); + end; + end; + kfree(void(data)); + end; + Assert(InvalidateVolume(volume), 'round-trip volume released'); + RemoveVirtualTree('/__storage_roundtrip__'); + volume^.filesystem := nil; + kfree(void(fs)); + io.syslog.logln('STORMETA round-trip', name); + end; begin passed := 0; failed := 0; device := PStorage_Device(kalloc(sizeof(TStorage_Device))); @@ -80,12 +155,12 @@ begin if br <> nil then kfree(void(br)); io.syslog.logln('STORMETA FAIL', 'test allocation'); exit; end; - memset(uint32(device),0,sizeof(TStorage_Device)); - memset(uint32(volume),0,sizeof(TStorage_Volume)); - memset(uint32(mock),0,sizeof(TMock)); memset(uint32(br),0,sizeof(TBootRecord)); + memset(PtrUInt(device),0,sizeof(TStorage_Device)); + memset(PtrUInt(volume),0,sizeof(TStorage_Volume)); + memset(PtrUInt(mock),0,sizeof(TMock)); memset(PtrUInt(br),0,sizeof(TBootRecord)); mock^.Data := puint8(kalloc(256*512)); - memset(uint32(mock^.Data),$CC,256*512); - device^.controllerId0 := uint32(mock); device^.sectorSize := 512; + memset(PtrUInt(mock^.Data),$CC,256*512); + device^.controllerId0 := PtrUInt(mock); device^.sectorSize := 512; device^.maxSectorCount := 256; device^.writable := true; device^.maxActive := 1; device^.dispatchRead := @dispatch; device^.dispatchWrite := @dispatch; device^.requestQueue := CFIFO_New(sizeof(Pointer),8); @@ -101,7 +176,7 @@ begin mock^.FailRead := false; mock^.Callbacks := 0; device^.cachedMBR := kalloc(512); mbr := PMaster_Boot_Record(device^.cachedMBR); - memset(uint32(mbr),0,512); mbr^.boot_sector := $AA55; mbr^.signature := $12345678; + memset(PtrUInt(mbr),0,512); mbr^.boot_sector := $AA55; mbr^.signature := $12345678; Assert(write_mbr(device,mbr) = eNone,'aliased MBR write succeeds'); Assert(PMaster_Boot_Record(mock^.Data)^.signature = $12345678,'aliased MBR bytes preserved'); before := PMaster_Boot_Record(device^.cachedMBR); @@ -119,7 +194,7 @@ begin Assert(device^.cachedMBR = before,'async failure retains cache'); mock^.FailWrite := 0; volume^.device := device; volume^.sectorStart := 64; volume^.sectorCount := 128; volume^.sectorSize := 512; - memset(uint32(@params),0,sizeof(params)); params.Version := 1; + memset(PtrUInt(@params),0,sizeof(params)); params.Version := 1; params.Flags := FS_FORMAT_PARAM_CLUSTER_SIZE; params.ClusterSize := 1; Assert(validate_format(volume,128,64,@params) = eNone,'valid format geometry'); params.ClusterSize := 3; @@ -136,7 +211,7 @@ begin Assert(create_volume(volume,128,64,@params) = eNone,'format completes'); Assert(not mock^.OutOfBounds,'all writes within device'); Assert((mock^.Data[63*512] = $CC) and (mock^.Data[192*512] = $CC),'neighbor partitions untouched'); - memcpy(uint32(mock^.Data)+64*512,uint32(br),sizeof(TBootRecord)); + memcpy(PtrUInt(mock^.Data)+64*512,PtrUInt(br),sizeof(TBootRecord)); Assert(FAT32ClusterLimit(br) = 97,'cluster bound excludes FAT padding'); Assert(identify_volume(volume),'fresh format identified'); Assert(volume^.freeSectors = 93,'free space excludes metadata and allocated directories'); @@ -154,10 +229,10 @@ begin volume^.labelText[0] := #0; Assert(read_label(volume) = eNone,'reload label from disk'); Assert(stringEquals(@volume^.labelText[0],'PROJECTS'),'label survives cache reset'); - Assert(PDirectory(uint32(mock^.Data)+97*512)^.fileName[0] = 'S','rename preserves SYSTEM directory'); + Assert(PDirectory(PtrUInt(mock^.Data)+97*512)^.fileName[0] = 'S','rename preserves SYSTEM directory'); { Add a valid backup boot sector and verify all three metadata writes. } - PBootRecord(uint32(mock^.Data)+64*512)^.backupCluster := 6; - memcpy(uint32(mock^.Data)+64*512,uint32(mock^.Data)+70*512,512); + PBootRecord(PtrUInt(mock^.Data)+64*512)^.backupCluster := 6; + memcpy(PtrUInt(mock^.Data)+64*512,PtrUInt(mock^.Data)+70*512,512); for i := 1 to 3 do begin mock^.FailWrite := mock^.Writes+i; Assert(write_label(volume,'NEW NAME') = eIOError,'label write failure propagated'); @@ -166,20 +241,20 @@ begin Assert(stringEquals(@volume^.labelText[0],'PROJECTS'),'failed rename retains previous label'); end; Assert(write_label(volume,'ABCDEFGHIJK') = eNone,'maximum length label'); - Assert(PBootRecord(uint32(mock^.Data)+70*512)^.volumeLabel[10] = ord('K'),'backup label updated'); + Assert(PBootRecord(PtrUInt(mock^.Data)+70*512)^.volumeLabel[10] = ord('K'),'backup label updated'); Assert(write_label(volume,'') = eNone,'clear label'); volume^.labelText[0] := 'X'; Assert(read_label(volume) = eNone,'reload cleared label'); Assert(volume^.labelText[0] = #0,'blank label persists'); params.Flags := params.Flags or FS_FORMAT_PARAM_LABEL; - memcpy(uint32(pchar('MY DATA')),uint32(@params.LabelText[0]),8); + memcpy(PtrUInt(pchar('MY DATA')),PtrUInt(@params.LabelText[0]),8); Assert(create_volume(volume,128,64,@params) = eNone,'format with label'); Assert(read_label(volume) = eNone,'read format label'); Assert(stringEquals(@volume^.labelText[0],'MY DATA'),'format label persists'); - Assert(PDirectory(uint32(mock^.Data)+97*512)^.attributes = $08,'format creates root volume label entry'); + Assert(PDirectory(PtrUInt(mock^.Data)+97*512)^.attributes = $08,'format creates root volume label entry'); Assert(not mock^.OutOfBounds,'label operations stay inside volume'); oldCount := get_volume_count(); - memset(uint32(@part),0,sizeof(part)); part.LBA_start := 64; part.sector_count := 128; + memset(PtrUInt(@part),0,sizeof(part)); part.LBA_start := 64; part.sector_count := 128; mock^.FailWrite := mock^.Writes+1; Assert(add_partition(device,0,part) = eIOError,'failed partition creation reported'); Assert(get_volume_count() = oldCount,'failed create publishes no volume'); @@ -205,7 +280,7 @@ begin kfree(void(mock^.Data)); mock^.Data := puint8(kalloc(4096*512)); if mock^.Data <> nil then begin - memset(uint32(mock^.Data),0,1500*512); + memset(PtrUInt(mock^.Data),0,1500*512); device^.maxSectorCount := 1500; volume^.sectorStart := 0; volume^.sectorCount := 1500; Assert(create_volume(volume,1500,0,@params) = eNone,'format multi-batch FAT fixture'); @@ -224,34 +299,40 @@ begin Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'missing FSInfo reports unknown free space'); Assert(mock^.MaxReadSectors = 1,'large volume discovery uses only metadata sector reads'); PBootRecord(mock^.Data)^.FSInfoCluster := 1; - puint32(uint32(mock^.Data)+512)^ := $41615252; - puint32(uint32(mock^.Data)+512+484)^ := $61417272; - puint32(uint32(mock^.Data)+512+488)^ := 100; - puint32(uint32(mock^.Data)+512+508)^ := $AA550000; + puint32(PtrUInt(mock^.Data)+512)^ := $41615252; + puint32(PtrUInt(mock^.Data)+512+484)^ := $61417272; + puint32(PtrUInt(mock^.Data)+512+488)^ := 100; + puint32(PtrUInt(mock^.Data)+512+508)^ := $AA550000; Assert(identify_volume(volume),'valid FSInfo avoids FAT scan'); Assert((volume^.freeSectors = 100) and ((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) = 0),'FSInfo count displayed'); - puint32(uint32(mock^.Data)+512+488)^ := $FFFFFFFF; + puint32(PtrUInt(mock^.Data)+512+488)^ := $FFFFFFFF; Assert(identify_volume(volume),'unknown FSInfo count does not prevent mount'); Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'unknown FSInfo count stays unknown'); - puint32(uint32(mock^.Data)+512+488)^ := 4096; + puint32(PtrUInt(mock^.Data)+512+488)^ := 4096; Assert(identify_volume(volume),'out-of-range FSInfo count does not prevent mount'); Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'out-of-range FSInfo count rejected'); - puint32(uint32(mock^.Data)+512+488)^ := 0; + puint32(PtrUInt(mock^.Data)+512+488)^ := 0; Assert(identify_volume(volume),'full volume FSInfo accepted'); Assert((volume^.freeSectors = 0) and ((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) = 0),'known zero differs from unknown'); - puint32(uint32(mock^.Data)+512+508)^ := 0; + puint32(PtrUInt(mock^.Data)+512+508)^ := 0; Assert(identify_volume(volume),'invalid FSInfo signature does not prevent mount'); Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'invalid FSInfo signature rejected'); mock^.FailReadLBA := 1; Assert(identify_volume(volume),'optional FSInfo read failure does not prevent mount'); mock^.FailReadLBA := 0; + FileRoundTrip('FAT32'); + FileRoundTrip('EXT2'); end else Assert(false,'allocate multi-batch FAT fixture'); kfree(device^.cachedMBR); CFIFO_Free(device^.requestQueue); kfree(void(mock^.Data)); kfree(void(mock)); kfree(void(volume)); kfree(void(device)); kfree(void(br)); - io.syslog.log('STORMETA','Passed: '); io.syslog.writeint(passed); - io.syslog.writestring(' Failed: '); io.syslog.writeintln(failed); + arch.platform.diagnostics.testSummary('STORMETA', 'STORMETA_TESTS_PASSED', 'STORMETA_TESTS_FAILED', passed, failed); +end; + +procedure UnitTest; +begin + RunTests(false); end; procedure command(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); @@ -263,6 +344,4 @@ end; procedure init; begin io.stdio.registerCommand('STORMETA',@command,'Run memory-backed storage metadata regression tests'); end; -initialization - boot.mgr.registerBoot('driver.storage.metatest',@init,'Storage Metadata Tests',BOOT_MGR_BARRIER_MID); end. diff --git a/src/driver/storage/driver.storage.mgr.pas b/src/driver/storage/driver.storage.mgr.pas index 1d713084..a93762f7 100644 --- a/src/driver/storage/driver.storage.mgr.pas +++ b/src/driver/storage/driver.storage.mgr.pas @@ -1,4 +1,4 @@ -// Copyright 2021 Aaron Hance +// Copyright 2021 Aaron Hance // Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,8 +36,7 @@ unit driver.storage.mgr; interface uses - arch.x86.bda, - boot.mgr, + arch.platform.process, core.ds.cfifo, core.ds.types, driver.storage.iorequest, @@ -49,7 +48,7 @@ uses driver.storage.types, io.syslog, debug.tracer, - core.util, arch.x86.util; + core.util, arch.platform.cpu; var storageDevices : PDList; @@ -122,6 +121,8 @@ uses procedure dispatch_next(device : PStorage_Device); forward; type + PDeviceSlot = ^PStorage_Device; + PRequestSlot = ^PIORequest; { Used by submit_io_wait: callback writes result here then sets Done=1. Process is parked in psAwaiting while Done=0; ISR sets psReady + Done=1. Done is read through a pointer so FPC cannot cache it (Lesson #6). } @@ -174,18 +175,20 @@ begin buf := puint32(kalloc(bufSz)); if buf = nil then exit; - memset(uint32(buf), 0, bufSz); - memcpy(uint32(mbr), uint32(buf), sizeof(TMaster_Boot_Record)); + memset(PtrUInt(buf), 0, bufSz); + memcpy(PtrUInt(mbr), PtrUInt(buf), sizeof(TMaster_Boot_Record)); alloc_mbr_io_buffer := buf; end; function claim_mbr(device : PStorage_Device) : boolean; -var flags : uint32; +var + irqFlags: PtrUInt; + flags : uint32; begin - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; claim_mbr := not device^.mbrWriteBusy; if claim_mbr then device^.mbrWriteBusy := true; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); end; procedure publish_mbr(device : PStorage_Device; buffer : pointer); @@ -210,17 +213,19 @@ begin end; procedure storage_get_stats(device : PStorage_Device; var stats : TStorageStats); +var + savedIRQ: PtrUInt; begin - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if device <> nil then stats := device^.Stats - else memset(uint32(@stats),0,sizeof(stats)); - asm popf end; + else memset(PtrUInt(@stats),0,sizeof(stats)); + arch.platform.cpu.unlock(savedIRQ); end; procedure init(); begin push_trace('driver.storage.mgr.init'); - storageDevices := DL_New(sizeof(TStorage_Device)); + storageDevices := DL_New(sizeof(PStorage_Device)); nextDeviceId := 0; bootDriveByte := $FF; { $FF = unknown/uninitialised } hdCount := 0; @@ -242,15 +247,24 @@ end; procedure register_device(device : PStorage_Device); var storedDev : PStorage_Device; + slot : PDeviceSlot; + irqFlags : PtrUInt; begin push_trace('driver.storage.mgr.register_device'); - device^.id := nextDeviceId; - nextDeviceId := nextDeviceId + 1; + if device = nil then exit; + storedDev := PStorage_Device(kalloc(sizeof(TStorage_Device))); + if storedDev = nil then exit; + memcpy(PtrUInt(device), PtrUInt(storedDev), sizeof(TStorage_Device)); + device := storedDev; { Initialise new Phase 2 fields } device^.removed := false; device^.requestQueue := CFIFO_New(SizeOf(PIORequest), 128); - memset(uint32(@device^.Stats),0,sizeof(TStorageStats)); + if device^.requestQueue = nil then begin + kfree(void(device)); + exit; + end; + memset(PtrUInt(@device^.Stats),0,sizeof(TStorageStats)); device^.activeCount := 0; device^.dispatching := false; if device^.maxActive = 0 then device^.maxActive := 1; @@ -261,11 +275,20 @@ begin { dispatchRead/dispatchWrite: left as-is — driver sets them before calling register_device. memset(0) guarantees nil for unmigrated drivers. } - DL_Add(storageDevices); - DL_Set(storageDevices, DL_Size(storageDevices) - 1, puint32(device)); - - { Get pointer to the DList copy so volumes reference the canonical device } - storedDev := PStorage_Device(DL_Get(storageDevices, DL_Size(storageDevices) - 1)); + { Volumes and queued requests retain this address across registry growth. } + irqFlags := arch.platform.cpu.lock; + slot := PDeviceSlot(DL_Add(storageDevices)); + if slot <> nil then begin + storedDev^.id := nextDeviceId; + inc(nextDeviceId); + slot^ := storedDev; + end; + arch.platform.cpu.unlock(irqFlags); + if slot = nil then begin + CFIFO_Free(device^.requestQueue); + kfree(void(device)); + exit; + end; { Identify whether this is the boot device using the BIOS drive byte. BIOS numbers HDDs from $80 upward; the nth HDD is $80 + (n-1). @@ -308,11 +331,15 @@ begin end; function get_device(index : uint32) : PStorage_Device; +var + irqFlags : PtrUInt; begin + irqFlags := arch.platform.cpu.lock; if index < DL_Size(storageDevices) then - get_device := PStorage_Device(DL_Get(storageDevices, index)) + get_device := PDeviceSlot(DL_Get(storageDevices, index))^ else get_device := nil; + arch.platform.cpu.unlock(irqFlags); end; function controller_type_2_string(controllerType : TControllerType) : pchar; @@ -355,7 +382,7 @@ begin kfree(device^.cachedMBR); cache := puint32(kalloc(sizeof(TMaster_Boot_Record))); if cache <> nil then - memcpy(uint32(buf), uint32(cache), sizeof(TMaster_Boot_Record)); + memcpy(PtrUInt(buf), PtrUInt(cache), sizeof(TMaster_Boot_Record)); device^.cachedMBR := pointer(cache); end; end; @@ -440,7 +467,7 @@ begin (device^.sectorSize > 0) do begin peek := CFIFO_Peek(device^.requestQueue); if peek = nil then break; - nextReq := PIORequest(puint32(peek)^); + nextReq := PRequestSlot(peek)^; if nextReq = nil then break; if (nextReq^.Device <> device) or (nextReq^.RequestType <> heapReq^.RequestType) then break; if heapReq^.SectorCount > device^.mergeMaxSectors then break; @@ -448,15 +475,15 @@ begin if heapReq^.SectorCount > ($FFFFFFFF div device^.sectorSize) then break; bytes := heapReq^.SectorCount*device^.sectorSize; if (heapReq^.LBA > $FFFFFFFF-heapReq^.SectorCount) or - (uint32(heapReq^.Buffer) > $FFFFFFFF-bytes) then break; + (PtrUInt(heapReq^.Buffer) > High(PtrUInt)-bytes) then break; if (nextReq^.LBA <> heapReq^.LBA+heapReq^.SectorCount) or - (uint32(nextReq^.Buffer) <> uint32(heapReq^.Buffer)+bytes) then break; + (PtrUInt(nextReq^.Buffer) <> PtrUInt(heapReq^.Buffer)+bytes) then break; if not CFIFO_Dequeue(device^.requestQueue,@nextReq) then break; tail^.MergeNext := nextReq; tail := nextReq; Inc(heapReq^.SectorCount,nextReq^.SectorCount); Inc(merged); Inc(device^.Stats.Merged); end; - ticks := puint32(@Counters.c32)^; + ticks := arch.platform.process.timerTicks; tail := heapReq; while tail <> nil do begin tail^.DispatchTick := ticks; tail^.State := iosDispatched; @@ -490,6 +517,8 @@ end; function submit_io(request : PIORequest) : TError; var + irqFlags: PtrUInt; + device : PStorage_Device; heapReq : PIORequest; flags : uint32; @@ -523,15 +552,15 @@ begin { Enqueue and attempt dispatch — must be ISR-safe and bounded. Reject once the fixed queue is full instead of growing with CLI held. } - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; if CFIFO_Size(device^.requestQueue) >= CFIFO_Capacity(device^.requestQueue) then begin Inc(device^.Stats.QueueFull); - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); ioreq_free(heapReq); submit_io := eQueueFull; exit; end; - heapReq^.QueuedTick := puint32(@Counters.c32)^; + heapReq^.QueuedTick := arch.platform.process.timerTicks; Inc(device^.Stats.Submitted); if request^.SectorCount <= 8 then Inc(device^.Stats.SmallRequests) else if request^.SectorCount <= 256 then Inc(device^.Stats.MediumRequests) @@ -540,7 +569,7 @@ begin if CFIFO_Size(device^.requestQueue) > device^.Stats.QueuePeak then device^.Stats.QueuePeak := CFIFO_Size(device^.requestQueue); dispatch_next(device); - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); submit_io := eNone; end; @@ -554,6 +583,8 @@ end; register across the hlt instruction). } function submit_io_wait(request : PIORequest) : TError; var + savedIRQ: PtrUInt; + wait : TSyncWaitData; err : TError; begin @@ -576,36 +607,38 @@ begin psAwaiting so the callback cannot fire in the narrow window between the Done=0 check and the state write — which would otherwise lose the wake signal and leave the process parked forever. } - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if puint32(@wait.Done)^ = 0 then begin if CurrentProcess <> nil then CurrentProcess^.State := psAwaiting; end; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); { Spin on Done via pointer — mandatory per Lesson #6. FPC would cache the value in a register without the indirection, causing an infinite loop even after the ISR sets Done=1. HLT yields the CPU each iteration so we do not burn cycles; the next interrupt (driver.storage.ctl.ahci or timer) wakes us. } while puint32(@wait.Done)^ = 0 do - asm hlt end; + arch.platform.cpu.idle; submit_io_wait := wait.Error; end; procedure complete_io(request : PIORequest; success : boolean; error : TError); -var device : PStorage_Device; next : PIORequest; ticks, flags : uint32; +var + irqFlags: PtrUInt; + device : PStorage_Device; next : PIORequest; ticks, flags : uint32; wasDispatching : boolean; begin if request = nil then exit; device := request^.Device; - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; wasDispatching := false; if device <> nil then begin wasDispatching := device^.dispatching; device^.dispatching := true; { callbacks cannot dispatch inside merged fan-out } if device^.activeCount > 0 then Dec(device^.activeCount); - ticks := puint32(@Counters.c32)^-request^.DispatchTick; + ticks := arch.platform.process.timerTicks-request^.DispatchTick; Inc(device^.Stats.ServiceTicks,ticks); if ticks > device^.Stats.MaxServiceTicks then device^.Stats.MaxServiceTicks := ticks; end; @@ -626,7 +659,7 @@ begin device^.dispatching := wasDispatching; dispatch_next(device); end; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); end; { ========================================================================== } @@ -739,7 +772,4 @@ begin storage_write_async := submit_io(@req); end; -initialization - boot.mgr.registerBoot('driver.storage.mgr', @init, 'Storage Manager', 'driver.storage.vfs'); - end. diff --git a/src/driver/storage/driver.storage.path.pas b/src/driver/storage/driver.storage.path.pas new file mode 100644 index 00000000..939b8bf0 --- /dev/null +++ b/src/driver/storage/driver.storage.path.pas @@ -0,0 +1,140 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Path syntax shared by VFS and boot consumers; no volume or driver access. + Returned strings are caller-owned. Normalization is lexical, not symlink + resolution, and never climbs above the root. } +unit driver.storage.path; + +interface + +function absolute(path, baseDir: pchar): pchar; +function normalize(path: pchar): pchar; +procedure UnitTest; + +implementation + +uses core.strings, core.util, memory.heap, arch.platform.diagnostics; + +function absolute(path, baseDir: pchar): pchar; +var + pathLen, baseLen, separator: uint32; + result: pchar; +begin + absolute := nil; + if path = nil then begin + absolute := stringCopy('/'); + exit; + end; + if path[0] = '/' then begin + absolute := stringCopy(path); + exit; + end; + if (baseDir = nil) or (baseDir[0] = #0) then baseDir := '/'; + pathLen := StringSize(path); + baseLen := StringSize(baseDir); + separator := 0; + if baseDir[baseLen - 1] <> '/' then separator := 1; + if pathLen >= High(uint32) - separator then exit; + if baseLen >= High(uint32) - pathLen - separator then exit; + + result := stringNew(baseLen + separator + pathLen); + if result = nil then exit; + memcpy(PtrUInt(baseDir), PtrUInt(result), baseLen); + if separator <> 0 then result[baseLen] := '/'; + memcpy(PtrUInt(path), PtrUInt(result) + baseLen + separator, pathLen); + absolute := result; +end; + +function normalize(path: pchar): pchar; +var + length, readPos, start, count, used: uint32; + result: pchar; +begin + normalize := nil; + if path = nil then path := ''; + length := StringSize(path); + if length >= High(uint32) - 1 then exit; + result := stringNew(length + 1); + if result = nil then exit; + result[0] := '/'; + used := 1; + readPos := 0; + + while readPos < length do begin + if path[readPos] = '/' then begin + inc(readPos); + continue; + end; + start := readPos; + while (readPos < length) and (path[readPos] <> '/') do inc(readPos); + count := readPos - start; + if (count = 1) and (path[start] = '.') then continue; + if (count = 2) and (path[start] = '.') and (path[start + 1] = '.') then begin + while (used > 1) and (result[used - 1] <> '/') do dec(used); + if used > 1 then dec(used); + end else begin + if used > 1 then begin + result[used] := '/'; + inc(used); + end; + memcpy(PtrUInt(path) + start, PtrUInt(result) + used, count); + inc(used, count); + end; + end; + result[used] := #0; + normalize := result; +end; + +procedure UnitTest; +var + passed, failed: uint32; + beforeFree: SizeUInt; + + procedure Check(actual, expected: pchar); + begin + if StringEquals(actual, expected) then + inc(passed) + else begin + inc(failed); + log('PATH', 'FAIL: path result'); + end; + kfree(void(actual)); + end; +begin + passed := 0; + failed := 0; + beforeFree := lmm_total_free; + Check(absolute('file', '/disk'), '/disk/file'); + Check(absolute('file', '/disk/'), '/disk/file'); + Check(absolute('/file', '/disk'), '/file'); + Check(absolute('file', ''), '/file'); + Check(absolute('file', nil), '/file'); + Check(absolute(nil, '/disk'), '/'); + Check(absolute('', '/disk'), '/disk/'); + Check(normalize('/a//b/./c/../..'), '/a'); + Check(normalize('../../a/..'), '/'); + Check(normalize('/a/../b/../c'), '/c'); + Check(normalize('a/b/'), '/a/b'); + Check(normalize('/.../.hidden'), '/.../.hidden'); + Check(normalize(''), '/'); + Check(normalize(nil), '/'); + if lmm_total_free = beforeFree then + inc(passed) + else begin + inc(failed); + log('PATH', 'FAIL: path allocation accounting'); + end; + testSummary('PATH', 'PATH_TESTS_PASSED', 'PATH_TESTS_FAILED', passed, failed); +end; + +end. diff --git a/src/driver/storage/driver.storage.types.pas b/src/driver/storage/driver.storage.types.pas index 283b07e3..73a40662 100644 --- a/src/driver/storage/driver.storage.types.pas +++ b/src/driver/storage/driver.storage.types.pas @@ -1,4 +1,5 @@ // Copyright 2021 Aaron Hance +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +24,7 @@ interface uses core.ds.types, - core.ds.lists; + core.ds.dynamic; type @@ -274,7 +275,7 @@ type TStorage_Device = record id : uint8; controller : TControllerType; - controllerId0 : uint32; + controllerId0 : PtrUInt; writable : boolean; removed : boolean; { true if device was hot-unplugged } volumes : PDList; diff --git a/src/driver/storage/driver.storage.vfs.pas b/src/driver/storage/driver.storage.vfs.pas index 54a8682a..2df686b2 100644 --- a/src/driver/storage/driver.storage.vfs.pas +++ b/src/driver/storage/driver.storage.vfs.pas @@ -24,8 +24,9 @@ unit driver.storage.vfs; interface uses - boot.mgr, + arch.platform.diagnostics, driver.storage.fdtable, + driver.storage.path, core.ds.hashmap, core.ds.lists, memory.heap, @@ -157,7 +158,7 @@ uses proc.mgr, proc.types, io.stdio, - core.util, arch.x86.util, + core.util, arch.platform.cpu, driver.storage.vol.mgr; { ===================== Implementation constants ============================ @@ -649,7 +650,7 @@ begin vfsParentDir[0] := '/'; end else begin vfsParentDir := stringNew(last); - core.util.memcpy(uint32(abs), uint32(vfsParentDir), last); + core.util.memcpy(PtrUInt(abs), PtrUInt(vfsParentDir), last); end; kfree(void(abs)); end; @@ -732,36 +733,6 @@ begin debug.tracer.push_trace('driver.storage.vfs.CombineToAbsolutePath.exit'); end; -function evaluatePath(Path : pchar) : pchar; -var - List : PLinkedListBase; - i : uint32; - elm : pchar; - -begin - debug.tracer.push_trace('driver.storage.vfs.evaluatePath.enter'); - List:= STRLL_FromString(Path, '/'); - { Forward-scan with stack semantics: safe for consecutive '..' sequences } - i := 0; - while i < STRLL_Size(List) do begin - elm := STRLL_Get(List, i); - if (elm <> nil) and StringEquals(elm, '..') then begin - STRLL_Delete(List, i); { remove '..' } - if i > 0 then begin - STRLL_Delete(List, i - 1); { remove preceding segment } - if i > 0 then i := i - 1; { rewind } - end; - end else if (elm <> nil) and StringEquals(elm, '.') then begin - STRLL_Delete(List, i); { remove '.' — index stays } - end else begin - i := i + 1; - end; - end; - evaluatePath:= CombineToAbsolutePath(List, STRLL_Size(List)); - STRLL_Free(List); - debug.tracer.push_trace('driver.storage.vfs.evaluatePath.exit'); -end; - function getAbsolutePath(Obj : PVFSObject) : pchar; var buf, new, old : pchar; @@ -796,38 +767,13 @@ end; function MakeAbsolutePath(Path : PChar) : pchar; var - AbsPath : pchar; - TempPath : pchar; - cwd : pchar; - + cwd: pchar; begin - debug.tracer.push_trace('driver.storage.vfs.MakeAbsolutePath.enter'); - if (Path = nil) or (Path[0] = char(0)) then begin - MakeAbsolutePath := stringNew(1); - MakeAbsolutePath[0] := '/'; - debug.tracer.push_trace('driver.storage.vfs.MakeAbsolutePath.exit'); - exit; - end; - if Path[0] = '/' then AbsPath:= stringCopy(Path) else begin - { Get per-process working directory } - cwd := nil; - if (proc.mgr.CurrentProcess <> nil) and - (proc.mgr.CurrentProcess^.Cwd <> nil) then - cwd := proc.mgr.CurrentProcess^.Cwd; - if (cwd = nil) or (StringSize(cwd) = 0) then begin - TempPath := stringNew(1); - TempPath[0] := '/'; - end else begin - if cwd[StringSize(cwd)-1] <> '/' then - TempPath:= StringConcat(cwd, '/') - else - TempPath:= stringCopy(cwd); - end; - AbsPath:= StringConcat(TempPath, Path); - kfree(void(TempPath)); - end; - MakeAbsolutePath:= AbsPath; - debug.tracer.push_trace('driver.storage.vfs.MakeAbsolutePath.exit'); + cwd := nil; + if proc.mgr.CurrentProcess <> nil then + cwd := proc.mgr.CurrentProcess^.Cwd; + if (Path <> nil) and (Path[0] = #0) then Path := nil; + MakeAbsolutePath := driver.storage.path.absolute(Path, cwd); end; function GetObjectFromPathEx(path : pchar; var volRelPath : pchar) : PVFSObject; @@ -1278,7 +1224,7 @@ begin { Make absolute and evaluate . / .. } AbsPath := MakeAbsolutePath(FullPath); - EvalPath := evaluatePath(AbsPath); + EvalPath := driver.storage.path.normalize(AbsPath); kfree(void(AbsPath)); { Resolve path through VFS (follows symlinks), get remaining in-volume path } @@ -1484,10 +1430,12 @@ end; check Done with interrupts masked before parking to avoid a lost wakeup. } function TransferFileSync(FileHandle : TFileHandle; Position : uint32; Buffer : puint8; Length : uint32; Writing : boolean) : uint32; -var wait : TVFSSyncWaitCtx; +var + savedIRQ: PtrUInt; + wait : TVFSSyncWaitCtx; begin TransferFileSync := 0; - memset(uint32(@wait), 0, sizeof(wait)); + memset(PtrUInt(@wait), 0, sizeof(wait)); wait.Error := eUnknown; wait.Process := CurrentProcess; if Writing then @@ -1495,11 +1443,11 @@ begin else ReadFileAsync(FileHandle, Position, Buffer, Length, @wait.BytesDone, @vfs_sync_complete, @wait); - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if puint32(@wait.Done)^ = 0 then if wait.Process <> nil then wait.Process^.State := psAwaiting; - asm popf end; - while puint32(@wait.Done)^ = 0 do asm hlt end; + arch.platform.cpu.unlock(savedIRQ); + while puint32(@wait.Done)^ = 0 do arch.platform.cpu.idle; if wait.Error = eNone then TransferFileSync := wait.BytesDone; if Writing and (wait.Error <> eNone) then begin io.syslog.log('VFS', 'Write failed: error='); io.syslog.writeint(ord(wait.Error)); @@ -1623,7 +1571,7 @@ begin debug.tracer.push_trace('driver.storage.vfs.WriteFileAsync.exit'); exit; end; - memset(uint32(ctx), 0, sizeof(TVFSWriteAsyncCtx)); + memset(PtrUInt(ctx), 0, sizeof(TVFSWriteAsyncCtx)); ctx^.FD := fd; ctx^.Position := Position; ctx^.BytesWrittenPtr := BytesWritten; @@ -1923,7 +1871,7 @@ var begin debug.tracer.push_trace('driver.storage.vfs.ChangeDirectory.enter'); TempPath:= MakeAbsolutePath(Path); - AbsPath:= evaluatePath(TempPath); + AbsPath:= driver.storage.path.normalize(TempPath); kfree(void(TempPath)); Validity:= PathValid(AbsPath); if (Validity = pvDirectory) then begin @@ -2107,21 +2055,8 @@ begin end; function MakeAbsolutePathFrom(Path : pchar; BaseDir : pchar) : pchar; -var - AbsPath : pchar; - TempPath : pchar; begin - if Path[0] = '/' then - AbsPath := stringCopy(Path) - else begin - if BaseDir[StringSize(BaseDir)-1] <> '/' then - TempPath := StringConcat(BaseDir, '/') - else - TempPath := stringCopy(BaseDir); - AbsPath := StringConcat(TempPath, Path); - kfree(void(TempPath)); - end; - MakeAbsolutePathFrom := AbsPath; + MakeAbsolutePathFrom := driver.storage.path.absolute(Path, BaseDir); end; function ResolvePathFrom(Path : pchar; BaseDir : pchar) : TIsPathValid; @@ -2130,7 +2065,7 @@ var AbsPath : pchar; begin TempPath := MakeAbsolutePathFrom(Path, BaseDir); - AbsPath := evaluatePath(TempPath); + AbsPath := driver.storage.path.normalize(TempPath); kfree(void(TempPath)); ResolvePathFrom := PathValid(AbsPath); kfree(void(AbsPath)); @@ -2142,7 +2077,7 @@ var AbsPath : pchar; begin TempPath := MakeAbsolutePathFrom(Path, BaseDir); - AbsPath := evaluatePath(TempPath); + AbsPath := driver.storage.path.normalize(TempPath); kfree(void(TempPath)); GetDirectoryListingFrom := GetDirectoryListing(AbsPath); kfree(void(AbsPath)); @@ -2183,7 +2118,7 @@ var Validity : TIsPathValid; begin TempPath := MakeAbsolutePathFrom(Path, BaseDir); - AbsPath := evaluatePath(TempPath); + AbsPath := driver.storage.path.normalize(TempPath); kfree(void(TempPath)); Validity := PathValid(AbsPath); if Validity = pvDirectory then @@ -2395,7 +2330,7 @@ begin if Callback <> nil then Callback(eOutOfMemory, CallbackData); exit; end; - memset(uint32(ctx), 0, sizeof(TVFSReadAsyncCtx)); + memset(PtrUInt(ctx), 0, sizeof(TVFSReadAsyncCtx)); ctx^.FD := fd; ctx^.BytesReadPtr := BytesRead; ctx^.UserCallback := Callback; @@ -2442,7 +2377,7 @@ begin CompletePathOperation(Error, eOutOfMemory, Callback, CallbackData); exit; end; - memset(uint32(ctx), 0, sizeof(TVFSPathAsyncCtx)); + memset(PtrUInt(ctx), 0, sizeof(TVFSPathAsyncCtx)); ctx^.Path := stringCopy(Path); if ctx^.Path = nil then begin kfree(void(ctx)); @@ -2550,7 +2485,7 @@ begin if Callback <> nil then Callback(eOutOfMemory, CallbackData); exit; end; - memset(uint32(ctx), 0, sizeof(TVFSDirAsyncCtx)); + memset(PtrUInt(ctx), 0, sizeof(TVFSDirAsyncCtx)); ctx^.Path := absPath; ctx^.ResultMap := ResultMap; ctx^.UserCallback := Callback; @@ -2828,7 +2763,7 @@ end; function DevZero_Read(devData : pointer; offset : uint32; buffer : puint8; length : uint32) : uint32; begin if (buffer <> nil) and (length > 0) then - core.util.memset(uint32(buffer), 0, length); + core.util.memset(PtrUInt(buffer), 0, length); DevZero_Read := length; end; @@ -3389,7 +3324,7 @@ begin lineLen := lineEnd - lineStart; if lineLen > 0 then begin lineBuf := pchar(kalloc(lineLen + 1)); - core.util.memcpy(uint32(mntPath) + lineStart, uint32(lineBuf), lineLen); + core.util.memcpy(PtrUInt(mntPath) + lineStart, PtrUInt(lineBuf), lineLen); lineBuf[lineLen] := char(0); { Check for 'mnt ' prefix } @@ -3601,20 +3536,8 @@ var end; procedure PrintSummary; - var - pStr, fStr, msg, tmp : pchar; begin - pStr := intToString(passed); - fStr := intToString(failed); - msg := stringConcat(pStr, ' passed, '); - tmp := stringConcat(msg, fStr); - kfree(void(msg)); - msg := stringConcat(tmp, ' failed.'); - kfree(void(tmp)); - io.syslog.logln('VFS', msg); - kfree(void(msg)); - kfree(void(pStr)); - kfree(void(fStr)); + arch.platform.diagnostics.testSummary('VFS', 'VFS_TESTS_PASSED', 'VFS_TESTS_FAILED', passed, failed); end; begin @@ -3629,26 +3552,26 @@ begin mountVolA^.id := $FFFFFFFE; mountVolB^.id := $FFFFFFFD; tmpKey := defaultVolumeName(mountVolA); Assert(stringEquals(tmpKey,'vol4294967294'),'unlabelled default mount name'); kfree(void(tmpKey)); - memcpy(uint32(pchar('MY DATA')),uint32(@mountVolA^.labelText[0]),8); + memcpy(PtrUInt(pchar('MY DATA')),PtrUInt(@mountVolA^.labelText[0]),8); tmpKey := defaultVolumeName(mountVolA); Assert(stringEquals(tmpKey,'MY DATA'),'label spaces preserved'); kfree(void(tmpKey)); - memset(uint32(@mountVolA^.labelText[0]),0,sizeof(mountVolA^.labelText)); - memcpy(uint32(pchar('A/B')),uint32(@mountVolA^.labelText[0]),4); + memset(PtrUInt(@mountVolA^.labelText[0]),0,sizeof(mountVolA^.labelText)); + memcpy(PtrUInt(pchar('A/B')),PtrUInt(@mountVolA^.labelText[0]),4); tmpKey := defaultVolumeName(mountVolA); Assert(stringEquals(tmpKey,'A_B'),'label cannot create nested mount paths'); kfree(void(tmpKey)); - memset(uint32(@mountVolA^.labelText[0]),0,sizeof(mountVolA^.labelText)); - memcpy(uint32(pchar('..')),uint32(@mountVolA^.labelText[0]),3); + memset(PtrUInt(@mountVolA^.labelText[0]),0,sizeof(mountVolA^.labelText)); + memcpy(PtrUInt(pchar('..')),PtrUInt(@mountVolA^.labelText[0]),3); tmpKey := defaultVolumeName(mountVolA); Assert(stringEquals(tmpKey,'vol4294967294'),'dot-dot label falls back safely'); kfree(void(tmpKey)); - memcpy(uint32(pchar('__MOUNT_TEST__')),uint32(@mountVolA^.labelText[0]),15); + memcpy(PtrUInt(pchar('__MOUNT_TEST__')),PtrUInt(@mountVolA^.labelText[0]),15); mountVolB^.labelText := mountVolA^.labelText; Assert(mountDefaultVolume(mountVolA) = eNone,'label mount succeeds'); obj := GetObjectFromPath('/disk/__MOUNT_TEST__'); Assert((obj <> nil) and (obj^.Reference = void(mountVolA)),'label mount points to correct volume'); { Exercise shared deletion against a memory-only filesystem. } mountDevice := PStorage_Device(kalloc(sizeof(TStorage_Device))); - memset(uint32(mountDevice), 0, sizeof(TStorage_Device)); - memset(uint32(@deleteState), 0, sizeof(deleteState)); + memset(PtrUInt(mountDevice), 0, sizeof(TStorage_Device)); + memset(PtrUInt(@deleteState), 0, sizeof(deleteState)); mountVolA^.device := mountDevice; mountVolA^.fsPrivate := @deleteState; Assert(DeleteFile(nil, @errCode)=eInvalidArgument, 'delete nil path'); @@ -3676,8 +3599,8 @@ begin Assert((obj <> nil) and (obj^.Reference = void(mountVolB)),'duplicate label gets ID suffix'); Assert(mountDefaultVolume(mountVolB) = eNone,'duplicate mount is idempotent'); Assert(InvalidateVolume(mountVolA),'remove old label mount'); - memset(uint32(@mountVolA^.labelText[0]),0,sizeof(mountVolA^.labelText)); - memcpy(uint32(pchar('__RENAMED_TEST__')),uint32(@mountVolA^.labelText[0]),17); + memset(PtrUInt(@mountVolA^.labelText[0]),0,sizeof(mountVolA^.labelText)); + memcpy(PtrUInt(pchar('__RENAMED_TEST__')),PtrUInt(@mountVolA^.labelText[0]),17); Assert(mountDefaultVolume(mountVolA) = eNone,'renamed volume remounts'); Assert(GetObjectFromPath('/disk/__MOUNT_TEST__') = nil,'old label mount removed'); obj := GetObjectFromPath('/disk/__RENAMED_TEST__'); @@ -3685,10 +3608,10 @@ begin InvalidateVolume(mountVolA); InvalidateVolume(mountVolB); { Exercise real registration notifications after the boot mount pass. } mountDevice := PStorage_Device(kalloc(sizeof(TStorage_Device))); - memset(uint32(mountDevice),0,sizeof(TStorage_Device)); + memset(PtrUInt(mountDevice),0,sizeof(TStorage_Device)); mountDevice^.writable := true; mountVolA^.device := mountDevice; mountVolB^.device := mountDevice; - memcpy(uint32(pchar('__HOTPLUG_TEST__')),uint32(@mountVolA^.labelText[0]),17); + memcpy(PtrUInt(pchar('__HOTPLUG_TEST__')),PtrUInt(@mountVolA^.labelText[0]),17); mountVolB^.labelText := mountVolA^.labelText; mountVolA^.filesystem := nil; driver.storage.vol.mgr.register_volume(mountDevice,mountVolA); @@ -4014,7 +3937,7 @@ begin devHandle := OpenFile('/dev/null', omRead, @devErr); Assert((devHandle <> 0) and (devErr = eNone), 'OpenFile /dev/null ok'); devBuf := puint8(kalloc(64)); - core.util.memset(uint32(devBuf), $FF, 64); + core.util.memset(PtrUInt(devBuf), $FF, 64); devRead := ReadFile(devHandle, 0, devBuf, 64); Assert(devRead = 0, '/dev/null read returns 0'); Assert(FileSizeFromHandle(devHandle) = 0, '/dev/null size = 0'); @@ -4028,7 +3951,7 @@ begin CloseFile(devHandle); { /dev/zero — open + read fills buffer with zeroes } - core.util.memset(uint32(devBuf), $FF, 64); + core.util.memset(PtrUInt(devBuf), $FF, 64); devHandle := OpenFile('/dev/zero', omRead, @devErr); Assert((devHandle <> 0) and (devErr = eNone), 'OpenFile /dev/zero ok'); devRead := ReadFile(devHandle, 0, devBuf, 64); @@ -4040,7 +3963,7 @@ begin { /dev/zero — two reads at different offsets } devHandle := OpenFile('/dev/zero', omRead, @devErr); Assert((devHandle <> 0) and (devErr = eNone), 'OpenFile /dev/zero read2 ok'); - core.util.memset(uint32(devBuf), $FF, 64); + core.util.memset(PtrUInt(devBuf), $FF, 64); devRead := ReadFile(devHandle, 0, devBuf, 16); Assert(devRead = 16, '/dev/zero read at 0 = 16'); Assert(devBuf[0] = 0, '/dev/zero read at 0 byte 0 = 0'); @@ -4051,7 +3974,7 @@ begin { /dev/zero — ReadFileAsync device dispatch } devHandle := OpenFile('/dev/zero', omRead, @devErr); Assert((devHandle <> 0) and (devErr = eNone), 'OpenFile /dev/zero for async ok'); - core.util.memset(uint32(devBuf), $FF, 64); + core.util.memset(PtrUInt(devBuf), $FF, 64); devBytesRead := 0; devCallbackOk := false; ReadFileAsync(devHandle, 0, devBuf, 32, @devBytesRead, nil, nil); @@ -4077,7 +4000,7 @@ begin { /dev/null — write: accepted } devHandle := OpenFile('/dev/null', omWrite, @devErr); Assert((devHandle <> 0) and (devErr = eNone), 'OpenFile /dev/null write ok'); - core.util.memset(uint32(devBuf), $AA, 64); + core.util.memset(PtrUInt(devBuf), $AA, 64); devRead := WriteFile(devHandle, 0, devBuf, 20); Assert(devRead = 20, '/dev/null write1 = 20'); devRead := WriteFile(devHandle, 20, devBuf, 12); @@ -4093,7 +4016,7 @@ begin { /dev/zero — write: accepted (discards data) } devHandle := OpenFile('/dev/zero', omWrite, @devErr); Assert((devHandle <> 0) and (devErr = eNone), 'OpenFile /dev/zero write ok'); - core.util.memset(uint32(devBuf), $BB, 64); + core.util.memset(PtrUInt(devBuf), $BB, 64); devRead := WriteFile(devHandle, 0, devBuf, 24); Assert(devRead = 24, '/dev/zero write = 24'); CloseFile(devHandle); @@ -4106,8 +4029,4 @@ begin PrintSummary; end; -initialization - boot.mgr.registerBoot('driver.storage.vfs', @init, 'Virtual File System', BOOT_MGR_BARRIER_STORAGE); - boot.mgr.registerBoot('driver.storage.vol.mount', @auto_mount_volumes, 'Mount Storage Volumes', BOOT_MGR_BARRIER_LATE); - end. diff --git a/src/driver/storage/fs/driver.storage.fs.asfs.pas b/src/driver/storage/fs/driver.storage.fs.asfs.pas index 8edc64e9..9acc22ae 100644 --- a/src/driver/storage/fs/driver.storage.fs.asfs.pas +++ b/src/driver/storage/fs/driver.storage.fs.asfs.pas @@ -32,7 +32,7 @@ uses core.strings, io.stdio, debug.tracer, - core.util, arch.x86.util; + core.util, arch.platform.cpu; type @@ -91,7 +91,7 @@ var begin buffer := puint32(kalloc(disk^.sectorSize)) - memset(uint32(buffer), 0, disk^.sectorSize); + memset(PtrUInt(buffer), 0, disk^.sectorSize); PFilesystemRecord := PFilesystemRecord(buffer); filesystemRecord^.magicNumber := $A2F2; @@ -102,7 +102,7 @@ begin //filesystemRecord.volumeLabel := config^ driver.storage.mgr.storage_write(disk, start, 1, buffer); - memset(uint32(buffer), 0, disk^.sectorSize); + memset(PtrUInt(buffer), 0, disk^.sectorSize); fileEntry := PFileEntry(buffer)[0]; fileEntry^.filename := thisArray; @@ -121,4 +121,4 @@ end; procedure detect_volumes(disk : PStorage_Device) begin -end; \ No newline at end of file +end; diff --git a/src/driver/storage/fs/driver.storage.fs.flatfs.pas b/src/driver/storage/fs/driver.storage.fs.flatfs.pas index 73052ac0..1fbe7e2c 100644 --- a/src/driver/storage/fs/driver.storage.fs.flatfs.pas +++ b/src/driver/storage/fs/driver.storage.fs.flatfs.pas @@ -23,7 +23,6 @@ unit driver.storage.fs.flatfs; interface uses - boot.mgr, driver.storage.fs.mgr, core.ds.lists, memory.heap, @@ -33,7 +32,7 @@ uses core.strings, io.syslog, debug.tracer, - core.util, arch.x86.util, + core.util, arch.platform.cpu, driver.storage.vol.mbr, driver.storage.vol.mgr; @@ -102,19 +101,19 @@ begin if entries <> nil then kfree(void(entries)); create_volume := eOutOfMemory; exit; end; - memset(uint32(info), 0, 512); memset(uint32(entries), 0, 125*512); + memset(PtrUInt(info), 0, 512); memset(PtrUInt(entries), 0, 125*512); err := storage_write(volume^.device, start, 1, puint32(info)); if err = eNone then begin entries[0].attribues := $10; - memcpy(uint32(pchar('/system')), uint32(@entries[0].name[0]), 7); + memcpy(PtrUInt(pchar('/system')), PtrUInt(@entries[0].name[0]), 7); entries[1].attribues := $10; - memcpy(uint32(pchar('/programs')), uint32(@entries[1].name[0]), 9); + memcpy(PtrUInt(pchar('/programs')), PtrUInt(@entries[1].name[0]), 9); entries[2].attribues := $10; - memcpy(uint32(pchar('/user')), uint32(@entries[2].name[0]), 5); + memcpy(PtrUInt(pchar('/user')), PtrUInt(@entries[2].name[0]), 5); err := storage_write(volume^.device, start+1, 125, puint32(entries)); end; if err = eNone then begin - memcpy(uint32(pchar('ASURO ')), uint32(@info^.OEMName[0]), 8); + memcpy(PtrUInt(pchar('ASURO ')), PtrUInt(@info^.OEMName[0]), 8); info^.version := 1; info^.sectorCount := sectors; info^.fileCount := 1000; info^.signature := $0B00B1E5; err := storage_write(volume^.device, start, 1, puint32(info)); @@ -144,7 +143,7 @@ begin //directories := read_directory(volume, directory, status); fileEntries := PFile_Entry(Kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount + 512); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount + 512); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, (sizeof(TFile_Entry) * fileCount) div 512, PuInt32(fileEntries)); @@ -160,13 +159,13 @@ begin fileEntry^.attribues := $10; fileEntry^.size := 0; fileEntry^.start := 0; - memcpy(uint32(directory), uint32(@fileEntry^.name), stringSize(directory)); + memcpy(PtrUInt(directory), PtrUInt(@fileEntry^.name), stringSize(directory)); push_trace('write_directory_3'); //write file table buffer := PuInt32(Kalloc(513)); - memset(uint32(buffer), 0, 512); + memset(PtrUInt(buffer), 0, 512); push_trace('write_directory.b4_read'); @@ -219,13 +218,13 @@ var begin compString := pchar(kalloc(60)); - memset(uint32(compString), 0, 60); + memset(PtrUInt(compString), 0, 60); afterString := pchar(kalloc(60)); - memset(uint32(afterString), 0, 60); + memset(PtrUInt(afterString), 0, 60); slash := stringNew(2); - memcpy(uint32(@slashArray), uint32(slash), 1); + memcpy(PtrUInt(@slashArray), PtrUInt(slash), 1); directories := STRLL_New(); @@ -271,7 +270,7 @@ var begin tempFileEntry := PFile_Entry(Kalloc(sizeof(TFile_Entry))); - memset(uint32(tempFileEntry), 0, sizeof(TFile_Entry)); + memset(PtrUInt(tempFileEntry), 0, sizeof(TFile_Entry)); i:=begining; j:=stop; @@ -322,8 +321,8 @@ var begin fileEntrys := PFile_Entry(Kalloc(sizeof(TFile_Entry) * fileCount + 512)); confirmedFileEntrys := PFile_Entry(Kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntrys), 0, sizeof(TFile_Entry) * fileCount + 512); - memset(uint32(confirmedFileEntrys), 0, sizeof(TFile_Entry) * fileCount + 512); + memset(PtrUInt(fileEntrys), 0, sizeof(TFile_Entry) * fileCount + 512); + memset(PtrUInt(confirmedFileEntrys), 0, sizeof(TFile_Entry) * fileCount + 512); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntrys)); @@ -391,7 +390,7 @@ begin //load file table fileEntries := PFile_Entry(Kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntries)); @@ -439,7 +438,7 @@ begin fileEntries[p].attribues := $01; //add file name - memcpy(uint32(fileName), uint32(@fileEntries[p].name), 14); + memcpy(PtrUInt(fileName), PtrUInt(@fileEntries[p].name), 14); end; @@ -471,7 +470,7 @@ begin //load file table fileEntries := PFile_Entry(Kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntries)); @@ -528,7 +527,7 @@ begin countFreeFlatFSSectors := 0; buffer := puint32(kalloc(512)); - memset(uint32(buffer), 0, 512); + memset(PtrUInt(buffer), 0, 512); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart, 1, buffer); diskInfo := PDisk_Info(buffer); @@ -544,7 +543,7 @@ begin usedSectors := 0; if fileCount > 0 then begin fileEntries := PFile_Entry(kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, tableSectors, puint32(fileEntries)); @@ -573,7 +572,7 @@ begin bufSize := disk^.sectorSize; if bufSize < 512 then bufSize := 512; buffer := puint32(kalloc(bufSize)); - memset(uint32(buffer), 0, bufSize); + memset(PtrUInt(buffer), 0, bufSize); if disk^.dispatchRead = nil then begin io.syslog.writestringln('FlatFS: detect_volumes: device has no read dispatch.'); @@ -586,7 +585,7 @@ begin if (PDisk_Info(buffer)^.signature = $0B00B1E5) then begin io.syslog.writestringln('FlatFS: volume found!'); volume := PStorage_volume(kalloc(sizeof(TStorage_Volume))); - memset(uint32(volume), 0, sizeof(TStorage_Volume)); + memset(PtrUInt(volume), 0, sizeof(TStorage_Volume)); volume^.device := disk; volume^.sectorStart := 2; volume^.sectorSize := 512; @@ -621,7 +620,7 @@ begin //load file table fileEntries := PFile_Entry(Kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntries)); @@ -654,7 +653,7 @@ begin //zero out file zeroBuffer := Kalloc(fileEntries[p].size); - memset(uint32(zeroBuffer), 0, fileEntries[p].size); + memset(PtrUInt(zeroBuffer), 0, fileEntries[p].size); //write file driver.storage.mgr.storage_write(volume^.device, fileEntries[p].start, fileEntries[p].size, zeroBuffer); @@ -663,7 +662,7 @@ begin Kfree(puint32(zeroBuffer)); //zero out file table - memset(uint32(@fileEntries[p]), 0, sizeof(TFile_Entry)); + memset(PtrUInt(@fileEntries[p]), 0, sizeof(TFile_Entry)); //write file table driver.storage.mgr.storage_write(volume^.device, volume^.sectorStart + 1 , 1, puint32(@fileEntries[position])); @@ -695,7 +694,7 @@ begin //load file table fileEntries := PFile_Entry(Kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntries)); @@ -721,7 +720,7 @@ begin position := (p * sizeof(TFile_Entry)) div 512; //zero file table - memset(uint32(@fileEntries[p]), 0, sizeof(TFile_Entry)); + memset(PtrUInt(@fileEntries[p]), 0, sizeof(TFile_Entry)); //write file table driver.storage.mgr.storage_write(volume^.device, volume^.sectorStart + 1 , 1, puint32(@fileEntries[position])); @@ -770,7 +769,7 @@ begin bufSize := volume^.device^.sectorSize; if bufSize < 512 then bufSize := 512; buffer := puint32(kalloc(bufSize)); - memset(uint32(buffer), 0, bufSize); + memset(PtrUInt(buffer), 0, bufSize); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart, 1, buffer); @@ -808,7 +807,7 @@ begin sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntrys)); slash := stringNew(2); - memcpy(uint32(@slashArray), uint32(slash), 1); + memcpy(PtrUInt(@slashArray), PtrUInt(slash), 1); for i := 0 to fileCount - 1 do begin if uint32(fileEntrys[i].name[0]) = 0 then break; @@ -850,7 +849,7 @@ begin getFileSize := 0; fileCount := 1000; fileEntries := PFile_Entry(kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntries)); @@ -888,7 +887,7 @@ begin found := false; fileEntries := PFile_Entry(kalloc(sizeof(TFile_Entry) * fileCount + 512)); - memset(uint32(fileEntries), 0, sizeof(TFile_Entry) * fileCount); + memset(PtrUInt(fileEntries), 0, sizeof(TFile_Entry) * fileCount); driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart + 1, sizeof(TFile_Entry) * fileCount div 512, puint32(fileEntries)); @@ -922,7 +921,7 @@ begin bytesToCopy := 512 - secOff; if bytesToCopy > remaining then bytesToCopy := remaining; - core.util.memcpy(uint32(secBuf) + secOff, uint32(buffer) + destPos, bytesToCopy); + core.util.memcpy(PtrUInt(secBuf) + secOff, PtrUInt(buffer) + destPos, bytesToCopy); destPos := destPos + bytesToCopy; remaining := remaining - bytesToCopy; @@ -954,7 +953,4 @@ begin driver.storage.fs.mgr.register_filesystem(@filesystem); end; -Initialization - boot.mgr.registerBoot('driver.storage.fs.flatfs', @init, 'FlatFS Filesystem', 'driver.storage.fs.mgr'); - end. diff --git a/src/driver/storage/fs/driver.storage.fs.iso9660.pas b/src/driver/storage/fs/driver.storage.fs.iso9660.pas index 50ca0aa6..3c233956 100644 --- a/src/driver/storage/fs/driver.storage.fs.iso9660.pas +++ b/src/driver/storage/fs/driver.storage.fs.iso9660.pas @@ -26,7 +26,6 @@ unit driver.storage.fs.iso9660; interface uses - boot.mgr, driver.storage.fs.mgr, core.ds.lists, memory.heap, @@ -35,7 +34,7 @@ uses core.strings, io.syslog, debug.tracer, - core.util, arch.x86.util, + core.util, arch.platform.cpu, driver.storage.vol.mgr; type @@ -121,6 +120,7 @@ var buf : puint32; bytes : uint32; secSize : uint32; + error : TError; begin readSectors := nil; if count = 0 then exit; @@ -130,8 +130,14 @@ begin bytes := count * secSize; buf := puint32(kalloc(bytes)); if buf = nil then exit; - memset(uint32(buf), 0, bytes); - driver.storage.mgr.storage_read(device, lba, count, buf); + memset(PtrUInt(buf), 0, bytes); + error := driver.storage.mgr.storage_read(device, lba, count, buf); + if error <> eNone then begin + io.syslog.log('ISO9660', 'Read failed: '); + io.syslog.writeintln(ord(error)); + kfree(buf); + exit; + end; readSectors := buf; end; @@ -177,24 +183,24 @@ var semi : sint32; begin s := stringNew(len + 1); - memcpy(uint32(raw), uint32(s), len); - puint8(uint32(s) + len)^ := 0; + memcpy(PtrUInt(raw), PtrUInt(s), len); + puint8(PtrUInt(s) + len)^ := 0; { Find and truncate at semicolon (version separator) } semi := -1; for i := 0 to len - 1 do begin - if puint8(uint32(s) + i)^ = ord(';') then begin + if puint8(PtrUInt(s) + i)^ = ord(';') then begin semi := i; break; end; end; if semi >= 0 then - puint8(uint32(s) + uint32(semi))^ := 0; + puint8(PtrUInt(s) + uint32(semi))^ := 0; { Strip trailing dot (ISO 9660 directories sometimes have it) } i := stringSize(s); - if (i > 0) and (puint8(uint32(s) + i - 1)^ = ord('.')) then - puint8(uint32(s) + i - 1)^ := 0; + if (i > 0) and (puint8(PtrUInt(s) + i - 1)^ = ord('.')) then + puint8(PtrUInt(s) + i - 1)^ := 0; cleanISOName := s; end; @@ -218,8 +224,8 @@ begin exit; end; for i := 0 to la - 1 do begin - ca := puint8(uint32(a) + i)^; - cb := puint8(uint32(b) + i)^; + ca := puint8(PtrUInt(a) + i)^; + cb := puint8(PtrUInt(b) + i)^; { Convert lowercase to uppercase } if (ca >= ord('a')) and (ca <= ord('z')) then ca := ca - 32; if (cb >= ord('a')) and (cb <= ord('z')) then cb := cb - 32; @@ -265,7 +271,7 @@ begin offset := 0; while offset < dirLen do begin - rec := PDirRecord(uint32(dirBuf) + offset); + rec := PDirRecord(PtrUInt(dirBuf) + offset); { A zero record length means we've hit padding — skip to next sector boundary } if rec^.recLen = 0 then begin @@ -279,12 +285,12 @@ begin { Skip '.' (0x00) and '..' (0x01) entries } if (rec^.fileIdLen = 1) and - ((puint8(uint32(rec) + 33)^ = 0) or (puint8(uint32(rec) + 33)^ = 1)) then begin + ((puint8(PtrUInt(rec) + 33)^ = 0) or (puint8(PtrUInt(rec) + 33)^ = 1)) then begin offset := offset + rec^.recLen; continue; end; - entryName := cleanISOName(pchar(uint32(rec) + 33), rec^.fileIdLen); + entryName := cleanISOName(pchar(PtrUInt(rec) + 33), rec^.fileIdLen); if equalsIgnoreCase(entryName, name) then begin pOutLBA^ := rec^.extentLBA_LSB; @@ -336,7 +342,7 @@ begin { Skip leading slash } i := 0; - if puint8(uint32(path))^ = ord('/') then i := 1; + if puint8(PtrUInt(path))^ = ord('/') then i := 1; foundDir := 1; while i <= pathLen do begin @@ -348,7 +354,7 @@ begin { Find next slash or end of path } start := i; - while (i < pathLen) and (puint8(uint32(path) + i)^ <> ord('/')) do + while (i < pathLen) and (puint8(PtrUInt(path) + i)^ <> ord('/')) do i := i + 1; compLen := i - start; if compLen = 0 then begin @@ -358,8 +364,8 @@ begin { Extract component } component := stringNew(compLen + 1); - memcpy(uint32(path) + start, uint32(component), compLen); - puint8(uint32(component) + compLen)^ := 0; + memcpy(PtrUInt(path) + start, PtrUInt(component), compLen); + puint8(PtrUInt(component) + compLen)^ := 0; { Read current directory extent } dirBuf := readExtent(device, curLBA, curLen); @@ -395,7 +401,7 @@ end; procedure cacheVolumeLabel(volume : PStorage_Volume; pvd : PPVD); var n, i : uint32; begin - memset(uint32(@volume^.labelText[0]),0,sizeof(volume^.labelText)); + memset(PtrUInt(@volume^.labelText[0]),0,sizeof(volume^.labelText)); n := 32; while (n > 0) and ((pvd^.volumeId[n-1] = ' ') or (pvd^.volumeId[n-1] = #0)) do Dec(n); i := 0; while i < n do begin volume^.labelText[i] := pvd^.volumeId[i]; Inc(i); end; @@ -438,7 +444,7 @@ begin pvd := PPVD(buf); volume := PStorage_Volume(kalloc(sizeof(TStorage_Volume))); - memset(uint32(volume), 0, sizeof(TStorage_Volume)); + memset(PtrUInt(volume), 0, sizeof(TStorage_Volume)); volume^.device := disk; volume^.sectorStart := 0; volume^.sectorSize := disk^.sectorSize; @@ -524,7 +530,7 @@ begin offset := 0; iters := 0; while offset < dirLen do begin - rec := PDirRecord(uint32(buf) + offset); + rec := PDirRecord(PtrUInt(buf) + offset); iters := iters + 1; if iters > 65536 then begin @@ -545,12 +551,12 @@ begin { Skip '.' (0x00) and '..' (0x01) } if (rec^.fileIdLen = 1) and - ((puint8(uint32(rec) + 33)^ = 0) or (puint8(uint32(rec) + 33)^ = 1)) then begin + ((puint8(PtrUInt(rec) + 33)^ = 0) or (puint8(PtrUInt(rec) + 33)^ = 1)) then begin offset := offset + rec^.recLen; continue; end; - entryName := cleanISOName(pchar(uint32(rec) + 33), rec^.fileIdLen); + entryName := cleanISOName(pchar(PtrUInt(rec) + 33), rec^.fileIdLen); elm := LL_Add(entries); dirEntry := PDirectory_Entry(elm); @@ -730,11 +736,14 @@ begin secOff := offset mod secSize; while remaining > 0 do begin - driver.storage.mgr.storage_read(volume^.device, curLBA, 1, secBuf); + if driver.storage.mgr.storage_read(volume^.device, curLBA, 1, secBuf) <> eNone then begin + kfree(secBuf); + exit; + end; bytesToCopy := secSize - secOff; if bytesToCopy > remaining then bytesToCopy := remaining; - core.util.memcpy(uint32(secBuf) + secOff, uint32(buffer) + destPos, bytesToCopy); + core.util.memcpy(PtrUInt(secBuf) + secOff, PtrUInt(buffer) + destPos, bytesToCopy); destPos := destPos + bytesToCopy; remaining := remaining - bytesToCopy; @@ -768,7 +777,4 @@ begin driver.storage.fs.mgr.register_filesystem(@filesystem); end; -initialization - boot.mgr.registerBoot('driver.storage.fs.iso9660', @Init, 'IS9660 File System', 'driver.storage.fs.mgr'); - end. diff --git a/src/driver/storage/fs/driver.storage.fs.mgr.pas b/src/driver/storage/fs/driver.storage.fs.mgr.pas index 79e6319f..5b70f31d 100644 --- a/src/driver/storage/fs/driver.storage.fs.mgr.pas +++ b/src/driver/storage/fs/driver.storage.fs.mgr.pas @@ -12,17 +12,18 @@ unit driver.storage.fs.mgr; interface uses - boot.mgr, core.ds.lists, memory.heap, driver.storage.types, core.strings, - debug.tracer, - core.util, arch.x86.util; + arch.platform.diagnostics, + arch.platform.cpu, + core.util; var filesystems : PLinkedListBase; +procedure UnitTest; procedure init(); procedure register_filesystem(filesystem : PFilesystem); function get_filesystem_count() : uint32; @@ -35,90 +36,148 @@ implementation procedure init(); begin - push_trace('driver.storage.fs.mgr.init'); - filesystems := LL_New(sizeof(TFilesystem)); + if filesystems = nil then + filesystems := LL_New(sizeof(TFilesystem)); end; procedure register_filesystem(filesystem : PFilesystem); var - elm : void; + entry: void; begin - push_trace('driver.storage.fs.mgr.register_filesystem'); - - elm := LL_Add(filesystems); - memcpy(uint32(filesystem), uint32(elm), sizeof(TFilesystem)); + if (filesystems = nil) or (filesystem = nil) then exit; + entry := LL_Add(filesystems); + if entry <> nil then + memcpy(PtrUInt(filesystem), PtrUInt(entry), sizeof(TFilesystem)); end; function get_filesystem_count() : uint32; begin - get_filesystem_count := LL_Size(filesystems); + get_filesystem_count := 0; + if filesystems <> nil then + get_filesystem_count := LL_Size(filesystems); end; function get_filesystem(index : uint32) : PFilesystem; begin - if index < LL_Size(filesystems) then - get_filesystem := PFilesystem(LL_Get(filesystems, index)) - else - get_filesystem := nil; + get_filesystem := nil; + if filesystems <> nil then + get_filesystem := PFilesystem(LL_Get(filesystems, index)); end; function find_filesystem_by_name(name : pchar) : PFilesystem; var - i : uint32; - fs : PFilesystem; + node: PLinkedList; + fs: PFilesystem; begin - push_trace('driver.storage.fs.mgr.find_filesystem_by_name'); find_filesystem_by_name := nil; - - for i := 0 to LL_Size(filesystems) - 1 do begin - fs := PFilesystem(LL_Get(filesystems, i)); + if (filesystems = nil) or (name = nil) then exit; + node := filesystems^.Head; + while node <> nil do begin + fs := PFilesystem(node^.Data); if stringEquals(fs^.sName, name) then begin find_filesystem_by_name := fs; exit; end; + node := node^.Next; end; end; function find_filesystem_by_id(system_id : uint8) : PFilesystem; var - i : uint32; - fs : PFilesystem; + node: PLinkedList; + fs: PFilesystem; begin - push_trace('driver.storage.fs.mgr.find_filesystem_by_id'); find_filesystem_by_id := nil; - - for i := 0 to LL_Size(filesystems) - 1 do begin - fs := PFilesystem(LL_Get(filesystems, i)); + if filesystems = nil then exit; + node := filesystems^.Head; + while node <> nil do begin + fs := PFilesystem(node^.Data); if fs^.system_id = system_id then begin find_filesystem_by_id := fs; exit; end; + node := node^.Next; end; end; procedure probe_volume(volume : PStorage_Volume); var - i : uint32; - fs : PFilesystem; + node: PLinkedList; + fs: PFilesystem; begin - push_trace('driver.storage.fs.mgr.probe_volume.enter'); - - { Try each registered filesystem's identify callback against this volume } - for i := 0 to LL_Size(filesystems) - 1 do begin - fs := PFilesystem(LL_Get(filesystems, i)); + if (filesystems = nil) or (volume = nil) then exit; + node := filesystems^.Head; + while node <> nil do begin + fs := PFilesystem(node^.Data); if fs^.identifyCallback <> nil then begin - push_trace('driver.storage.fs.mgr.probe_volume.tryFS'); if fs^.identifyCallback(volume) then begin volume^.filesystem := fs; - push_trace('driver.storage.fs.mgr.probe_volume.found'); exit; end; end; + node := node^.Next; end; - push_trace('driver.storage.fs.mgr.probe_volume.exit'); end; -Initialization - boot.mgr.registerBoot('driver.storage.fs.mgr', @init, 'Filesystem Manager', 'driver.storage.vol.mgr'); +function testIdentify(volume: PStorage_Volume): boolean; +begin + testIdentify := volume^.sectorCount = 123; +end; + +procedure UnitTest; +var + saved: PLinkedListBase; + fs: PFilesystem; + volume: PStorage_Volume; + flags: PtrUInt; + beforeFree: SizeUInt; + passed, failed: uint32; + + procedure Assert(condition: boolean); + begin + if condition then + inc(passed) + else + inc(failed); + end; +begin + passed := 0; + failed := 0; + flags := arch.platform.cpu.lock; + beforeFree := lmm_total_free; + saved := filesystems; + filesystems := nil; + Assert(get_filesystem_count = 0); + Assert(find_filesystem_by_name('test') = nil); + init; + Assert(find_filesystem_by_id(123) = nil); + fs := PFilesystem(kalloc(sizeof(TFilesystem))); + volume := PStorage_Volume(kalloc(sizeof(TStorage_Volume))); + if (fs <> nil) and (volume <> nil) then begin + fs^.sName := 'test'; + fs^.system_id := 123; + fs^.identifyCallback := @testIdentify; + register_filesystem(fs); + init; + Assert(get_filesystem_count = 1); + Assert((find_filesystem_by_name('test') = get_filesystem(0)) and + (find_filesystem_by_id(123) = get_filesystem(0))); + Assert(get_filesystem(1) = nil); + probe_volume(volume); + Assert(volume^.filesystem = nil); + volume^.sectorCount := 123; + probe_volume(volume); + Assert((volume^.filesystem <> nil) and (volume^.filesystem <> fs) and + (volume^.filesystem^.identifyCallback = @testIdentify)); + end else + inc(failed); + kfree(void(fs)); + kfree(void(volume)); + if filesystems <> nil then LL_Free(filesystems); + filesystems := saved; + Assert(lmm_total_free = beforeFree); + arch.platform.cpu.unlock(flags); + testSummary('FSMGR', 'FSMGR_TESTS_PASSED', 'FSMGR_TESTS_FAILED', passed, failed); +end; end. diff --git a/src/driver/storage/fs/ext2/driver.storage.fs.ext2.core.pas b/src/driver/storage/fs/ext2/driver.storage.fs.ext2.core.pas index f3d88d89..77da682a 100644 --- a/src/driver/storage/fs/ext2/driver.storage.fs.ext2.core.pas +++ b/src/driver/storage/fs/ext2/driver.storage.fs.ext2.core.pas @@ -1,7 +1,7 @@ unit driver.storage.fs.ext2.core; interface -uses driver.storage.types, driver.storage.fs.ext2.types, core.ds.lists; +uses arch.platform.cpu, driver.storage.types, driver.storage.fs.ext2.types, core.ds.lists; function Get16(p : pointer; offset : uint32) : uint16; function Get32(p : pointer; offset : uint32) : uint32; @@ -39,13 +39,13 @@ implementation uses memory.heap, core.util, core.strings, driver.storage.mgr; function Get16(p : pointer; offset : uint32) : uint16; -begin Get16 := puint16(uint32(p)+offset)^; end; +begin Get16 := puint16(PtrUInt(p)+offset)^; end; function Get32(p : pointer; offset : uint32) : uint32; -begin Get32 := puint32(uint32(p)+offset)^; end; +begin Get32 := puint32(PtrUInt(p)+offset)^; end; procedure Put16(p : pointer; offset : uint32; value : uint16); -begin puint16(uint32(p)+offset)^ := value; end; +begin puint16(PtrUInt(p)+offset)^ := value; end; procedure Put32(p : pointer; offset, value : uint32); -begin puint32(uint32(p)+offset)^ := value; end; +begin puint32(PtrUInt(p)+offset)^ := value; end; function PowerOf(value, base : uint32) : boolean; begin @@ -60,7 +60,7 @@ begin end; function GroupDesc(v : PExt2Volume; group : uint32) : PExt2Group; -begin GroupDesc := PExt2Group(uint32(v^.Groups)+group*32); end; +begin GroupDesc := PExt2Group(PtrUInt(v^.Groups)+group*32); end; function GroupBlocks(v : PExt2Volume; group : uint32) : uint32; var first, count : uint32; @@ -117,7 +117,9 @@ begin end; function BeginOp(volume : PStorage_Volume) : PExt2Op; -var op : PExt2Op; v : PExt2Volume; +var + savedIRQ: PtrUInt; + op : PExt2Op; v : PExt2Volume; begin BeginOp := nil; if volume = nil then exit; @@ -125,20 +127,20 @@ begin if v = nil then exit; op := PExt2Op(kalloc(sizeof(TExt2Op))); if op = nil then exit; - memset(uint32(op),0,sizeof(TExt2Op)); + memset(PtrUInt(op),0,sizeof(TExt2Op)); op^.V := v; op^.Scratch := puint8(kalloc(v^.BlockSize*2)); if op^.Scratch = nil then begin kfree(void(op)); exit; end; - op^.Data := puint8(uint32(op^.Scratch)+v^.BlockSize); + op^.Data := puint8(PtrUInt(op^.Scratch)+v^.BlockSize); while true do begin - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if not v^.Busy then begin v^.Busy := true; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); break; end; - asm popf end; - asm hlt end; + arch.platform.cpu.unlock(savedIRQ); + arch.platform.cpu.idle; end; BeginOp := op; end; @@ -195,7 +197,7 @@ begin ReadInode := false; if not InodeLocation(op,number,block,offset) then exit; if not ReadBlock(op,block,op^.Scratch) then exit; - memcpy(uint32(op^.Scratch)+offset,uint32(@inode),128); + memcpy(PtrUInt(op^.Scratch)+offset,PtrUInt(@inode),128); if (inode.Links = 0) or (inode.DTime <> 0) then begin op^.Error := eCorruptFilesystem; exit; end; { Compression, extents and inline data are not ext2 block maps. } if (inode.Flags and ($4 or $200 or $800 or $80000 or $10000000)) <> 0 then begin op^.Error := eNotSupported; exit; end; @@ -209,7 +211,7 @@ begin WriteInode := false; if not InodeLocation(op,number,block,offset) then exit; if not ReadBlock(op,block,op^.Scratch) then exit; - memcpy(uint32(@inode),uint32(op^.Scratch)+offset,128); + memcpy(PtrUInt(@inode),PtrUInt(op^.Scratch)+offset,128); WriteInode := WriteBlock(op,block,op^.Scratch); end; @@ -252,9 +254,9 @@ begin if v^.GDTBlocks >= v^.BlockCount-v^.FirstBlock then exit; v^.Groups := puint8(kalloc(v^.GDTBlocks*v^.BlockSize)); if v^.Groups = nil then exit; - memset(uint32(@op),0,sizeof(op)); op.V := v; + memset(PtrUInt(@op),0,sizeof(op)); op.V := v; for i := 0 to v^.GDTBlocks-1 do - if not ReadBlock(@op,v^.FirstBlock+1+i,pointer(uint32(v^.Groups)+i*v^.BlockSize)) then exit; + if not ReadBlock(@op,v^.FirstBlock+1+i,pointer(PtrUInt(v^.Groups)+i*v^.BlockSize)) then exit; tables := v^.InodesPerGroup div (v^.BlockSize div v^.InodeSize); for g := 0 to v^.GroupCount-1 do begin d := GroupDesc(v,g); first := v^.FirstBlock+g*v^.BlocksPerGroup; count := GroupBlocks(v,g); @@ -290,14 +292,14 @@ begin if volume^.fsPrivate <> nil then exit; v := PExt2Volume(kalloc(sizeof(TExt2Volume))); if v = nil then exit; - memset(uint32(v),0,sizeof(TExt2Volume)); v^.Volume := volume; + memset(PtrUInt(v),0,sizeof(TExt2Volume)); v^.Volume := volume; v^.Super := puint8(kalloc(1024)); if v^.Super = nil then begin FreeVolume(v); exit; end; if storage_read(volume^.device,volume^.sectorStart+2,2,puint32(v^.Super)) <> eNone then begin FreeVolume(v); exit; end; if not LoadVolume(v) then begin FreeVolume(v); exit; end; volume^.fsPrivate := v; volume^.sectorSize := 512; volume^.freeSectors := Get32(v^.Super,12)*v^.SectorsPerBlock; - memset(uint32(@volume^.labelText),0,sizeof(volume^.labelText)); + memset(PtrUInt(@volume^.labelText),0,sizeof(volume^.labelText)); for i := 0 to 15 do volume^.labelText[i] := char(v^.Super[120+i]); Identify := true; end; @@ -316,7 +318,7 @@ var block : uint32; begin block := group div (op^.V^.BlockSize div 32); SaveGroup := WriteBlock(op,op^.V^.FirstBlock+1+block, - pointer(uint32(op^.V^.Groups)+block*op^.V^.BlockSize)); + pointer(PtrUInt(op^.V^.Groups)+block*op^.V^.BlockSize)); end; function Allocate(op : PExt2Op; inodeBit : boolean; var number : uint32) : boolean; @@ -357,7 +359,7 @@ begin if not SaveGroup(op,g) then exit; v^.AllocGroup := g; number := absolute; if not inodeBit then begin - memset(uint32(op^.Scratch),0,v^.BlockSize); + memset(PtrUInt(op^.Scratch),0,v^.BlockSize); if not WriteBlock(op,number,op^.Scratch) then exit; end; Allocate := true; exit; @@ -436,7 +438,7 @@ var i, length : uint32; begin DirRecord := false; if limit-offset < 8 then begin op^.Error:=eCorruptFilesystem; exit; end; - d := PExt2Dirent(uint32(op^.Data)+offset); + d := PExt2Dirent(PtrUInt(op^.Data)+offset); length := d^.NameLength; if not op^.V^.FileTypes and (d^.FileType <> 0) then begin op^.Error:=eCorruptFilesystem; exit; end; if (d^.Length < 8) or (d^.Length mod 4 <> 0) or (d^.Length > limit-offset) or @@ -475,7 +477,7 @@ begin while offset0) and (d^.NameLength=length) then - if SameName(pchar(uint32(d)+8),name,length) then begin number:=d^.Inode; Lookup:=true; exit; end; + if SameName(pchar(PtrUInt(d)+8),name,length) then begin number:=d^.Inode; Lookup:=true; exit; end; Inc(offset,d^.Length); end; end; @@ -495,7 +497,7 @@ begin start:=pos; while (path[pos]<>char(0)) and (path[pos]<>'/') and (pos-start<=255) do Inc(pos); if pos-start>255 then begin op^.Error:=eFilenameTooLong; exit; end; - if not Lookup(op,inode,pchar(uint32(path)+start),pos-start,next,block,offset) then exit; + if not Lookup(op,inode,pchar(PtrUInt(path)+start),pos-start,next,block,offset) then exit; number:=next; if not ReadInode(op,number,inode) then exit; end; @@ -526,11 +528,11 @@ end; procedure SetDirent(op : PExt2Op; offset, length, number : uint32; name : pchar; nameLength : uint32; isDir : boolean); var d : PExt2Dirent; begin - d:=PExt2Dirent(uint32(op^.Data)+offset); - memset(uint32(d),0,length); + d:=PExt2Dirent(PtrUInt(op^.Data)+offset); + memset(PtrUInt(d),0,length); d^.Inode:=number; d^.Length:=length; d^.NameLength:=nameLength; if op^.V^.FileTypes then begin if isDir then d^.FileType:=2 else d^.FileType:=1; end; - memcpy(uint32(name),uint32(d)+8,nameLength); + memcpy(PtrUInt(name),PtrUInt(d)+8,nameLength); end; function AddEntry(op : PExt2Op; parent : uint32; var dir : TExt2Inode; name : pchar; number : uint32; isDir : boolean) : boolean; @@ -585,9 +587,9 @@ begin { Clear the full inode slot, including any extended inode fields. } if not InodeLocation(op,number,block,offset) then exit; if not ReadBlock(op,block,op^.Scratch) then exit; - memset(uint32(op^.Scratch)+offset,0,op^.V^.InodeSize); + memset(PtrUInt(op^.Scratch)+offset,0,op^.V^.InodeSize); if not WriteBlock(op,block,op^.Scratch) then exit; - memset(uint32(@inode),0,sizeof(inode)); + memset(PtrUInt(@inode),0,sizeof(inode)); inode.Mode:=EXT2_FILE or $1A4; inode.Links:=1; if isDir then begin inode.Mode:=EXT2_DIR or $1ED; inode.Links:=2; end; if not WriteInode(op,number,inode) then exit; @@ -617,11 +619,11 @@ begin if (length=0) or (length=4096) then begin op^.Error:=eInvalidPath; exit; end; split:=0; for i:=0 to length-1 do if path[i]='/' then split:=i+1; - name:=pchar(uint32(path)+split); + name:=pchar(PtrUInt(path)+split); if not ValidName(op,name,i) then exit; copyPath:=pchar(kalloc(split+1)); if copyPath=nil then begin op^.Error:=eOutOfMemory; exit; end; - memcpy(uint32(path),uint32(copyPath),split); copyPath[split]:=char(0); + memcpy(PtrUInt(path),PtrUInt(copyPath),split); copyPath[split]:=char(0); ParentPath:=Resolve(op,copyPath,parent,inode); kfree(void(copyPath)); end; @@ -659,7 +661,7 @@ begin offset:=0; while offset0 then if not (((d^.NameLength=1) and (name[0]='.')) or ((d^.NameLength=2) and (name[0]='.') and (name[1]='.'))) then begin op^.Error:=eDirectoryNotEmpty; exit; end; @@ -770,7 +772,7 @@ begin else begin list:=PLinkedListBase(kalloc(sizeof(TLinkedListBase))); if list=nil then op^.Error:=eOutOfMemory else begin - memset(uint32(list),0,sizeof(TLinkedListBase)); list^.ElementSize:=sizeof(TDirectory_Entry); + memset(PtrUInt(list),0,sizeof(TLinkedListBase)); list^.ElementSize:=sizeof(TDirectory_Entry); for logical:=0 to dir.Size div op^.V^.BlockSize-1 do begin if not MapBlock(op,number,dir,logical,false,block) then break; if block=0 then begin op^.Error:=eCorruptFilesystem; break; end; @@ -788,10 +790,10 @@ begin if node<>nil then kfree(void(node)); if e<>nil then kfree(void(e)); op^.Error:=eOutOfMemory; break; end; - memset(uint32(e),0,sizeof(TDirectory_Entry)); + memset(PtrUInt(e),0,sizeof(TDirectory_Entry)); e^.fileName:=pchar(kalloc(uint32(d^.NameLength)+1)); if e^.fileName=nil then begin kfree(void(node)); kfree(void(e)); op^.Error:=eOutOfMemory; break; end; - memcpy(uint32(d)+8,uint32(e^.fileName),d^.NameLength); e^.fileName[d^.NameLength]:=char(0); + memcpy(PtrUInt(d)+8,PtrUInt(e^.fileName),d^.NameLength); e^.fileName[d^.NameLength]:=char(0); e^.fileSize:=inode.Size; if (inode.Mode and $F000)=EXT2_DIR then e^.entryType:=directoryEntry else e^.entryType:=fileEntry; node^.Data:=void(e); node^.Next:=nil; node^.Previous:=tail; @@ -874,7 +876,7 @@ begin if block<>0 then if ReadBlock(op,block,op^.Data) then begin within:=oldSize mod op^.V^.BlockSize; - memset(uint32(op^.Data)+within,0,op^.V^.BlockSize-within); + memset(PtrUInt(op^.Data)+within,0,op^.V^.BlockSize-within); WriteBlock(op,block,op^.Data); end; end; @@ -885,15 +887,15 @@ begin within:=(offset+done) mod op^.V^.BlockSize; amount:=op^.V^.BlockSize-within; if amount>limit-done then amount:=limit-done; if not MapBlock(op,fileCtx^.Inode,inode,(offset+done) div op^.V^.BlockSize,writing,block) then break; - if block=0 then memset(uint32(op^.Data),0,op^.V^.BlockSize) + if block=0 then memset(PtrUInt(op^.Data),0,op^.V^.BlockSize) else if (not writing) or (within<>0) or (amount<>op^.V^.BlockSize) then if not ReadBlock(op,block,op^.Data) then break; if writing then begin - memcpy(uint32(buffer)+done,uint32(op^.Data)+within,amount); + memcpy(PtrUInt(buffer)+done,PtrUInt(op^.Data)+within,amount); if not WriteBlock(op,block,op^.Data) then break; if offset+done+amount>inode.Size then inode.Size:=offset+done+amount; if not WriteInode(op,fileCtx^.Inode,inode) then break; - end else memcpy(uint32(op^.Data)+within,uint32(buffer)+done,amount); + end else memcpy(PtrUInt(op^.Data)+within,PtrUInt(buffer)+done,amount); Inc(done,amount); end; if writing and op^.Mutating and (op^.Error=eNone) then FinishMutation(op); diff --git a/src/driver/storage/fs/ext2/driver.storage.fs.ext2.format.pas b/src/driver/storage/fs/ext2/driver.storage.fs.ext2.format.pas index 6c975051..bfa1e6c8 100644 --- a/src/driver/storage/fs/ext2/driver.storage.fs.ext2.format.pas +++ b/src/driver/storage/fs/ext2/driver.storage.fs.ext2.format.pas @@ -14,7 +14,7 @@ uses driver.storage.fs.ext2.types, driver.storage.fs.ext2.core, function ValidateLabel(text : pchar; var normalized : TVolumeLabel) : TError; var i : uint32; begin - memset(uint32(@normalized),0,sizeof(normalized)); + memset(PtrUInt(@normalized),0,sizeof(normalized)); ValidateLabel:=eInvalidArgument; if text=nil then exit; i:=0; while (i<16) and (text[i]<>char(0)) do begin @@ -29,7 +29,7 @@ function Geometry(volume : PStorage_Volume; sectors, start : uint32; config : PF var g, count, minimum, perBlock, tables, reserve : uint32; labelText : TVolumeLabel; begin Geometry:=eInvalidArgument; - memset(uint32(@v),0,sizeof(v)); + memset(PtrUInt(@v),0,sizeof(v)); if volume=nil then exit; if volume^.device=nil then exit; if not volume^.device^.writable then begin Geometry:=eReadOnly; exit; end; @@ -98,7 +98,7 @@ begin if err<>eNone then begin kfree(void(v)); FormatVolume:=err; exit; end; op:=PExt2Op(kalloc(sizeof(TExt2Op))); if op=nil then begin kfree(void(v)); exit; end; - memset(uint32(op),0,sizeof(TExt2Op)); op^.V:=v; + memset(PtrUInt(op),0,sizeof(TExt2Op)); op^.V:=v; v^.Super:=puint8(kalloc(1024)); v^.Groups:=puint8(kalloc(v^.GDTBlocks*v^.BlockSize)); op^.Scratch:=puint8(kalloc(v^.BlockSize)); if (v^.Super=nil) or (v^.Groups=nil) or (op^.Scratch=nil) then begin @@ -106,7 +106,7 @@ begin if op^.Scratch<>nil then kfree(void(op^.Scratch)); kfree(void(op)); kfree(void(v)); exit; end; - memset(uint32(v^.Super),0,1024); memset(uint32(v^.Groups),0,v^.GDTBlocks*v^.BlockSize); + memset(PtrUInt(v^.Super),0,1024); memset(PtrUInt(v^.Groups),0,v^.GDTBlocks*v^.BlockSize); Put32(v^.Super,0,v^.InodeCount); Put32(v^.Super,4,v^.BlockCount); Put32(v^.Super,16,v^.InodeCount-10); Put32(v^.Super,20,v^.FirstBlock); i:=0; if v^.BlockSize=2048 then i:=1 else if v^.BlockSize=4096 then i:=2; @@ -120,13 +120,13 @@ begin v^.Super[110]:=(v^.Super[110] and $0F) or $40; v^.Super[112]:=(v^.Super[112] and $3F) or $80; if config<>nil then if (config^.Flags and FS_FORMAT_PARAM_LABEL)<>0 then - memcpy(uint32(@config^.LabelText[0]),uint32(v^.Super)+120,11); + memcpy(PtrUInt(@config^.LabelText[0]),PtrUInt(v^.Super)+120,11); tables:=v^.InodesPerGroup div (v^.BlockSize div 128); freeBlocks:=0; rootBlock:=0; for g:=0 to v^.GroupCount-1 do begin first:=v^.FirstBlock+g*v^.BlocksPerGroup; count:=v^.BlockCount-first; if count>v^.BlocksPerGroup then count:=v^.BlocksPerGroup; reserve:=0; if HasSuper(g,1) then reserve:=1+v^.GDTBlocks; - d:=PExt2Group(uint32(v^.Groups)+g*32); + d:=PExt2Group(PtrUInt(v^.Groups)+g*32); d^.BlockBitmap:=first+reserve; d^.InodeBitmap:=first+reserve+1; d^.InodeTable:=first+reserve+2; used:=reserve+2+tables; d^.FreeInodes:=v^.InodesPerGroup; @@ -135,42 +135,42 @@ begin end; Put32(v^.Super,12,freeBlocks); { Invalidate the primary signature before writing metadata. Publish it last. } - memset(uint32(op^.Scratch),0,v^.BlockSize); + memset(PtrUInt(op^.Scratch),0,v^.BlockSize); op^.Error:=storage_write(volume^.device,start+2,2,puint32(op^.Scratch)); g:=0; while (gv^.BlocksPerGroup then count:=v^.BlocksPerGroup; - d:=PExt2Group(uint32(v^.Groups)+g*32); used:=count-d^.FreeBlocks; - memset(uint32(op^.Scratch),$FF,v^.BlockSize); + d:=PExt2Group(PtrUInt(v^.Groups)+g*32); used:=count-d^.FreeBlocks; + memset(PtrUInt(op^.Scratch),$FF,v^.BlockSize); for bit:=used to count-1 do op^.Scratch[bit div 8]:=op^.Scratch[bit div 8] and not (1 shl (bit mod 8)); WriteBlock(op,d^.BlockBitmap,op^.Scratch); - memset(uint32(op^.Scratch),$FF,v^.BlockSize); + memset(PtrUInt(op^.Scratch),$FF,v^.BlockSize); used:=0; if g=0 then used:=10; for bit:=used to v^.InodesPerGroup-1 do op^.Scratch[bit div 8]:=op^.Scratch[bit div 8] and not (1 shl (bit mod 8)); WriteBlock(op,d^.InodeBitmap,op^.Scratch); - memset(uint32(op^.Scratch),0,v^.BlockSize); + memset(PtrUInt(op^.Scratch),0,v^.BlockSize); for i:=0 to tables-1 do begin if not WriteBlock(op,d^.InodeTable+i,op^.Scratch) then break; end; if HasSuper(g,1) then begin for i:=0 to v^.GDTBlocks-1 do - if not WriteBlock(op,first+1+i,pointer(uint32(v^.Groups)+i*v^.BlockSize)) then break; + if not WriteBlock(op,first+1+i,pointer(PtrUInt(v^.Groups)+i*v^.BlockSize)) then break; if g<>0 then begin - memset(uint32(op^.Scratch),0,v^.BlockSize); - memcpy(uint32(v^.Super),uint32(op^.Scratch),1024); Put16(op^.Scratch,90,g); + memset(PtrUInt(op^.Scratch),0,v^.BlockSize); + memcpy(PtrUInt(v^.Super),PtrUInt(op^.Scratch),1024); Put16(op^.Scratch,90,g); WriteBlock(op,first,op^.Scratch); end; end; Inc(g); end; if op^.Error=eNone then begin - memset(uint32(op^.Scratch),0,v^.BlockSize); - inode:=PExt2Inode(uint32(op^.Scratch)+128); + memset(PtrUInt(op^.Scratch),0,v^.BlockSize); + inode:=PExt2Inode(PtrUInt(op^.Scratch)+128); inode^.Mode:=EXT2_DIR or $1ED; inode^.Size:=v^.BlockSize; inode^.Links:=2; inode^.Sectors:=v^.SectorsPerBlock; inode^.Blocks[0]:=rootBlock; WriteBlock(op,PExt2Group(v^.Groups)^.InodeTable,op^.Scratch); - memset(uint32(op^.Scratch),0,v^.BlockSize); + memset(PtrUInt(op^.Scratch),0,v^.BlockSize); Put32(op^.Scratch,0,2); Put16(op^.Scratch,4,12); op^.Scratch[6]:=1; op^.Scratch[7]:=2; op^.Scratch[8]:=ord('.'); Put32(op^.Scratch,12,2); Put16(op^.Scratch,16,v^.BlockSize-12); op^.Scratch[18]:=2; op^.Scratch[19]:=2; op^.Scratch[20]:=ord('.'); op^.Scratch[21]:=ord('.'); @@ -188,14 +188,14 @@ begin SetLabel:=ValidateLabel(text,labelText); if SetLabel<>eNone then exit; op:=BeginOp(volume); if op=nil then begin SetLabel:=eOutOfMemory; exit; end; if BeginMutation(op) then begin - memcpy(uint32(@labelText),uint32(op^.V^.Super)+120,16); + memcpy(PtrUInt(@labelText),PtrUInt(op^.V^.Super)+120,16); for g:=1 to op^.V^.GroupCount-1 do if HasSuper(g,Get32(op^.V^.Super,100)) then begin first:=op^.V^.FirstBlock+g*op^.V^.BlocksPerGroup; if not ReadBlock(op,first,op^.Scratch) then break; - memcpy(uint32(@labelText),uint32(op^.Scratch)+120,16); + memcpy(PtrUInt(@labelText),PtrUInt(op^.Scratch)+120,16); if not WriteBlock(op,first,op^.Scratch) then break; end; - if FinishMutation(op) then memcpy(uint32(@labelText),uint32(@volume^.labelText),sizeof(labelText)); + if FinishMutation(op) then memcpy(PtrUInt(@labelText),PtrUInt(@volume^.labelText),sizeof(labelText)); end; SetLabel:=op^.Error; EndOp(op); end; diff --git a/src/driver/storage/fs/ext2/driver.storage.fs.ext2.pas b/src/driver/storage/fs/ext2/driver.storage.fs.ext2.pas index 4ecade60..f70dba03 100644 --- a/src/driver/storage/fs/ext2/driver.storage.fs.ext2.pas +++ b/src/driver/storage/fs/ext2/driver.storage.fs.ext2.pas @@ -1,12 +1,13 @@ unit driver.storage.fs.ext2; interface -uses driver.storage.types; +uses arch.platform.cpu, driver.storage.types; +procedure StartWorker; procedure init; procedure ReleaseVolume(volume : PStorage_Volume); implementation -uses boot.mgr, driver.storage.fs.mgr, driver.storage.vol.mgr, +uses driver.storage.fs.mgr, driver.storage.vol.mgr, driver.storage.fs.ext2.types, driver.storage.fs.ext2.core, driver.storage.fs.ext2.format, memory.heap, core.util, core.strings, proc.mgr, proc.types; @@ -62,15 +63,17 @@ begin end; procedure WorkLoop(pctx : PProcessContext); -var work : PWork; done : uint32; error : TError; callback : TIOCallback; data : pointer; +var + savedIRQ: PtrUInt; + work : PWork; done : uint32; error : TError; callback : TIOCallback; data : pointer; begin while true do begin - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; work:=head; if work<>nil then begin head:=work^.Next; if head=nil then tail:=nil; Dec(queued); end else pctx^.State:=psAwaiting; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); if work=nil then begin { Queue publication wakes the worker under the same CLI section. No per-I/O process, IRQ continuation, or volatile spin is needed. } @@ -92,7 +95,9 @@ begin worker:=proc.mgr.create('ext2.io',@WorkLoop,nil,1); end; procedure QueueTransfer(volume : PStorage_Volume; directory, name : pchar; offset : uint32; buffer : puint32; count : uint32; ctx : pointer; completed : puint32; callback : TIOCallback; data : pointer; writing : boolean); -var work : PWork; error : TError; +var + savedIRQ: PtrUInt; + work : PWork; error : TError; begin if completed<>nil then completed^:=0; error:=eNone; @@ -101,7 +106,7 @@ begin if error<>eNone then begin if callback<>nil then callback(error,data); exit; end; work:=PWork(kalloc(sizeof(TWork))); if work=nil then begin if callback<>nil then callback(eOutOfMemory,data); exit; end; - memset(uint32(work),0,sizeof(TWork)); + memset(PtrUInt(work),0,sizeof(TWork)); if ctx=nil then begin if (directory=nil) or (name=nil) then error:=eInvalidArgument else begin work^.Directory:=stringCopy(directory); work^.Name:=stringCopy(name); @@ -112,13 +117,13 @@ begin work^.Volume:=volume; work^.FileCtx:=PExt2File(ctx); work^.Offset:=offset; work^.Buffer:=buffer; work^.Count:=count; work^.Completed:=completed; work^.Writing:=writing; work^.Callback:=callback; work^.CallbackData:=data; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if queued>=64 then error:=eQueueFull else begin if tail=nil then head:=work else tail^.Next:=work; tail:=work; Inc(queued); if worker^.State=psAwaiting then worker^.State:=psReady; end; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); if error<>eNone then begin FreeWork(work); if callback<>nil then callback(error,data); end; end; @@ -177,7 +182,7 @@ var volume : PStorage_Volume; begin if device=nil then exit; volume:=PStorage_Volume(kalloc(sizeof(TStorage_Volume))); if volume=nil then exit; - memset(uint32(volume),0,sizeof(TStorage_Volume)); + memset(PtrUInt(volume),0,sizeof(TStorage_Volume)); volume^.device:=device; volume^.sectorCount:=device^.maxSectorCount; volume^.sectorSize:=device^.sectorSize; if Identify(volume) then begin volume^.filesystem:=@filesystem; @@ -187,7 +192,7 @@ end; procedure init; begin - memset(uint32(@filesystem),0,sizeof(filesystem)); + memset(PtrUInt(@filesystem),0,sizeof(filesystem)); filesystem.sName:='EXT2'; filesystem.system_id:=$83; filesystem.identifyCallback:=@Identify; filesystem.detectCallback:=@Detect; filesystem.validateFormat:=@ValidateFormat; filesystem.createCallback:=@FormatVolume; @@ -203,7 +208,4 @@ begin driver.storage.fs.mgr.register_filesystem(@filesystem); end; -initialization - boot.mgr.registerBoot('driver.storage.fs.ext2',@init,'Ext2 Filesystem','driver.storage.fs.mgr'); - boot.mgr.registerBoot('driver.storage.ext2.worker',@StartWorker,'Ext2 I/O Worker','proc.mgr'); end. diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas index 24742ac5..a38654d4 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas @@ -9,7 +9,7 @@ uses driver.storage.types, core.strings, - core.util, arch.x86.util, + core.util, arch.platform.cpu, debug.tracer, io.syslog, driver.storage.fs.fat32.types; @@ -127,11 +127,13 @@ end; { Register ofi in the volume write-ref table (call once DirLoc is valid). } procedure fatRegisterWriteHandle(volCtx : PFATVolumeCtx; ofi : PFATOpenFile); var + savedIRQ: PtrUInt; + i : uint32; freeSlot : sint32; begin if (volCtx = nil) or (ofi = nil) or ofi^.Registered then exit; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; freeSlot := -1; for i := 0 to FAT_OPEN_HANDLE_SLOTS - 1 do begin if (volCtx^.WriteRefs[i].Count > 0) and @@ -139,7 +141,7 @@ begin (volCtx^.WriteRefs[i].EntryIdx = ofi^.DirLoc.EntryIdx) then begin volCtx^.WriteRefs[i].Count := volCtx^.WriteRefs[i].Count + 1; ofi^.Registered := true; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); exit; end; if (freeSlot < 0) and (volCtx^.WriteRefs[i].Count = 0) then @@ -152,17 +154,19 @@ begin ofi^.Registered := true; end; { If all slots are full, Registered stays false → truncation skipped for safety } - asm popf end; + arch.platform.cpu.unlock(savedIRQ); end; { Unregister ofi and return the count that was present before decrement. } function fatUnregisterWriteHandle(volCtx : PFATVolumeCtx; ofi : PFATOpenFile) : uint32; var + savedIRQ: PtrUInt; + i : uint32; begin fatUnregisterWriteHandle := 0; if (volCtx = nil) or (ofi = nil) or not ofi^.Registered then exit; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; for i := 0 to FAT_OPEN_HANDLE_SLOTS - 1 do begin if (volCtx^.WriteRefs[i].Count > 0) and (volCtx^.WriteRefs[i].SectorLBA = ofi^.DirLoc.SectorLBA) and @@ -170,11 +174,11 @@ begin fatUnregisterWriteHandle := volCtx^.WriteRefs[i].Count; volCtx^.WriteRefs[i].Count := volCtx^.WriteRefs[i].Count - 1; ofi^.Registered := false; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); exit; end; end; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); end; function fatPreallocClusterCount(bytesPerCluster : uint32) : uint32; @@ -373,14 +377,14 @@ begin if extLen > 0 then begin nameBuf := pchar(kalloc(nameLen + extLen + 2)); if nameBuf = nil then exit; - memcpy(uint32(@dir^.fileName[0]), uint32(nameBuf), nameLen); + memcpy(PtrUInt(@dir^.fileName[0]), PtrUInt(nameBuf), nameLen); nameBuf[nameLen] := '.'; - memcpy(uint32(@dir^.fileExtension[0]), uint32(@nameBuf[nameLen + 1]), extLen); + memcpy(PtrUInt(@dir^.fileExtension[0]), PtrUInt(@nameBuf[nameLen + 1]), extLen); nameBuf[nameLen + extLen + 1] := char(0); end else begin nameBuf := pchar(kalloc(nameLen + 1)); if nameBuf = nil then exit; - memcpy(uint32(@dir^.fileName[0]), uint32(nameBuf), nameLen); + memcpy(PtrUInt(@dir^.fileName[0]), PtrUInt(nameBuf), nameLen); nameBuf[nameLen] := char(0); end; @@ -406,7 +410,7 @@ begin if bufSize < 512 then bufSize := 512; sectorBuf := puint32(kalloc(bufSize)); if sectorBuf = nil then exit; - memset(uint32(sectorBuf), 0, bufSize); + memset(PtrUInt(sectorBuf), 0, bufSize); if driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart, 1, sectorBuf) <> eNone then begin kfree(sectorBuf); exit; end; @@ -641,7 +645,7 @@ begin fatCtxRegister := false; node := PFATVolumeCtxNode(kalloc(sizeof(TFATVolumeCtxNode))); if node = nil then exit; - memset(uint32(node), 0, sizeof(TFATVolumeCtxNode)); + memset(PtrUInt(node), 0, sizeof(TFATVolumeCtxNode)); node^.Volume := volume; node^.Ctx := ctx; node^.Next := fatCtxListHead; @@ -651,12 +655,14 @@ end; function fatCtxDetach(volume : PStorage_Volume) : PFATVolumeCtx; var + savedIRQ: PtrUInt; + prev : PFATVolumeCtxNode; node : PFATVolumeCtxNode; begin fatCtxDetach := nil; prev := nil; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; node := fatCtxListHead; while node <> nil do begin if node^.Volume = volume then begin @@ -666,13 +672,13 @@ begin else prev^.Next := node^.Next; kfree(void(node)); - asm popf end; + arch.platform.cpu.unlock(savedIRQ); exit; end; prev := node; node := node^.Next; end; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); end; procedure fatCtxFree(ctx : PFATVolumeCtx); @@ -694,8 +700,8 @@ var ctx : PFATVolumeCtx; i : uint32; transfer : PFATTransferCtx; - poolBase : uint32; - scratchBase: uint32; + poolBase : PtrUInt; + scratchBase: PtrUInt; begin FAT32GetVolumeCtx := nil; if volume = nil then exit; @@ -708,7 +714,7 @@ begin ctx := PFATVolumeCtx(kalloc(sizeof(TFATVolumeCtx))); if ctx = nil then exit; - memset(uint32(ctx), 0, sizeof(TFATVolumeCtx)); + memset(PtrUInt(ctx), 0, sizeof(TFATVolumeCtx)); if not readBootRecordRaw(volume, ctx^.Info.BootRecord) then begin fatCtxFree(ctx); @@ -732,8 +738,8 @@ begin fatCtxFree(ctx); exit; end; - memset(uint32(ctx^.Cache.Data), 0, FAT_CACHE_LINES * 512); - memset(uint32(ctx^.Cache.EvictBuf), 0, 512); + memset(PtrUInt(ctx^.Cache.Data), 0, FAT_CACHE_LINES * 512); + memset(PtrUInt(ctx^.Cache.EvictBuf), 0, 512); ctx^.Cache.Busy := false; ctx^.Cache.FatStart := volume^.sectorStart + ctx^.Info.BootRecord.rsvSectors; ctx^.Cache.Device := volume^.device; @@ -746,10 +752,10 @@ begin ctx^.TransferPoolBuf := kalloc(FAT_TRANSFER_POOL_CAPACITY * sizeof(TFATTransferCtx)); ctx^.TransferScratchBuf := kalloc(FAT_TRANSFER_POOL_CAPACITY * ctx^.Info.BootRecord.sectorSize); if (ctx^.TransferPoolBuf <> nil) and (ctx^.TransferScratchBuf <> nil) then begin - memset(uint32(ctx^.TransferPoolBuf), 0, FAT_TRANSFER_POOL_CAPACITY * sizeof(TFATTransferCtx)); - memset(uint32(ctx^.TransferScratchBuf), 0, FAT_TRANSFER_POOL_CAPACITY * ctx^.Info.BootRecord.sectorSize); - poolBase := uint32(ctx^.TransferPoolBuf); - scratchBase := uint32(ctx^.TransferScratchBuf); + memset(PtrUInt(ctx^.TransferPoolBuf), 0, FAT_TRANSFER_POOL_CAPACITY * sizeof(TFATTransferCtx)); + memset(PtrUInt(ctx^.TransferScratchBuf), 0, FAT_TRANSFER_POOL_CAPACITY * ctx^.Info.BootRecord.sectorSize); + poolBase := PtrUInt(ctx^.TransferPoolBuf); + scratchBase := PtrUInt(ctx^.TransferScratchBuf); if FAT_TRANSFER_POOL_CAPACITY > 0 then for i := 0 to FAT_TRANSFER_POOL_CAPACITY - 1 do begin transfer := PFATTransferCtx(poolBase + (i * sizeof(TFATTransferCtx))); @@ -781,6 +787,8 @@ end; procedure fatCacheFlush(cache : PFATCache; volume : PStorage_Volume); var + savedIRQ: PtrUInt; + i : uint32; lba : uint32; doFlush : boolean; @@ -788,33 +796,33 @@ begin if cache = nil then exit; while true do begin - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if not cache^.Busy then begin Inc(cache^.Device^.Stats.FatCacheMisses); cache^.Busy := true; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); break; end; - asm popf end; - asm hlt end; + arch.platform.cpu.unlock(savedIRQ); + arch.platform.cpu.idle; end; for i := 0 to FAT_CACHE_LINES - 1 do begin - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; doFlush := cache^.Lines[i].Valid and cache^.Lines[i].Dirty; if doFlush then begin lba := cache^.FatStart + cache^.Lines[i].SectorIdx; cache^.Lines[i].Dirty := false; - memcpy(uint32(cache^.Data) + (i * 512), uint32(cache^.EvictBuf), 512); + memcpy(PtrUInt(cache^.Data) + (i * 512), PtrUInt(cache^.EvictBuf), 512); end; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); if doFlush then driver.storage.mgr.storage_write(volume^.device, lba, 1, cache^.EvictBuf); end; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; cache^.Busy := false; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); end; procedure FAT32Flush(volume : PStorage_Volume); @@ -839,6 +847,8 @@ end; function readFat(volume : PStorage_Volume; cluster : uint32; bootRecord : PBootRecord) : uint32; var + savedIRQ: PtrUInt; + ctx : PFATVolumeCtx; cache : PFATCache; fatSecIdx : uint32; @@ -863,45 +873,45 @@ begin baseSlot := setIdx * FAT_CACHE_WAYS; entryOff := cluster mod 128; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; for w := 0 to FAT_CACHE_WAYS - 1 do if cache^.Lines[baseSlot + w].Valid and (cache^.Lines[baseSlot + w].SectorIdx = fatSecIdx) then begin Inc(cache^.Device^.Stats.FatCacheHits); - dataPtr := puint32(uint32(cache^.Data) + ((baseSlot + w) * 512)); + dataPtr := puint32(PtrUInt(cache^.Data) + ((baseSlot + w) * 512)); readFat := dataPtr[entryOff] and $0FFFFFFF; cacheTouch(cache,baseSlot,baseSlot+w); - asm popf end; + arch.platform.cpu.unlock(savedIRQ); exit; end; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); while true do begin - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if not cache^.Busy then begin for w := 0 to FAT_CACHE_WAYS - 1 do if cache^.Lines[baseSlot + w].Valid and (cache^.Lines[baseSlot + w].SectorIdx = fatSecIdx) then begin Inc(cache^.Device^.Stats.FatCacheHits); - dataPtr := puint32(uint32(cache^.Data) + ((baseSlot + w) * 512)); + dataPtr := puint32(PtrUInt(cache^.Data) + ((baseSlot + w) * 512)); readFat := dataPtr[entryOff] and $0FFFFFFF; cacheTouch(cache,baseSlot,baseSlot+w); - asm popf end; + arch.platform.cpu.unlock(savedIRQ); exit; end; Inc(cache^.Device^.Stats.FatCacheMisses); cache^.Busy := true; victim := cacheVictim(cache,baseSlot); - dataPtr := puint32(uint32(cache^.Data) + (victim * 512)); + dataPtr := puint32(PtrUInt(cache^.Data) + (victim * 512)); doEvict := cache^.Lines[victim].Valid and cache^.Lines[victim].Dirty; if doEvict then begin evictLBA := cache^.FatStart + cache^.Lines[victim].SectorIdx; - memcpy(uint32(dataPtr), uint32(cache^.EvictBuf), 512); + memcpy(PtrUInt(dataPtr), PtrUInt(cache^.EvictBuf), 512); end; cache^.Lines[victim].Valid := false; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); break; end; - asm popf end; - asm hlt end; + arch.platform.cpu.unlock(savedIRQ); + arch.platform.cpu.idle; end; if doEvict then @@ -909,18 +919,20 @@ begin lba := cache^.FatStart + fatSecIdx; driver.storage.mgr.storage_read(cache^.Device, lba, 1, dataPtr); - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; cache^.Lines[victim].SectorIdx := fatSecIdx; cache^.Lines[victim].Valid := true; cache^.Lines[victim].Dirty := false; cacheTouch(cache,baseSlot,victim); readFat := dataPtr[entryOff] and $0FFFFFFF; cache^.Busy := false; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); end; procedure writeFat(volume : PStorage_Volume; cluster : uint32; value : uint32; bootRecord : PBootRecord); var + savedIRQ: PtrUInt; + ctx : PFATVolumeCtx; cache : PFATCache; fatSecIdx : uint32; @@ -942,53 +954,53 @@ begin baseSlot := setIdx * FAT_CACHE_WAYS; entryOff := cluster mod 128; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; for w := 0 to FAT_CACHE_WAYS - 1 do if cache^.Lines[baseSlot + w].Valid and (cache^.Lines[baseSlot + w].SectorIdx = fatSecIdx) then begin Inc(cache^.Device^.Stats.FatCacheHits); - dataPtr := puint32(uint32(cache^.Data) + ((baseSlot + w) * 512)); + dataPtr := puint32(PtrUInt(cache^.Data) + ((baseSlot + w) * 512)); if value = 0 then dataPtr[entryOff] := 0 else dataPtr[entryOff] := (dataPtr[entryOff] and $F0000000) or (value and $0FFFFFFF); cache^.Lines[baseSlot + w].Dirty := true; cacheTouch(cache,baseSlot,baseSlot+w); - asm popf end; + arch.platform.cpu.unlock(savedIRQ); exit; end; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); while true do begin - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if not cache^.Busy then begin for w := 0 to FAT_CACHE_WAYS - 1 do if cache^.Lines[baseSlot + w].Valid and (cache^.Lines[baseSlot + w].SectorIdx = fatSecIdx) then begin Inc(cache^.Device^.Stats.FatCacheHits); - dataPtr := puint32(uint32(cache^.Data) + ((baseSlot + w) * 512)); + dataPtr := puint32(PtrUInt(cache^.Data) + ((baseSlot + w) * 512)); if value = 0 then dataPtr[entryOff] := 0 else dataPtr[entryOff] := (dataPtr[entryOff] and $F0000000) or (value and $0FFFFFFF); cache^.Lines[baseSlot + w].Dirty := true; cacheTouch(cache,baseSlot,baseSlot+w); - asm popf end; + arch.platform.cpu.unlock(savedIRQ); exit; end; Inc(cache^.Device^.Stats.FatCacheMisses); cache^.Busy := true; victim := cacheVictim(cache,baseSlot); - dataPtr := puint32(uint32(cache^.Data) + (victim * 512)); + dataPtr := puint32(PtrUInt(cache^.Data) + (victim * 512)); doEvict := cache^.Lines[victim].Valid and cache^.Lines[victim].Dirty; if doEvict then begin evictLBA := cache^.FatStart + cache^.Lines[victim].SectorIdx; - memcpy(uint32(dataPtr), uint32(cache^.EvictBuf), 512); + memcpy(PtrUInt(dataPtr), PtrUInt(cache^.EvictBuf), 512); end; cache^.Lines[victim].Valid := false; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); break; end; - asm popf end; - asm hlt end; + arch.platform.cpu.unlock(savedIRQ); + arch.platform.cpu.idle; end; if doEvict then @@ -996,7 +1008,7 @@ begin lba := cache^.FatStart + fatSecIdx; driver.storage.mgr.storage_read(cache^.Device, lba, 1, dataPtr); - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; cache^.Lines[victim].SectorIdx := fatSecIdx; cache^.Lines[victim].Valid := true; cache^.Lines[victim].Dirty := false; @@ -1007,7 +1019,7 @@ begin cache^.Lines[victim].Dirty := true; cacheTouch(cache,baseSlot,victim); cache^.Busy := false; - asm popf end; + arch.platform.cpu.unlock(savedIRQ); end; function runMapEnsureCapacity(var runMap : TFATRunMap; needed : uint32) : boolean; @@ -1027,9 +1039,9 @@ begin runMapEnsureCapacity := false; exit; end; - memset(uint32(newRuns), 0, newCapacity * sizeof(TFATRun)); + memset(PtrUInt(newRuns), 0, newCapacity * sizeof(TFATRun)); if (runMap.Runs <> nil) and (runMap.Count > 0) then - memcpy(uint32(runMap.Runs), uint32(newRuns), runMap.Count * sizeof(TFATRun)); + memcpy(PtrUInt(runMap.Runs), PtrUInt(newRuns), runMap.Count * sizeof(TFATRun)); if runMap.Runs <> nil then kfree(void(runMap.Runs)); runMap.Runs := newRuns; @@ -1358,7 +1370,7 @@ begin if (parts <> nil) and (LL_Size(parts) > 0) then for i := 0 to LL_Size(parts) - 1 do begin - partName := pchar(puint32(LL_Get(parts, i))^); + partName := STRLL_Get(parts, i); if (partName = nil) or (partName[0] = char(0)) then continue; splitFileNameParts(partName, namePart, extPart); @@ -1413,7 +1425,7 @@ begin end; rawDir := @PDirectory(sectorBuf)[ofi^.DirLoc.EntryIdx]; - memset(uint32(rawDir), 0, sizeof(TDirectory)); + memset(PtrUInt(rawDir), 0, sizeof(TDirectory)); rawDir^.fileName := ofi^.CleanName; rawDir^.attributes := 0; fillFatExt(rawDir^.fileExtension, ofi^.ExtPart); @@ -1608,7 +1620,9 @@ function FAT32FindRunForOffset(ofi : PFATOpenFile; fileOffset : uint32; var outRunIdx : uint32; var outRunFileCluster : uint32; var outStartCluster : uint32; var outClusterOffset : uint32; var outAvailableBytes : uint32) : boolean; -var target, i, lo, hi, mid, base, bpc, flags : uint32; +var + irqFlags: PtrUInt; + target, i, lo, hi, mid, base, bpc, flags : uint32; begin FAT32FindRunForOffset := false; if (ofi = nil) or (ofi^.VolumeInfo = nil) or (ofi^.RunMap.Count = 0) then exit; @@ -1641,9 +1655,9 @@ begin outClusterOffset := target-base; outStartCluster := ofi^.RunMap.Runs[i].StartCluster + outClusterOffset; outAvailableBytes := (ofi^.RunMap.Runs[i].ClusterCount-outClusterOffset)*bpc - (fileOffset mod bpc); - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; ofi^.Cursor.RunIdx := i; ofi^.Cursor.FileClusterStart := base; ofi^.Cursor.Valid := true; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); FAT32FindRunForOffset := true; end; @@ -1711,6 +1725,12 @@ begin ofi^.FirstCluster := firstNew; end else begin tempRuns := findFreeClusterRuns(ofi^.Volume, ofi^.VolumeInfo, extraClusters); + { Preallocation is optional; a small write can still fit on a nearly + full volume when the preferred reserve does not. } + if (tempRuns = nil) and (targetClusters > needClusters) then begin + extraClusters := needClusters - ofi^.AllocClusters; + tempRuns := findFreeClusterRuns(ofi^.Volume, ofi^.VolumeInfo, extraClusters); + end; if tempRuns = nil then begin FAT32EnsureCapacityForWrite := eDiskFull; exit; @@ -1749,6 +1769,8 @@ end; function FAT32TransferAlloc(volume : PStorage_Volume) : PFATTransferCtx; var + savedIRQ: PtrUInt; + volCtx : PFATVolumeCtx; ctx : PFATTransferCtx; idx : uint32; @@ -1757,32 +1779,34 @@ begin volCtx := FAT32GetVolumeCtx(volume); if volCtx = nil then exit; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; ctx := PFATTransferCtx(volCtx^.TransferFreeList); if ctx <> nil then volCtx^.TransferFreeList := pointer(ctx^.Next); - asm popf end; + arch.platform.cpu.unlock(savedIRQ); if ctx = nil then exit; - idx := (uint32(ctx) - uint32(volCtx^.TransferPoolBuf)) div sizeof(TFATTransferCtx); - memset(uint32(ctx), 0, sizeof(TFATTransferCtx)); + idx := (PtrUInt(ctx) - PtrUInt(volCtx^.TransferPoolBuf)) div sizeof(TFATTransferCtx); + memset(PtrUInt(ctx), 0, sizeof(TFATTransferCtx)); ctx^.VolumeCtx := volCtx; - ctx^.ScratchSector := puint32(uint32(volCtx^.TransferScratchBuf) + (idx * volCtx^.Info.BootRecord.sectorSize)); + ctx^.ScratchSector := puint32(PtrUInt(volCtx^.TransferScratchBuf) + (idx * volCtx^.Info.BootRecord.sectorSize)); ctx^.ScratchSize := volCtx^.Info.BootRecord.sectorSize; FAT32TransferAlloc := ctx; end; procedure FAT32TransferFree(ctx : PFATTransferCtx); var + savedIRQ: PtrUInt; + volCtx : PFATVolumeCtx; begin if ctx = nil then exit; volCtx := ctx^.VolumeCtx; if volCtx = nil then exit; - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; ctx^.Next := PFATTransferCtx(volCtx^.TransferFreeList); volCtx^.TransferFreeList := pointer(ctx); - asm popf end; + arch.platform.cpu.unlock(savedIRQ); end; function FAT32OpenFile(volume : PStorage_Volume; directory : pchar; fileName : pchar; var fileSize : uint32) : pointer; @@ -1805,14 +1829,14 @@ begin ofi := PFATOpenFile(kalloc(sizeof(TFATOpenFile))); if ofi = nil then exit; - memset(uint32(ofi), 0, sizeof(TFATOpenFile)); + memset(PtrUInt(ofi), 0, sizeof(TFATOpenFile)); ofi^.Volume := volume; ofi^.VolumeInfo := info; ofi^.ScratchSize := info^.BootRecord.sectorSize; if ofi^.ScratchSize > 0 then begin ofi^.ScratchSector := puint32(kalloc(ofi^.ScratchSize)); if ofi^.ScratchSector <> nil then - memset(uint32(ofi^.ScratchSector), 0, ofi^.ScratchSize); + memset(PtrUInt(ofi^.ScratchSector), 0, ofi^.ScratchSize); end; splitFileNameParts(fileName, namePart, extPart); @@ -2107,7 +2131,7 @@ begin clusterLBA := FAT32ClusterToLBA(info, cluster); for ds := 0 to info^.BootRecord.spc - 1 do begin - memset(uint32(buffer), 0, info^.BootRecord.sectorSize); + memset(PtrUInt(buffer), 0, info^.BootRecord.sectorSize); if ds = 0 then begin dir := @PDirectory(buffer)[0]; dir^.fileName[0] := '.'; @@ -2217,7 +2241,7 @@ begin exit; end; outEntry := PDirectory_Entry(slot); - memset(uint32(outEntry), 0, sizeof(TDirectory_Entry)); + memset(PtrUInt(outEntry), 0, sizeof(TDirectory_Entry)); outEntry^.fileName := fatEntryName(rawDir); if outEntry^.fileName = nil then begin fatFreeGenericEntries(entries); @@ -2336,7 +2360,7 @@ begin end; rawDir := @PDirectory(sectorBuf)[loc.EntryIdx]; - memset(uint32(rawDir), 0, sizeof(TDirectory)); + memset(PtrUInt(rawDir), 0, sizeof(TDirectory)); rawDir^.fileName := cleanName; rawDir^.attributes := attributes; fillFatExt(rawDir^.fileExtension, extPart); diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas index dc3af03e..3e7600ab 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas @@ -14,7 +14,7 @@ function normalize_label(text : pchar; var normalized : TVolumeLabel) : TError; var i, n, first : uint32; c : char; begin normalize_label := eInvalidArgument; - memset(uint32(@normalized[0]), 0, sizeof(normalized)); + memset(PtrUInt(@normalized[0]), 0, sizeof(normalized)); if text = nil then exit; n := 0; while (n < 12) and (text[n] <> #0) do Inc(n); @@ -37,7 +37,7 @@ end; procedure unpack_label(src : pchar; var dest : TVolumeLabel); var n, i : uint32; begin - memset(uint32(@dest[0]), 0, sizeof(dest)); + memset(PtrUInt(@dest[0]), 0, sizeof(dest)); n := 11; while (n > 0) and ((src[n-1] = ' ') or (src[n-1] = #0)) do Dec(n); for i := 0 to 10 do if i < n then dest[i] := src[i]; end; @@ -62,7 +62,7 @@ begin br := PBootRecord(buffer); lim := FAT32ClusterLimit(br); if (lim <= 2) or (br^.manySectors = 0) or (br^.manySectors > volume^.sectorCount) or (br^.rootCluster < 2) or (br^.rootCluster >= lim) or - (puint16(uint32(buffer)+510)^ <> $AA55) then load_boot := eCorruptFilesystem; + (puint16(PtrUInt(buffer)+510)^ <> $AA55) then load_boot := eCorruptFilesystem; end; { Bounded walk of the root FAT chain. A label is authoritative when present. @@ -113,12 +113,12 @@ var mem, boot, scratch : puint32; sector, offset, i : uint32; found, noName : bo begin mem := puint32(kalloc(1024)); if mem = nil then begin read_label := eOutOfMemory; exit; end; - boot := mem; scratch := puint32(uint32(mem)+512); + boot := mem; scratch := puint32(PtrUInt(mem)+512); read_label := load_boot(volume,boot); if read_label = eNone then begin read_label := locate_label(volume,PBootRecord(boot),scratch,sector,offset,found); if read_label = eNone then begin - if found then unpack_label(pchar(uint32(scratch)+offset),value) + if found then unpack_label(pchar(PtrUInt(scratch)+offset),value) else begin unpack_label(pchar(@PBootRecord(boot)^.volumeLabel[0]),value); noName := true; @@ -142,8 +142,8 @@ begin if not volume^.device^.writable then begin write_label := eReadOnly; exit; end; mem := puint32(kalloc(5*512)); if mem = nil then begin write_label := eOutOfMemory; exit; end; - boot := mem; scratch := puint32(uint32(mem)+512); rootOld := puint32(uint32(mem)+1024); - backupOld := puint32(uint32(mem)+1536); bootOld := puint32(uint32(mem)+2048); + boot := mem; scratch := puint32(PtrUInt(mem)+512); rootOld := puint32(PtrUInt(mem)+1024); + backupOld := puint32(PtrUInt(mem)+1536); bootOld := puint32(PtrUInt(mem)+2048); bootWritten := false; backupWritten := false; rootWritten := false; backup := 0; repeat write_label := load_boot(volume,boot); if write_label <> eNone then break; @@ -153,17 +153,17 @@ begin if (sector = 0) and (value[0] <> #0) then begin write_label := eDiskFull; break; end; if sector <> 0 then begin write_label := storage_read(volume^.device,sector,1,scratch); if write_label <> eNone then break; - memcpy(uint32(scratch),uint32(rootOld),512); - entry := PDirectory(uint32(scratch)+offset); + memcpy(PtrUInt(scratch),PtrUInt(rootOld),512); + entry := PDirectory(PtrUInt(scratch)+offset); if value[0] = #0 then begin if found then entry^.fileName[0] := char($E5); end else begin if entry^.fileName[0] = #0 then begin { Retain an end marker after a newly inserted label. } if offset = 480 then begin write_label := eDiskFull; break; end; - puint8(uint32(entry)+32)^ := 0; + puint8(PtrUInt(entry)+32)^ := 0; end; - memset(uint32(entry),0,32); pack_label(@value[0],@entry^.fileName[0]); entry^.attributes := $08; + memset(PtrUInt(entry),0,32); pack_label(@value[0],@entry^.fileName[0]); entry^.attributes := $08; end; end; if (br^.backupCluster <> 0) and (br^.backupCluster <> $FFFF) then begin @@ -171,7 +171,7 @@ begin backup := volume^.sectorStart+br^.backupCluster; write_label := storage_read(volume^.device,backup,1,backupOld); if write_label <> eNone then break; end; - memcpy(uint32(boot),uint32(bootOld),512); + memcpy(PtrUInt(boot),PtrUInt(bootOld),512); if value[0] = #0 then pack_label('NO NAME',pchar(@br^.volumeLabel[0])) else pack_label(@value[0],pchar(@br^.volumeLabel[0])); bootWritten := true; @@ -180,7 +180,7 @@ begin bootWritten := true; if backup <> 0 then begin { Preserve backup-specific bytes; only update its label. } - memcpy(uint32(backupOld),uint32(boot),512); + memcpy(PtrUInt(backupOld),PtrUInt(boot),512); if value[0] = #0 then pack_label('NO NAME',pchar(@br^.volumeLabel[0])) else pack_label(@value[0],pchar(@br^.volumeLabel[0])); backupWritten := true; diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas index 449cd930..4d1bf6d3 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas @@ -3,7 +3,6 @@ unit driver.storage.fs.fat32; interface uses - boot.mgr, driver.storage.fs.mgr, driver.storage.types; @@ -31,7 +30,7 @@ end; procedure init; begin io.syslog.logln('FAT32', 'init: registering FAT32 filesystem'); - memset(uint32(@filesystem), 0, sizeof(TFilesystem)); + memset(PtrUInt(@filesystem), 0, sizeof(TFilesystem)); filesystem.sName := 'FAT32'; filesystem.system_id := $0B; filesystem.readDirCallback := @FAT32ReadDirectory; @@ -61,7 +60,4 @@ begin driver.storage.fs.mgr.register_filesystem(@filesystem); end; -initialization - boot.mgr.registerBoot('driver.storage.fs.fat32', @init, 'FAT32 Filesystem', 'driver.storage.fs.mgr'); - end. diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.transfer.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.transfer.pas index d82e6b88..05eb4c36 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.transfer.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.transfer.pas @@ -21,7 +21,7 @@ implementation uses proc.mgr, proc.types, driver.storage.mgr, - core.util, arch.x86.util, + core.util, arch.platform.cpu, memory.heap, driver.storage.fs.fat32.types, driver.storage.fs.fat32.core; @@ -86,7 +86,7 @@ begin sectorLBA := FAT32ClusterToLBA(ofi^.VolumeInfo, startCluster) + (runByteOffset div sectorSize); if (sectorOffset <> 0) or (remaining < sectorSize) or - (((uint32(buffer)+bytesDone) and 1) <> 0) then begin + (((PtrUInt(buffer)+bytesDone) and 1) <> 0) then begin if (scratch = nil) or (scratchSize < sectorSize) then break; chunkBytes := sectorSize - sectorOffset; if chunkBytes > remaining then @@ -94,9 +94,9 @@ begin err := driver.storage.mgr.storage_read(ofi^.Volume^.device, sectorLBA, 1, scratch); if err <> eNone then break; if mode = ftmRead then - memcpy(uint32(scratch) + sectorOffset, uint32(buffer) + bytesDone, chunkBytes) + memcpy(PtrUInt(scratch) + sectorOffset, PtrUInt(buffer) + bytesDone, chunkBytes) else begin - memcpy(uint32(buffer) + bytesDone, uint32(scratch) + sectorOffset, chunkBytes); + memcpy(PtrUInt(buffer) + bytesDone, PtrUInt(scratch) + sectorOffset, chunkBytes); err := driver.storage.mgr.storage_write(ofi^.Volume^.device, sectorLBA, 1, scratch); if err <> eNone then break; end; @@ -112,10 +112,10 @@ begin chunkBytes := chunkSectors * sectorSize; if mode = ftmRead then err := driver.storage.mgr.storage_read(ofi^.Volume^.device, sectorLBA, chunkSectors, - puint32(uint32(buffer) + bytesDone)) + puint32(PtrUInt(buffer) + bytesDone)) else err := driver.storage.mgr.storage_write(ofi^.Volume^.device, sectorLBA, chunkSectors, - puint32(uint32(buffer) + bytesDone)); + puint32(PtrUInt(buffer) + bytesDone)); end; if err <> eNone then break; @@ -166,13 +166,13 @@ begin end; ftpAwaitScratchRead: begin if ctx^.Mode = ftmRead then begin - memcpy(uint32(ctx^.ScratchSector) + ctx^.PendingScratchOffset, - uint32(ctx^.Buffer) + ctx^.PendingBufferOffset, ctx^.PendingScratchBytes); + memcpy(PtrUInt(ctx^.ScratchSector) + ctx^.PendingScratchOffset, + PtrUInt(ctx^.Buffer) + ctx^.PendingBufferOffset, ctx^.PendingScratchBytes); ctx^.BytesDone := ctx^.BytesDone + ctx^.PendingScratchBytes; asyncContinue(ctx); end else begin - memcpy(uint32(ctx^.Buffer) + ctx^.PendingBufferOffset, - uint32(ctx^.ScratchSector) + ctx^.PendingScratchOffset, ctx^.PendingScratchBytes); + memcpy(PtrUInt(ctx^.Buffer) + ctx^.PendingBufferOffset, + PtrUInt(ctx^.ScratchSector) + ctx^.PendingScratchOffset, ctx^.PendingScratchBytes); ctx^.Phase := ftpAwaitScratchWrite; if driver.storage.mgr.storage_write_async( ctx^.Volume^.device, ctx^.PendingLBA, 1, ctx^.ScratchSector, @asyncStep, ctx) <> eNone then @@ -237,7 +237,7 @@ begin ((ctx^.Offset+pos) mod ctx^.OpenFile^.VolumeInfo^.BytesPerCluster) div size; Inc(ctx^.ReadPending); err := driver.storage.mgr.storage_read_async(ctx^.Volume^.device,lba,sectors, - puint32(uint32(ctx^.Buffer)+pos),@readSlotDone,slot); + puint32(PtrUInt(ctx^.Buffer)+pos),@readSlotDone,slot); if err <> eNone then begin Dec(ctx^.ReadPending); slot^.Error := err; break; end; if slot^.Error <> eNone then break; { synchronous completion failure } Inc(pos,bytes); @@ -290,7 +290,7 @@ begin ctx^.PendingBufferOffset := ctx^.BytesDone; if (sectorOffset <> 0) or (remaining < sectorSize) or - (((uint32(ctx^.Buffer)+ctx^.BytesDone) and 1) <> 0) then begin + (((PtrUInt(ctx^.Buffer)+ctx^.BytesDone) and 1) <> 0) then begin chunkBytes := sectorSize - sectorOffset; if chunkBytes > remaining then chunkBytes := remaining; @@ -323,10 +323,10 @@ begin if ctx^.Mode = ftmRead then err := driver.storage.mgr.storage_read_async(ctx^.Volume^.device, ctx^.PendingLBA, chunkSectors, - puint32(uint32(ctx^.Buffer) + ctx^.PendingBufferOffset), @asyncStep, ctx) + puint32(PtrUInt(ctx^.Buffer) + ctx^.PendingBufferOffset), @asyncStep, ctx) else err := driver.storage.mgr.storage_write_async(ctx^.Volume^.device, ctx^.PendingLBA, chunkSectors, - puint32(uint32(ctx^.Buffer) + ctx^.PendingBufferOffset), @asyncStep, ctx); + puint32(PtrUInt(ctx^.Buffer) + ctx^.PendingBufferOffset), @asyncStep, ctx); if err <> eNone then asyncComplete(ctx, err); end; @@ -334,9 +334,11 @@ end; { Serialize pump state against IRQ completion and turn inline completions into iteration, so fake/RAM drivers cannot recursively exhaust the stack. } procedure asyncContinue(ctx : PFATTransferCtx); -var flags : uint32; +var + irqFlags: PtrUInt; + flags : uint32; begin - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; if ctx^.Pumping then ctx^.Resume := true else begin ctx^.Pumping := true; @@ -347,7 +349,7 @@ begin ctx^.Pumping := false; if ctx^.Finished then asyncComplete(ctx,ctx^.LastError); end; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); end; type @@ -364,6 +366,8 @@ end; function FAT32ReadFileAtOffset(volume : PStorage_Volume; directory : pchar; fileName : pchar; offset : uint32; buffer : puint32; byteCount : uint32; ctx : pointer) : uint32; var + savedIRQ: PtrUInt; + ofi : PFATOpenFile; tempCtx : pointer; fileSize : uint32; @@ -386,10 +390,10 @@ begin wait.Done := 0; wait.Bytes := 0; wait.Process := CurrentProcess; FAT32ReadFileAtOffsetAsync(volume,directory,fileName,offset,buffer,readLimit,ofi, @wait.Bytes,@readWaitDone,@wait); - asm pushf; cli end; + savedIRQ := arch.platform.cpu.lock; if (puint32(@wait.Done)^ = 0) and (CurrentProcess <> nil) then CurrentProcess^.State := psAwaiting; - asm popf end; - while puint32(@wait.Done)^ = 0 do asm hlt end; + arch.platform.cpu.unlock(savedIRQ); + while puint32(@wait.Done)^ = 0 do arch.platform.cpu.idle; FAT32ReadFileAtOffset := wait.Bytes; end; diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas index 89e01cc1..f8539b6e 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas @@ -9,7 +9,7 @@ function validate_format(volume : PStorage_Volume; sectors, start : uint32; conf function create_volume(volume : PStorage_Volume; sectors, start : uint32; config : PFSFormatParams) : TError; implementation -uses io.syslog, debug.tracer, memory.heap, core.util, arch.x86.util, +uses io.syslog, debug.tracer, memory.heap, core.util, arch.platform.cpu, driver.storage.mgr, driver.storage.vol.mgr, driver.storage.fs.fat32.labels; var fat32FilesystemPtr : PFilesystem = nil; @@ -18,8 +18,8 @@ begin fat32FilesystemPtr := fs; end; function bootSectorHasSignature(buffer : puint32) : boolean; begin - bootSectorHasSignature := (puint8(uint32(buffer)+510)^ = $55) and - (puint8(uint32(buffer)+511)^ = $AA); + bootSectorHasSignature := (puint8(PtrUInt(buffer)+510)^ = $55) and + (puint8(PtrUInt(buffer)+511)^ = $AA); end; function formatSPC(config : PFSFormatParams) : uint32; @@ -123,7 +123,7 @@ begin if bufSize < 512 then bufSize := 512; buffer := puint32(kalloc(bufSize)); if buffer = nil then exit; - memset(uint32(buffer), 0, bufSize); + memset(PtrUInt(buffer), 0, bufSize); if disk^.dispatchRead = nil then begin io.syslog.logln('FAT32', 'detect_volumes: device has no read dispatch'); @@ -142,7 +142,7 @@ begin kfree(buffer); exit; end; - memset(uint32(volume), 0, sizeof(TStorage_Volume)); + memset(PtrUInt(volume), 0, sizeof(TStorage_Volume)); volume^.device := disk; volume^.sectorStart := 0; volume^.sectorSize := br^.sectorSize; @@ -179,7 +179,7 @@ begin if bufSize < 512 then bufSize := 512; buffer := puint32(kalloc(bufSize)); if buffer = nil then exit; - memset(uint32(buffer), 0, bufSize); + memset(PtrUInt(buffer), 0, bufSize); if storage_read(volume^.device, volume^.sectorStart, 1, buffer) <> eNone then begin kfree(buffer); exit; end; br := PBootRecord(buffer); @@ -236,7 +236,7 @@ var begin create_volume := validate_format(volume, sectors, start, config); if create_volume <> eNone then exit; - memset(uint32(@labelValue[0]),0,sizeof(labelValue)); + memset(PtrUInt(@labelValue[0]),0,sizeof(labelValue)); if (config <> nil) and ((config^.Flags and FS_FORMAT_PARAM_LABEL) <> 0) then normalize_label(@config^.LabelText[0],labelValue); spc := formatSPC(config); fat := formatFATSize(sectors, spc); @@ -247,20 +247,20 @@ begin if buffer <> nil then kfree(buffer); create_volume := eOutOfMemory; exit; end; - memset(uint32(boot), 0, 512); memset(uint32(buffer), 0, 128*512); + memset(PtrUInt(boot), 0, 512); memset(PtrUInt(buffer), 0, 128*512); br := PBootRecord(boot); br^.sectorSize := 512; br^.spc := uint8(spc); br^.rsvSectors := 32; br^.numFats := 1; br^.mediaDescp := $F8; br^.hiddenSectors := start; br^.manySectors := sectors; br^.FATSize := fat; br^.rootCluster := 2; br^.driveNumber := $80; br^.volumeID := 62; br^.bsignature := $29; - memcpy(uint32(pchar('ASURO ')), uint32(@br^.OEMName[0]), 8); - memcpy(uint32(pchar('FAT32 ')), uint32(@br^.identString[0]), 8); - memcpy(uint32(pchar('NO NAME ')), uint32(@br^.volumeLabel[0]), 11); + memcpy(PtrUInt(pchar('ASURO ')), PtrUInt(@br^.OEMName[0]), 8); + memcpy(PtrUInt(pchar('FAT32 ')), PtrUInt(@br^.identString[0]), 8); + memcpy(PtrUInt(pchar('NO NAME ')), PtrUInt(@br^.volumeLabel[0]), 11); if labelValue[0] <> #0 then begin - memset(uint32(@br^.volumeLabel[0]),ord(' '),11); + memset(PtrUInt(@br^.volumeLabel[0]),ord(' '),11); i := 0; while (i < 11) and (labelValue[i] <> #0) do begin br^.volumeLabel[i] := ord(labelValue[i]); Inc(i); end; end; - puint8(uint32(boot)+510)^ := $55; puint8(uint32(boot)+511)^ := $AA; + puint8(PtrUInt(boot)+510)^ := $55; puint8(PtrUInt(boot)+511)^ := $AA; err := eNone; repeat { Invalidate the old boot signature before modifying allocation metadata. } @@ -274,20 +274,20 @@ begin buffer[0] := $0FFFFFF8; buffer[1] := $0FFFFFFF; buffer[2] := $0FFFFFFF; buffer[3] := $0FFFFFFF; if not writeSectors(fatStart, 1, buffer) then break; - memset(uint32(buffer), 0, 128*512); + memset(PtrUInt(buffer), 0, 128*512); dir := PDirectory(buffer); if labelValue[0] <> #0 then begin - memcpy(uint32(@br^.volumeLabel[0]),uint32(@dir^.fileName[0]),11); + memcpy(PtrUInt(@br^.volumeLabel[0]),PtrUInt(@dir^.fileName[0]),11); dir^.attributes := $08; dir := @PDirectory(buffer)[1]; end; - memcpy(uint32(pchar('SYSTEM ')), uint32(@dir^.fileName[0]), 11); + memcpy(PtrUInt(pchar('SYSTEM ')), PtrUInt(@dir^.fileName[0]), 11); dir^.attributes := $10; dir^.clusterLow := 3; if not writeSectors(dataStart, spc, buffer) then break; - memset(uint32(buffer), 0, 128*512); + memset(PtrUInt(buffer), 0, 128*512); dir := PDirectory(buffer); - memcpy(uint32(pchar('. ')), uint32(@dir[0].fileName[0]), 11); + memcpy(PtrUInt(pchar('. ')), PtrUInt(@dir[0].fileName[0]), 11); dir[0].attributes := $10; dir[0].clusterLow := 3; - memcpy(uint32(pchar('.. ')), uint32(@dir[1].fileName[0]), 11); + memcpy(PtrUInt(pchar('.. ')), PtrUInt(@dir[1].fileName[0]), 11); dir[1].attributes := $10; dir[1].clusterLow := 0; { parent is root } if not writeSectors(dataStart+spc, spc, buffer) then break; writeSectors(start, 1, boot); diff --git a/src/driver/storage/vol/driver.storage.vol.gpt.pas b/src/driver/storage/vol/driver.storage.vol.gpt.pas index 10dcf511..c74d2b68 100644 --- a/src/driver/storage/vol/driver.storage.vol.gpt.pas +++ b/src/driver/storage/vol/driver.storage.vol.gpt.pas @@ -31,7 +31,7 @@ uses driver.storage.types, io.syslog, debug.tracer, - core.util, arch.x86.util; + core.util, arch.platform.cpu; const GPT_SIGNATURE : array[0..7] of char = ('E','F','I',' ','P','A','R','T'); @@ -184,7 +184,7 @@ begin read_sector := nil; buf := puint32(kalloc(device^.sectorSize)); if buf = nil then exit; - memset(uint32(buf), 0, device^.sectorSize); + memset(PtrUInt(buf), 0, device^.sectorSize); err := driver.storage.mgr.storage_read(device, lba, 1, buf); if err <> eNone then begin kfree(buf); @@ -211,7 +211,7 @@ begin { Copy to a tightly-sized allocation } gpt_read_header := PGPTHeader(kalloc(sizeof(TGPTHeader))); if gpt_read_header <> nil then - memcpy(uint32(header), uint32(gpt_read_header), sizeof(TGPTHeader)); + memcpy(PtrUInt(header), PtrUInt(gpt_read_header), sizeof(TGPTHeader)); kfree(sectorBuf); exit; end; @@ -235,7 +235,7 @@ begin if gpt_validate_header(header) then begin gpt_read_header := PGPTHeader(kalloc(sizeof(TGPTHeader))); if gpt_read_header <> nil then - memcpy(uint32(header), uint32(gpt_read_header), sizeof(TGPTHeader)); + memcpy(PtrUInt(header), PtrUInt(gpt_read_header), sizeof(TGPTHeader)); end; kfree(sectorBuf); end; @@ -258,7 +258,7 @@ begin buf := puint32(kalloc(sectorsNeeded * device^.sectorSize)); if buf = nil then exit; - memset(uint32(buf), 0, sectorsNeeded * device^.sectorSize); + memset(PtrUInt(buf), 0, sectorsNeeded * device^.sectorSize); err := driver.storage.mgr.storage_read(device, header^.PartEntryLBA, sectorsNeeded, buf); if err <> eNone then begin diff --git a/src/driver/storage/vol/driver.storage.vol.mgr.pas b/src/driver/storage/vol/driver.storage.vol.mgr.pas index 737fcabd..ef8790d8 100644 --- a/src/driver/storage/vol/driver.storage.vol.mgr.pas +++ b/src/driver/storage/vol/driver.storage.vol.mgr.pas @@ -26,7 +26,6 @@ unit driver.storage.vol.mgr; interface uses - boot.mgr, io.syslog, driver.storage.fs.mgr, core.ds.lists, @@ -38,7 +37,7 @@ uses core.strings, io.stdio, debug.tracer, - core.util, arch.x86.util; + core.util, arch.platform.cpu; var volumes : PDList; @@ -96,6 +95,7 @@ uses proc.types; type + PVolumeSlot = ^PStorage_Volume; PLabelWork = ^TLabelWork; TLabelWork = record Volume : PStorage_Volume; @@ -187,15 +187,17 @@ begin end; function claim_metadata(device : PStorage_Device) : boolean; -var flags : uint32; +var + irqFlags: PtrUInt; + flags : uint32; begin claim_metadata := false; if (device = nil) or device^.removed or (not device^.writable) then exit; - asm pushfd; cli; pop flags end; + irqFlags := arch.platform.cpu.lock; if not device^.metadataBusy then begin device^.metadataBusy := true; claim_metadata := true; end; - if (flags and $200) <> 0 then asm sti end; + arch.platform.cpu.unlock(irqFlags); end; function update_partition_system_id(device : PStorage_Device; sectorStart : uint32; systemId : uint8) : TError; @@ -210,7 +212,7 @@ begin if src^.partition[slot].system_id = systemId then exit; copyMbr := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record))); if copyMbr = nil then begin update_partition_system_id := eOutOfMemory; exit; end; - memcpy(uint32(src), uint32(copyMbr), sizeof(TMaster_Boot_Record)); + memcpy(PtrUInt(src), PtrUInt(copyMbr), sizeof(TMaster_Boot_Record)); copyMbr^.partition[slot].system_id := systemId; update_partition_system_id := write_mbr(device, copyMbr); kfree(void(copyMbr)); @@ -222,7 +224,7 @@ begin perform_format := eFileInUse; if not driver.storage.vfs.InvalidateVolume(vol) then exit; vol^.filesystem := fs; - memset(uint32(@vol^.labelText[0]),0,sizeof(vol^.labelText)); + memset(PtrUInt(@vol^.labelText[0]),0,sizeof(vol^.labelText)); err := fs^.createCallback(vol, vol^.sectorCount, vol^.sectorStart, config); { Do not identify/remount a partially formatted volume after an error. } vol^.filesystem := nil; @@ -306,7 +308,7 @@ procedure init(); begin push_trace('driver.storage.vol.mgr.init'); volumes := DL_New(sizeof(Pointer)); - memset(uint32(@LifecycleWatches[0]), 0, sizeof(LifecycleWatches)); + memset(PtrUInt(@LifecycleWatches[0]), 0, sizeof(LifecycleWatches)); NextLifecycleWatchID := 1; NextVolumeID := 0; end; @@ -417,7 +419,7 @@ begin push_trace('driver.storage.vol.mgr.get_volume.enter'); if index < DL_Size(volumes) then begin slot := DL_Get(volumes, index); - get_volume := PStorage_Volume(slot^); + get_volume := PVolumeSlot(slot)^; end else get_volume := nil; push_trace('driver.storage.vol.mgr.get_volume.exit'); @@ -430,7 +432,7 @@ var mbr : PMaster_Boot_Record; begin push_trace('driver.storage.vol.mgr.get_partition'); - memset(uint32(@get_partition), 0, sizeof(TPartition_table)); + memset(PtrUInt(@get_partition), 0, sizeof(TPartition_table)); if (device = nil) or (index > 3) then exit; mbr := driver.storage.mgr.get_cached_mbr(device); if mbr = nil then exit; @@ -475,7 +477,7 @@ begin end; newVol := PStorage_Volume(kalloc(sizeof(TStorage_Volume))); if newVol = nil then begin partition_operation := eOutOfMemory; exit; end; - memset(uint32(newVol), 0, sizeof(TStorage_Volume)); + memset(PtrUInt(newVol), 0, sizeof(TStorage_Volume)); newVol^.device := device; newVol^.sectorStart := part.LBA_start; newVol^.sectorCount := part.sector_count; newVol^.sectorSize := device^.sectorSize; end; @@ -484,9 +486,9 @@ begin if newVol <> nil then kfree(void(newVol)); partition_operation := eOutOfMemory; exit; end; - memcpy(uint32(src), uint32(mbr), sizeof(TMaster_Boot_Record)); + memcpy(PtrUInt(src), PtrUInt(mbr), sizeof(TMaster_Boot_Record)); if (kind = 2) or (mbr^.boot_sector <> $AA55) then begin - memset(uint32(mbr), 0, sizeof(TMaster_Boot_Record)); mbr^.boot_sector := $AA55; + memset(PtrUInt(mbr), 0, sizeof(TMaster_Boot_Record)); mbr^.boot_sector := $AA55; end; err := eNone; if kind = 0 then mbr^.partition[slot] := part @@ -505,7 +507,7 @@ begin Inc(i); end; if (kind = 1) and (err = eNone) then - memset(uint32(@mbr^.partition[slot]), 0, sizeof(TPartition_table)); + memset(PtrUInt(@mbr^.partition[slot]), 0, sizeof(TPartition_table)); end; if err = eNone then err := write_mbr(device, mbr); kfree(void(mbr)); @@ -546,7 +548,7 @@ var part : TPartition_table; begin remove_partition := eFileInUse; if not claim_metadata(device) then exit; - memset(uint32(@part), 0, sizeof(part)); + memset(PtrUInt(@part), 0, sizeof(part)); remove_partition := partition_operation(device, 1, slot, part); device^.metadataBusy := false; end; @@ -556,7 +558,7 @@ var part : TPartition_table; begin init_disk := eFileInUse; if not claim_metadata(device) then exit; - memset(uint32(@part), 0, sizeof(part)); + memset(PtrUInt(@part), 0, sizeof(part)); init_disk := partition_operation(device, 2, 0, part); device^.metadataBusy := false; end; @@ -626,7 +628,7 @@ begin if gptEntries <> nil then begin { Iterate partition entries } for i := 0 to gptHeader^.NumPartEntries - 1 do begin - entry := PGPTPartitionEntry(uint32(gptEntries) + i * gptHeader^.PartEntrySize); + entry := PGPTPartitionEntry(PtrUInt(gptEntries) + i * gptHeader^.PartEntrySize); if gpt_guid_is_zero(entry^.TypeGUID) then continue; { empty slot } if entry^.StartLBA_Hi <> 0 then continue; { LBA beyond 32-bit range } if entry^.EndLBA_Hi <> 0 then continue; @@ -808,14 +810,14 @@ procedure remove_partition_async(device : PStorage_Device; slot : uint32; callback : TIOCallback; callbackData : pointer); var part : TPartition_table; begin - memset(uint32(@part), 0, sizeof(part)); + memset(PtrUInt(@part), 0, sizeof(part)); submit_partition(device, 1, slot, part, callback, callbackData); end; procedure init_disk_async(device : PStorage_Device; callback : TIOCallback; callbackData : pointer); var part : TPartition_table; begin - memset(uint32(@part), 0, sizeof(part)); + memset(PtrUInt(@part), 0, sizeof(part)); submit_partition(device, 2, 0, part, callback, callbackData); end; @@ -866,7 +868,7 @@ begin ctx^.Config := nil; if config <> nil then begin ctx^.Config := PFSFormatParams(kalloc(sizeof(TFSFormatParams))); - if ctx^.Config <> nil then memcpy(uint32(config), uint32(ctx^.Config), sizeof(TFSFormatParams)); + if ctx^.Config <> nil then memcpy(PtrUInt(config), PtrUInt(ctx^.Config), sizeof(TFSFormatParams)); end; if (config = nil) or (ctx^.Config <> nil) then begin notify_lifecycle(sleVolumeFormatStarted, device, vol, eNone); @@ -941,7 +943,4 @@ begin device^.metadataBusy := false; end; -initialization - boot.mgr.registerBoot('driver.storage.vol.mgr', @init, 'Volume Manager', 'driver.storage.mgr*'); - end. diff --git a/src/driver/timer/driver.timer.rtc.pas b/src/driver/timer/driver.timer.rtc.pas index e6549120..1630bb16 100644 --- a/src/driver/timer/driver.timer.rtc.pas +++ b/src/driver/timer/driver.timer.rtc.pas @@ -22,8 +22,7 @@ unit driver.timer.rtc; interface uses - boot.mgr, - arch.x86.isr.mgr, core.util, arch.x86.util, arch.x86.isr.tmr0; + core.util, arch.platform.cpu; type TDateTime = record @@ -66,7 +65,7 @@ var begin outb($70, $0A); - io_wait(); + arch.platform.cpu.outb($80, 0); bin:= inb($71); is_update_in_progress:= (bin AND ($1 SHL 7)) <> 0; end; @@ -74,7 +73,7 @@ end; procedure update(); begin //outb($70, $0C); // select register C - //io_wait(); + //arch.platform.cpu.outb($80, 0); //inb($71); //console.writestringln('driver.timer.rtc Update'); //while not is_update_in_progress do begin @@ -82,37 +81,37 @@ begin //while is_update_in_progress do begin //end; outb($70, $00); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Seconds:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); outb($70, $02); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Minutes:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); outb($70, $04); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Hours:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); outb($70, $06); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Weekday:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); outb($70, $07); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Day:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); outb($70, $08); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Month:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); outb($70, $09); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Year:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); outb($70, $32); - io_wait(); + arch.platform.cpu.outb($80, 0); DateTime.Century:= BCDToUint8(inb($71)); - io_wait(); + arch.platform.cpu.outb($80, 0); end; function getDateTime : TDateTime; @@ -122,34 +121,8 @@ begin end; procedure init; -var - prev : uint8; - begin - CLI; - //setup driver.timer.rtc - outb($70, $8A); - io_wait(); - outb($71, $20); - io_wait(); - - //enable ints - outb($70, $8B); - io_wait(); - prev:= inb($71); - io_wait(); - outb($70, $8B); - io_wait(); - outb($71, prev OR $40); - STI; - outb($70, $00); - inb($71); - - //arch.x86.isr.mgr.registerISR(32 + 8, @update); - //arch.x86.isr.tmr0.hook(uint32(@update)); + update; end; -initialization - boot.mgr.registerBoot('driver.timer.rtc', @init, 'Real Time Clock Driver', 'arch.x86.fault'); - -end. \ No newline at end of file +end. diff --git a/src/driver/video/driver.video.bga.pas b/src/driver/video/driver.video.bga.pas index 9c19e866..d795f250 100644 --- a/src/driver/video/driver.video.bga.pas +++ b/src/driver/video/driver.video.bga.pas @@ -17,9 +17,8 @@ unit driver.video.bga; interface uses - boot.mgr, - core.util, arch.x86.util, io.syslog, debug.tracer, driver.video.gpu, driver.bus.pci, driver.types, driver.mgr, - arch.x86.memory.virtual, memory.heap; + core.util, arch.platform.cpu, io.syslog, debug.tracer, driver.video.gpu, driver.bus.pci, driver.types, driver.mgr, + arch.platform.memory, memory.heap; procedure init; @@ -58,6 +57,7 @@ const var BGAPresent : boolean = false; BGAFramebuffer: uint32 = 0; + BGAMemoryBytes: uint64 = 0; BGAPCIBus : uint8 = 0; BGAPCISlot : uint8 = 0; BGAPCIFunc : uint8 = 0; @@ -76,22 +76,6 @@ begin bgaReadReg := inw(BGA_PORT_DATA); end; -{ Map the framebuffer physical memory pages so we can access it } -procedure mapFramebuffer(address, width, height : uint32; bpp : uint8); -var - framebufferSize : uint32; - lowerPage, upperPage, page : uint32; -begin - push_trace('driver.video.bga.mapFramebuffer'); - framebufferSize := (width * height * uint32(bpp)) div 8; - lowerPage := (address SHR 22) - 1; - upperPage := ((address + framebufferSize) SHR 22) + 1; - for page := lowerPage to upperPage do begin - kpalloc(page SHL 22); - end; - pop_trace; -end; - { Drivermanagement load callback: called when driver.bus.pci finds a matching device } function load(ptr : void) : boolean; var @@ -141,6 +125,18 @@ begin id := bgaReadReg(BGA_REG_ID); if (id >= BGA_ID_MIN) and (id <= BGA_ID_MAX) then begin BGAPresent := true; + { PCI's legacy device_id field contains the vendor ID. VirtualBox's + BGA data port returns VRAM bytes on a 32-bit read; index $0A is its + monitor-count interface, not the Bochs 64 KiB memory register. + See VirtualBox DevVGA.cpp, vgaIoPortReadVbeData. } + if (dev^.device_id = $80EE) or (dev^.device_id = $15AD) then + BGAMemoryBytes := inl(BGA_PORT_DATA) + else if id >= $B0C5 then + BGAMemoryBytes := uint64(bgaReadReg($0A)) * 65536 + else if id = $B0C4 then + BGAMemoryBytes := 8 * 1024 * 1024 + else + BGAMemoryBytes := 4 * 1024 * 1024; io.syslog.log('BGA', 'Detected BGA version $'); io.syslog.writehexln(id); @@ -164,6 +160,10 @@ end; { GPU framework: setMode callback } function bgaSetMode(width, height : uint32; bpp : uint8; var info : TGPUModeInfo) : boolean; +var + mapped: pointer; + bytes: uint64; + oldWidth, oldHeight, oldBPP, oldEnable: uint16; begin push_trace('driver.video.bga.bgaSetMode'); bgaSetMode := false; @@ -173,6 +173,18 @@ begin exit; end; + if (width = 0) or (height = 0) or (width > 65535) or + (height > 65535) or (bpp <> 32) then exit; + bytes := uint64(width) * height * 4; + if (BGAMemoryBytes = 0) or (bytes > BGAMemoryBytes) or + (bytes > High(SizeUInt)) then exit; + mapped := arch.platform.memory.mapDevice(BGAFramebuffer, SizeUInt(bytes)); + if mapped = nil then exit; + oldWidth := bgaReadReg(BGA_REG_XRES); + oldHeight := bgaReadReg(BGA_REG_YRES); + oldBPP := bgaReadReg(BGA_REG_BPP); + oldEnable := bgaReadReg(BGA_REG_ENABLE); + { Disable the display while changing mode } bgaWriteReg(BGA_REG_ENABLE, BGA_DISABLED); @@ -191,20 +203,28 @@ begin bgaWriteReg(BGA_REG_ENABLE, BGA_ENABLED or BGA_LFB_ENABLED); { Verify mode was set } - if bgaReadReg(BGA_REG_XRES) <> (width and $FFFF) then begin + if (bgaReadReg(BGA_REG_XRES) <> width) or + (bgaReadReg(BGA_REG_YRES) <> height) or + (bgaReadReg(BGA_REG_BPP) <> bpp) then begin + bgaWriteReg(BGA_REG_ENABLE, BGA_DISABLED); + bgaWriteReg(BGA_REG_XRES, oldWidth); + bgaWriteReg(BGA_REG_YRES, oldHeight); + bgaWriteReg(BGA_REG_BPP, oldBPP); + bgaWriteReg(BGA_REG_VIRT_WIDTH, oldWidth); + bgaWriteReg(BGA_REG_VIRT_HEIGHT, oldHeight); + bgaWriteReg(BGA_REG_ENABLE, oldEnable); io.syslog.logln('BGA', 'setMode: XRES verify failed.'); pop_trace; exit; end; - { Map the framebuffer pages } - mapFramebuffer(BGAFramebuffer, width, height, bpp); + { Fill in the mode info for the caller } info.Width := width; info.Height := height; info.BPP := bpp; - info.Framebuffer := BGAFramebuffer; + info.Framebuffer := PtrUInt(mapped); info.Pitch := (width * uint32(bpp)) div 8; io.syslog.log('BGA', 'Mode set: '); @@ -247,7 +267,5 @@ begin pop_trace; end; -initialization - boot.mgr.registerBoot('driver.video.bga', @init, 'BGA Display Driver Initialization', 'driver.video'); end. diff --git a/src/driver/video/driver.video.desktop.pas b/src/driver/video/driver.video.desktop.pas index b1383e35..e73f6061 100644 --- a/src/driver/video/driver.video.desktop.pas +++ b/src/driver/video/driver.video.desktop.pas @@ -15,8 +15,7 @@ unit driver.video.desktop; interface uses - boot.mgr, - driver.video.lvgl, driver.video, driver.video.gpu, driver.timer.rtc, core.strings, core.util, arch.x86.util, debug.tracer, arch.x86.multiboot, arch.x86.cpu, driver.io.serial, driver.video.windows, core.version; + driver.video.lvgl, driver.video, driver.video.gpu, driver.timer.rtc, core.strings, core.util, arch.platform.cpu, debug.tracer, boot.multiboot, driver.io.serial, driver.video.windows, core.version; type TProgLaunchProc = procedure; @@ -246,6 +245,9 @@ var mem_str, res_str, cpu_str, clk_str : pchar; cw : sint32; badge_h : sint32; + registers, features: TCPURegisters; + vendor: array[0..12] of char; + maxLeaf: uint32; begin if driver.video.windows.isWindowOpen(sysinfo_win_id) then exit; @@ -269,9 +271,20 @@ begin { ---- System Information ---- } addSectionHeader(content, 'System Information'); - cpu_str := @arch.x86.cpu.CPUID.Identifier[0]; + queryCPU(0, 0, registers); + maxLeaf := registers.eax; + puint32(@vendor[0])^ := registers.ebx; + puint32(@vendor[4])^ := registers.edx; + puint32(@vendor[8])^ := registers.ecx; + vendor[12] := #0; + cpu_str := @vendor[0]; + queryCPU(1, 0, features); addInfoRow(content, cw, 'CPU Vendor', cpu_str); - clk_str := stringConcat(intToString(arch.x86.cpu.CPUID.ClockSpeed.MHz), ' MHz'); + clk_str := 'Not reported'; + if maxLeaf >= $16 then begin + queryCPU($16, 0, registers); + if registers.eax <> 0 then clk_str := stringConcat(intToString(registers.eax), ' MHz'); + end; addInfoRow(content, cw, 'CPU Clock', clk_str); mem_str := stringConcat(intToString(((multibootinfo^.mem_upper + 1000) div 1024) + 1), ' MB'); addInfoRow(content, cw, 'Memory', mem_str); @@ -314,65 +327,65 @@ begin lv_obj_remove_flag(instr_box, LV_OBJ_FLAG_SCROLLABLE); { Old capabilities (EDX) } - if arch.x86.cpu.CPUID.Capabilities0^.FPU then addBadge(instr_box, 'FPU'); - if arch.x86.cpu.CPUID.Capabilities0^.VME then addBadge(instr_box, 'VME'); - if arch.x86.cpu.CPUID.Capabilities0^.DE then addBadge(instr_box, 'DE'); - if arch.x86.cpu.CPUID.Capabilities0^.PSE then addBadge(instr_box, 'PSE'); - if arch.x86.cpu.CPUID.Capabilities0^.TSC then addBadge(instr_box, 'TSC'); - if arch.x86.cpu.CPUID.Capabilities0^.MSR then addBadge(instr_box, 'MSR'); - if arch.x86.cpu.CPUID.Capabilities0^.PAE then addBadge(instr_box, 'PAE'); - if arch.x86.cpu.CPUID.Capabilities0^.MCE then addBadge(instr_box, 'arch.x86.fault.mce'); - if arch.x86.cpu.CPUID.Capabilities0^.CX8 then addBadge(instr_box, 'CX8'); - if arch.x86.cpu.CPUID.Capabilities0^.APIC then addBadge(instr_box, 'APIC'); - if arch.x86.cpu.CPUID.Capabilities0^.SEP then addBadge(instr_box, 'SEP'); - if arch.x86.cpu.CPUID.Capabilities0^.MTRR then addBadge(instr_box, 'MTRR'); - if arch.x86.cpu.CPUID.Capabilities0^.PGE then addBadge(instr_box, 'PGE'); - if arch.x86.cpu.CPUID.Capabilities0^.MCA then addBadge(instr_box, 'MCA'); - if arch.x86.cpu.CPUID.Capabilities0^.CMOV then addBadge(instr_box, 'CMOV'); - if arch.x86.cpu.CPUID.Capabilities0^.PAT then addBadge(instr_box, 'PAT'); - if arch.x86.cpu.CPUID.Capabilities0^.PSE36 then addBadge(instr_box, 'PSE36'); - if arch.x86.cpu.CPUID.Capabilities0^.PSN then addBadge(instr_box, 'PSN'); - if arch.x86.cpu.CPUID.Capabilities0^.CLF then addBadge(instr_box, 'CLF'); - if arch.x86.cpu.CPUID.Capabilities0^.DTES then addBadge(instr_box, 'DTES'); - if arch.x86.cpu.CPUID.Capabilities0^.ACPI then addBadge(instr_box, 'ACPI'); - if arch.x86.cpu.CPUID.Capabilities0^.MMX then addBadge(instr_box, 'MMX'); - if arch.x86.cpu.CPUID.Capabilities0^.FXSR then addBadge(instr_box, 'FXSR'); - if arch.x86.cpu.CPUID.Capabilities0^.SSE then addBadge(instr_box, 'SSE'); - if arch.x86.cpu.CPUID.Capabilities0^.SSE2 then addBadge(instr_box, 'SSE2'); - if arch.x86.cpu.CPUID.Capabilities0^.SS then addBadge(instr_box, 'SS'); - if arch.x86.cpu.CPUID.Capabilities0^.HTT then addBadge(instr_box, 'HTT'); - if arch.x86.cpu.CPUID.Capabilities0^.TM1 then addBadge(instr_box, 'TM1'); - if arch.x86.cpu.CPUID.Capabilities0^.IA64 then addBadge(instr_box, 'IA64'); - if arch.x86.cpu.CPUID.Capabilities0^.PBE then addBadge(instr_box, 'PBE'); + if (features.edx and (uint32(1) shl 0) <> 0) then addBadge(instr_box, 'FPU'); + if (features.edx and (uint32(1) shl 1) <> 0) then addBadge(instr_box, 'VME'); + if (features.edx and (uint32(1) shl 2) <> 0) then addBadge(instr_box, 'DE'); + if (features.edx and (uint32(1) shl 3) <> 0) then addBadge(instr_box, 'PSE'); + if (features.edx and (uint32(1) shl 4) <> 0) then addBadge(instr_box, 'TSC'); + if (features.edx and (uint32(1) shl 5) <> 0) then addBadge(instr_box, 'MSR'); + if (features.edx and (uint32(1) shl 6) <> 0) then addBadge(instr_box, 'PAE'); + if (features.edx and (uint32(1) shl 7) <> 0) then addBadge(instr_box, 'arch.x86.fault.mce'); + if (features.edx and (uint32(1) shl 8) <> 0) then addBadge(instr_box, 'CX8'); + if (features.edx and (uint32(1) shl 9) <> 0) then addBadge(instr_box, 'APIC'); + if (features.edx and (uint32(1) shl 11) <> 0) then addBadge(instr_box, 'SEP'); + if (features.edx and (uint32(1) shl 12) <> 0) then addBadge(instr_box, 'MTRR'); + if (features.edx and (uint32(1) shl 13) <> 0) then addBadge(instr_box, 'PGE'); + if (features.edx and (uint32(1) shl 14) <> 0) then addBadge(instr_box, 'MCA'); + if (features.edx and (uint32(1) shl 15) <> 0) then addBadge(instr_box, 'CMOV'); + if (features.edx and (uint32(1) shl 16) <> 0) then addBadge(instr_box, 'PAT'); + if (features.edx and (uint32(1) shl 17) <> 0) then addBadge(instr_box, 'PSE36'); + if (features.edx and (uint32(1) shl 18) <> 0) then addBadge(instr_box, 'PSN'); + if (features.edx and (uint32(1) shl 19) <> 0) then addBadge(instr_box, 'CLF'); + if (features.edx and (uint32(1) shl 21) <> 0) then addBadge(instr_box, 'DTES'); + if (features.edx and (uint32(1) shl 22) <> 0) then addBadge(instr_box, 'ACPI'); + if (features.edx and (uint32(1) shl 23) <> 0) then addBadge(instr_box, 'MMX'); + if (features.edx and (uint32(1) shl 24) <> 0) then addBadge(instr_box, 'FXSR'); + if (features.edx and (uint32(1) shl 25) <> 0) then addBadge(instr_box, 'SSE'); + if (features.edx and (uint32(1) shl 26) <> 0) then addBadge(instr_box, 'SSE2'); + if (features.edx and (uint32(1) shl 27) <> 0) then addBadge(instr_box, 'SS'); + if (features.edx and (uint32(1) shl 28) <> 0) then addBadge(instr_box, 'HTT'); + if (features.edx and (uint32(1) shl 29) <> 0) then addBadge(instr_box, 'TM1'); + if (features.edx and (uint32(1) shl 30) <> 0) then addBadge(instr_box, 'IA64'); + if (features.edx and (uint32(1) shl 31) <> 0) then addBadge(instr_box, 'PBE'); { New capabilities (ECX) } - if arch.x86.cpu.CPUID.Capabilities1^.SSE3 then addBadge(instr_box, 'SSE3'); - if arch.x86.cpu.CPUID.Capabilities1^.PCLMUL then addBadge(instr_box, 'PCLMUL'); - if arch.x86.cpu.CPUID.Capabilities1^.DTES64 then addBadge(instr_box, 'DTES64'); - if arch.x86.cpu.CPUID.Capabilities1^.MONITOR then addBadge(instr_box, 'MONITOR'); - if arch.x86.cpu.CPUID.Capabilities1^.DS_CPL then addBadge(instr_box, 'DS_CPL'); - if arch.x86.cpu.CPUID.Capabilities1^.VMX then addBadge(instr_box, 'VMX'); - if arch.x86.cpu.CPUID.Capabilities1^.SMX then addBadge(instr_box, 'SMX'); - if arch.x86.cpu.CPUID.Capabilities1^.EST then addBadge(instr_box, 'EST'); - if arch.x86.cpu.CPUID.Capabilities1^.TM2 then addBadge(instr_box, 'TM2'); - if arch.x86.cpu.CPUID.Capabilities1^.SSSE3 then addBadge(instr_box, 'SSSE3'); - if arch.x86.cpu.CPUID.Capabilities1^.CID then addBadge(instr_box, 'CID'); - if arch.x86.cpu.CPUID.Capabilities1^.FMA then addBadge(instr_box, 'FMA'); - if arch.x86.cpu.CPUID.Capabilities1^.CX16 then addBadge(instr_box, 'CX16'); - if arch.x86.cpu.CPUID.Capabilities1^.ETPRD then addBadge(instr_box, 'ETPRD'); - if arch.x86.cpu.CPUID.Capabilities1^.PDCM then addBadge(instr_box, 'PDCM'); - if arch.x86.cpu.CPUID.Capabilities1^.PCIDE then addBadge(instr_box, 'PCIDE'); - if arch.x86.cpu.CPUID.Capabilities1^.DCA then addBadge(instr_box, 'DCA'); - if arch.x86.cpu.CPUID.Capabilities1^.SSE4_1 then addBadge(instr_box, 'SSE4.1'); - if arch.x86.cpu.CPUID.Capabilities1^.SSE4_2 then addBadge(instr_box, 'SSE4.2'); - if arch.x86.cpu.CPUID.Capabilities1^.x2APIC then addBadge(instr_box, 'x2APIC'); - if arch.x86.cpu.CPUID.Capabilities1^.MOVBE then addBadge(instr_box, 'MOVBE'); - if arch.x86.cpu.CPUID.Capabilities1^.POPCNT then addBadge(instr_box, 'POPCNT'); - if arch.x86.cpu.CPUID.Capabilities1^.AES then addBadge(instr_box, 'AES'); - if arch.x86.cpu.CPUID.Capabilities1^.XSAVE then addBadge(instr_box, 'XSAVE'); - if arch.x86.cpu.CPUID.Capabilities1^.OSXSAVE then addBadge(instr_box, 'OSXSAVE'); - if arch.x86.cpu.CPUID.Capabilities1^.AVX then addBadge(instr_box, 'AVX'); - if arch.x86.cpu.CPUID.Capabilities1^.RDRAND then addBadge(instr_box, 'RDRAND'); + if (features.ecx and (uint32(1) shl 0) <> 0) then addBadge(instr_box, 'SSE3'); + if (features.ecx and (uint32(1) shl 1) <> 0) then addBadge(instr_box, 'PCLMUL'); + if (features.ecx and (uint32(1) shl 2) <> 0) then addBadge(instr_box, 'DTES64'); + if (features.ecx and (uint32(1) shl 3) <> 0) then addBadge(instr_box, 'MONITOR'); + if (features.ecx and (uint32(1) shl 4) <> 0) then addBadge(instr_box, 'DS_CPL'); + if (features.ecx and (uint32(1) shl 5) <> 0) then addBadge(instr_box, 'VMX'); + if (features.ecx and (uint32(1) shl 6) <> 0) then addBadge(instr_box, 'SMX'); + if (features.ecx and (uint32(1) shl 7) <> 0) then addBadge(instr_box, 'EST'); + if (features.ecx and (uint32(1) shl 8) <> 0) then addBadge(instr_box, 'TM2'); + if (features.ecx and (uint32(1) shl 9) <> 0) then addBadge(instr_box, 'SSSE3'); + if (features.ecx and (uint32(1) shl 10) <> 0) then addBadge(instr_box, 'CID'); + if (features.ecx and (uint32(1) shl 12) <> 0) then addBadge(instr_box, 'FMA'); + if (features.ecx and (uint32(1) shl 13) <> 0) then addBadge(instr_box, 'CX16'); + if (features.ecx and (uint32(1) shl 14) <> 0) then addBadge(instr_box, 'ETPRD'); + if (features.ecx and (uint32(1) shl 15) <> 0) then addBadge(instr_box, 'PDCM'); + if (features.ecx and (uint32(1) shl 17) <> 0) then addBadge(instr_box, 'PCIDE'); + if (features.ecx and (uint32(1) shl 18) <> 0) then addBadge(instr_box, 'DCA'); + if (features.ecx and (uint32(1) shl 19) <> 0) then addBadge(instr_box, 'SSE4.1'); + if (features.ecx and (uint32(1) shl 20) <> 0) then addBadge(instr_box, 'SSE4.2'); + if (features.ecx and (uint32(1) shl 21) <> 0) then addBadge(instr_box, 'x2APIC'); + if (features.ecx and (uint32(1) shl 22) <> 0) then addBadge(instr_box, 'MOVBE'); + if (features.ecx and (uint32(1) shl 23) <> 0) then addBadge(instr_box, 'POPCNT'); + if (features.ecx and (uint32(1) shl 25) <> 0) then addBadge(instr_box, 'AES'); + if (features.ecx and (uint32(1) shl 26) <> 0) then addBadge(instr_box, 'XSAVE'); + if (features.ecx and (uint32(1) shl 27) <> 0) then addBadge(instr_box, 'OSXSAVE'); + if (features.ecx and (uint32(1) shl 28) <> 0) then addBadge(instr_box, 'AVX'); + if (features.ecx and (uint32(1) shl 30) <> 0) then addBadge(instr_box, 'RDRAND'); { Force layout so wrapped row heights are resolved, then set instr_box to its actual content height. } @@ -863,7 +876,4 @@ begin animateResultsPanel; end; -Initialization - boot.mgr.registerBoot('proc.desktop', @init, 'Initialize Desktop', 'boot.splash.teardown'); - end. diff --git a/src/driver/video/driver.video.doublebuffer.pas b/src/driver/video/driver.video.doublebuffer.pas index 6603cad6..7ea5a84c 100644 --- a/src/driver/video/driver.video.doublebuffer.pas +++ b/src/driver/video/driver.video.doublebuffer.pas @@ -22,17 +22,26 @@ unit driver.video.doublebuffer; interface uses - memory.heap, debug.tracer, driver.video.types, driver.io.serial, core.util, arch.x86.util; + memory.heap, debug.tracer, driver.video.types, driver.io.serial, core.util, arch.platform.cpu; //Init the driver, and register with the driver.video interface in a state ready for execution. procedure init(Register : FRegisterDriver); implementation -function allocateBackBuffer(Width : uint32; Height : uint32; BitsPerPixel : uint8) : uint64; +function allocateBackBuffer(Width : uint32; Height : uint32; BitsPerPixel : uint8) : PtrUInt; +var + bytes: uint64; begin + allocateBackBuffer := 0; + if (Width = 0) or (Height = 0) or (BitsPerPixel = 0) or + (BitsPerPixel mod 8 <> 0) then exit; + bytes := uint64(Width) * (BitsPerPixel div 8); + if bytes > High(uint32) then exit; + bytes := bytes * Height; + if bytes > High(SizeUInt) then exit; debug.tracer.push_trace('driver.video.doublebuffer.allocateBackBuffer.enter'); - allocateBackBuffer:= uint64(klalloc((Width * Height * BitsPerPixel) div 8)); + allocateBackBuffer := PtrUInt(klalloc(SizeUInt(bytes))); debug.tracer.push_trace('driver.video.doublebuffer.allocateBackBuffer.exit'); end; @@ -44,6 +53,7 @@ begin if (VInterface^.BackBuffer.Location <> 0) then begin VInterface^.BackBuffer.Width:= Width; VInterface^.BackBuffer.Height:= Height; + VInterface^.BackBuffer.Pitch := Width * (BitsPerPixel div 8); VInterface^.BackBuffer.BitsPerPixel:= BitsPerPixel; VInterface^.BackBuffer.Initialized:= True; end; @@ -53,27 +63,18 @@ end; procedure Flush(FrontBuffer : PVideoBuffer; BackBuffer : PVideoBuffer); var - idx : uint32; - Back,Front : uint32; - BufferSize : uint32; - -const - //COPY_WIDTH = 64; //Use this for 64bit copies - COPY_WIDTH = 128; //Use this for SSE copies - + row, bytes : uint32; begin - //debug.tracer.push_trace('driver.video.doublebuffer.Flush.enter'); - if not(BackBuffer^.Initialized) then exit; - if ((FrontBuffer^.Width > BackBuffer^.Width) or (FrontBuffer^.Height > BackBuffer^.Height)) then exit; - Back:= BackBuffer^.Location; - Front:= FrontBuffer^.Location; - BufferSize:= ( ( BackBuffer^.Width * BackBuffer^.Height * BackBuffer^.BitsPerPixel) div COPY_WIDTH ) - 1; - for idx:=0 to BufferSize do begin - //Front[idx]:= Back[idx]; - // -- TODO: Get SSE working here for 128bit copies -- - __SSE_128_memcpy(Back + (idx * 16), Front + (idx * 16)); - end; - //debug.tracer.push_trace('driver.video.doublebuffer.Flush.exit'); + if not (FrontBuffer^.Initialized and BackBuffer^.Initialized) then exit; + if (BackBuffer^.Width > FrontBuffer^.Width) or + (BackBuffer^.Height > FrontBuffer^.Height) or + (BackBuffer^.BitsPerPixel <> FrontBuffer^.BitsPerPixel) then exit; + bytes := BackBuffer^.Pitch; + if (bytes = 0) or (bytes > FrontBuffer^.Pitch) or + (BackBuffer^.Height = 0) then exit; + for row := 0 to BackBuffer^.Height - 1 do + memcpy(BackBuffer^.Location + SizeUInt(row) * bytes, + FrontBuffer^.Location + SizeUInt(row) * FrontBuffer^.Pitch, bytes); end; function enable(VideoInterface : PVideoInterface) : boolean; @@ -96,4 +97,4 @@ begin debug.tracer.push_trace('driver.video.doublebuffer.init.exit'); end; -end. \ No newline at end of file +end. diff --git a/src/driver/video/driver.video.gpu.pas b/src/driver/video/driver.video.gpu.pas index 5ab11a17..849863a0 100644 --- a/src/driver/video/driver.video.gpu.pas +++ b/src/driver/video/driver.video.gpu.pas @@ -15,8 +15,7 @@ unit driver.video.gpu; interface uses - boot.mgr, - core.util, arch.x86.util, io.syslog, debug.tracer, core.strings; + core.util, arch.platform.cpu, io.syslog, debug.tracer, core.strings; const GPU_MAX_DRIVERS = 8; @@ -28,7 +27,7 @@ type Width : uint32; Height : uint32; BPP : uint8; - Framebuffer : uint32; { physical address of linear framebuffer } + Framebuffer : PtrUInt; { mapped address of linear framebuffer } Pitch : uint32; { bytes per scanline } end; PGPUModeInfo = ^TGPUModeInfo; @@ -173,6 +172,7 @@ procedure fireModeChangeCallbacks(const info : TGPUModeInfo); var i : uint8; begin + if CallbackCount = 0 then exit; for i := 0 to CallbackCount - 1 do begin if Callbacks[i] <> nil then Callbacks[i](info); @@ -185,6 +185,7 @@ var begin push_trace('driver.video.gpu.setMode'); setMode := false; + if DriverCount = 0 then exit; for i := 0 to DriverCount - 1 do begin if Drivers[i].Available and (Drivers[i].SetMode <> nil) then begin @@ -224,7 +225,4 @@ begin activeDriverName := 'none'; end; -initialization - boot.mgr.registerBoot('driver.video.gpu', @init, 'GPU Driver Framework Initialization', 'driver.mgr'); - end. diff --git a/src/driver/video/driver.video.lvgl.pas b/src/driver/video/driver.video.lvgl.pas index efdc0c0e..5364e944 100644 --- a/src/driver/video/driver.video.lvgl.pas +++ b/src/driver/video/driver.video.lvgl.pas @@ -13,7 +13,7 @@ interface uses driver.video, driver.video.types, core.gfx.color, driver.io.serial, debug.tracer, memory.heap, - driver.hid.mouse, driver.hid.keyboard, arch.x86.isr.tmr0, core.util, arch.x86.util, io.syslog, driver.video.gpu; + driver.hid.mouse, driver.hid.keyboard, arch.platform.interrupts, core.util, arch.platform.cpu, io.syslog, driver.video.gpu; { ============================================================ Opaque LVGL pointer core.types (internal C structs) @@ -46,16 +46,29 @@ type { ============================================================ Stack-allocatable core.types with known sizes ============================================================ } - { lv_style_t — ~12 bytes on 32-bit, padded to 16 for safety } - lv_style_t = packed array[0..15] of uint8; + { C-aligned LVGL 9.2.2 layout. } + lv_style_t = record + values_and_props: pointer; + has_group: uint32; + prop_count: uint8; + end; Plv_style = ^lv_style_t; - { lv_anim_t — ~80-100 bytes on 32-bit, padded to 128 } - lv_anim_t = packed array[0..127] of uint8; + { C-aligned LVGL 9.2.2 layout. } + lv_anim_t = record + callbacks_and_data: array[0..8] of pointer; + values_and_timing: array[0..8] of sint32; + bezier: array[0..3] of sint32; + last_timer_run: uint32; + flags: uint8; + end; Plv_anim = ^lv_anim_t; - { lv_style_transition_dsc_t — ~20 bytes, padded to 24 } - lv_style_transition_dsc_t = packed array[0..23] of uint8; + { C-aligned LVGL 9.2.2 layout. } + lv_style_transition_dsc_t = record + properties, user_data, path_callback: pointer; + duration, delay: uint32; + end; Plv_style_transition_dsc = ^lv_style_transition_dsc_t; { ============================================================ @@ -97,7 +110,7 @@ type end; { lv_style_value_t — C union: int32/pointer/color, all 4 bytes on 32-bit } - lv_style_value_t = packed record + lv_style_value_t = record case byte of 0: (num: sint32); 1: (ptr: pointer); @@ -1669,6 +1682,8 @@ function lv_animimg_get_repeat_count(img: Plv_obj): uint32; cdecl; external; { ============================================================ High-level API for core.version ============================================================ } +procedure lv_event_stop_processing(e: Plv_event); cdecl; external; + procedure lvgl_init(screen_w, screen_h: uint32); function lvgl_handler: uint32; function lvgl_get_display: Plv_display; @@ -1683,6 +1698,8 @@ procedure lvgl_update_resolution(screen_w, screen_h: uint32); procedure lvgl_lock; procedure lvgl_unlock; +var onDebugToggle: procedure = nil; + { Color helper (Pascal wrapper for inline C function) } function lv_color_make(r, g, b: uint8): lv_color_t; @@ -1692,7 +1709,7 @@ function lv_color_make(r, g, b: uint8): lv_color_t; implementation uses - app.uidebug, proc.types, proc.mgr; + proc.types, proc.mgr, arch.common.legacyirq; { ============================================================ Wrappers for LVGL inline/macro functions @@ -1729,9 +1746,10 @@ var { Tick counter — incremented by 1024Hz timer ISR } tick_accumulator : uint32; + tick_fraction : uint64; { Keyboard ring buffer for LVGL } - kb_buf : array[0..15] of uint32; + kb_buf : array[0..63] of uint32; kb_head : uint32; { next write slot (hook side) } kb_tail : uint32; { next read slot (LVGL poll side) } kb_current_key : uint32; { key currently reported as pressed to LVGL, 0 = none } @@ -1739,7 +1757,7 @@ var kb_held_code : uint32; { the LVGL key code of the held key } const - KB_BUF_SIZE = 16; + KB_BUF_SIZE = 64; { LVGL render buffer: 1/10th of screen } LV_BUF_LINES = 120; @@ -1759,7 +1777,7 @@ begin end; { ============================================================ - LVGL Mutex — blocking, recursive, SMP-safe + LVGL Mutex — recursive, FIFO handoff on the single active CPU ============================================================ } const LVGL_MTX_MAX_WAITERS = 16; @@ -1768,84 +1786,85 @@ var lvgl_mtx_locked : uint32 = 0; lvgl_mtx_owner : pointer = nil; lvgl_mtx_depth : uint32 = 0; - lvgl_mtx_waiters : array[0..LVGL_MTX_MAX_WAITERS-1] of pointer; + lvgl_mtx_waiters : array[0..LVGL_MTX_MAX_WAITERS-1] of uint32; + lvgl_mtx_head: uint32 = 0; lvgl_mtx_nwait : uint32 = 0; procedure lvgl_lock; var - old : uint32; - cur : pointer; + irqFlags: PtrUInt; + cur: PProcessContext; + parked: boolean; begin - cur := pointer(proc.mgr.CurrentProcess); - - { Recursive lock — same owner, just bump depth } - if (lvgl_mtx_locked <> 0) and (lvgl_mtx_owner = cur) then begin - lvgl_mtx_depth := lvgl_mtx_depth + 1; + cur := proc.mgr.CurrentProcess; + irqFlags := arch.platform.cpu.lock; + if (lvgl_mtx_locked <> 0) and (lvgl_mtx_owner = pointer(cur)) then begin + inc(lvgl_mtx_depth); + arch.platform.cpu.unlock(irqFlags); exit; end; + arch.platform.cpu.unlock(irqFlags); while true do begin - { CLI to prevent scheduler preemption during lock check + park } - asm pushfd; cli end; - - { Atomic test-and-set (SMP-safe via LOCK prefix) } - old := 1; - asm - mov eax, 1 - lock xchg dword ptr [lvgl_mtx_locked], eax - mov old, eax - end; - - if old = 0 then begin - { Acquired } - lvgl_mtx_owner := cur; + irqFlags := arch.platform.cpu.lock; + if lvgl_mtx_locked = 0 then begin + lvgl_mtx_locked := 1; + lvgl_mtx_owner := pointer(cur); lvgl_mtx_depth := 1; - asm popfd end; + end; + { Unlock hands ownership directly to the oldest waiting process. } + if lvgl_mtx_owner = pointer(cur) then begin + arch.platform.cpu.unlock(irqFlags); exit; end; - - { Failed — park ourselves so the scheduler skips us. - The lock holder will wake us on unlock. } - if lvgl_mtx_nwait < LVGL_MTX_MAX_WAITERS then begin - lvgl_mtx_waiters[lvgl_mtx_nwait] := cur; - lvgl_mtx_nwait := lvgl_mtx_nwait + 1; + parked := lvgl_mtx_nwait < LVGL_MTX_MAX_WAITERS; + if parked then begin + lvgl_mtx_waiters[(lvgl_mtx_head + lvgl_mtx_nwait) mod LVGL_MTX_MAX_WAITERS] := cur^.ProcessID; + inc(lvgl_mtx_nwait); + cur^.State := psAwaiting; end; - PProcessContext(cur)^.State := psAwaiting; - asm popfd end; - - { Yield — scheduler will skip us (psAwaiting). - Resume here when the lock holder wakes us (sets psReady). } - asm hlt end; + arch.platform.cpu.unlock(irqFlags); + if parked then + proc.mgr.proc_await + else + proc.mgr.proc_yield; end; end; procedure lvgl_unlock; var - i : uint32; + irqFlags: PtrUInt; + next: PProcessContext; + pid: uint32; begin - { Recursive unlock — just decrement depth } - if lvgl_mtx_depth > 1 then begin - lvgl_mtx_depth := lvgl_mtx_depth - 1; + irqFlags := arch.platform.cpu.lock; + if lvgl_mtx_owner <> pointer(proc.mgr.CurrentProcess) then begin + arch.platform.cpu.unlock(irqFlags); exit; end; - - asm pushfd; cli end; - + if lvgl_mtx_depth > 1 then begin + dec(lvgl_mtx_depth); + arch.platform.cpu.unlock(irqFlags); + exit; + end; + while lvgl_mtx_nwait <> 0 do begin + pid := lvgl_mtx_waiters[lvgl_mtx_head]; + lvgl_mtx_head := (lvgl_mtx_head + 1) mod LVGL_MTX_MAX_WAITERS; + dec(lvgl_mtx_nwait); + next := proc.mgr.findByID(pid); + if (next <> nil) and (next^.State = psAwaiting) then begin + lvgl_mtx_owner := pointer(next); + lvgl_mtx_depth := 1; + next^.State := psReady; + arch.platform.cpu.unlock(irqFlags); + exit; + end; + end; lvgl_mtx_locked := 0; lvgl_mtx_owner := nil; lvgl_mtx_depth := 0; - - { Wake all waiters — they will compete for the lock } - if lvgl_mtx_nwait > 0 then begin - for i := 0 to lvgl_mtx_nwait - 1 do - if lvgl_mtx_waiters[i] <> nil then - PProcessContext(lvgl_mtx_waiters[i])^.State := psReady; - lvgl_mtx_nwait := 0; - end; - - asm popfd end; + arch.platform.cpu.unlock(irqFlags); end; - { ============================================================ Flush callback — copies LVGL render buffer to driver.video back buffer Handles 32bpp (direct copy) and 16bpp (ARGB8888 -> RGB565). @@ -1873,7 +1892,7 @@ begin for x := 0 to area_w - 1 do begin dst32[x] := src[x]; end; - src := puint32(uint32(src) + uint32(area_w * 4)); + src := puint32(PtrUInt(src) + uint32(area_w * 4)); end; end else if bpp = 16 then begin { 16bpp: convert ARGB8888 -> RGB565, 2 bytes per pixel } @@ -1886,7 +1905,7 @@ begin r := (pixel shr 16) and $FF; dst16[x] := uint16(((uint16(r) shr 3) shl 11) or ((uint16(g) shr 2) shl 5) or (uint16(b) shr 3)); end; - src := puint32(uint32(src) + uint32(area_w * 4)); + src := puint32(PtrUInt(src) + uint32(area_w * 4)); end; end; @@ -1934,7 +1953,7 @@ begin kb_tail := (kb_tail + 1) mod KB_BUF_SIZE; data^.key := kb_current_key; data^.state := LV_INDEV_STATE_PRESSED; - data^.continue_reading := false; { one press per timer cycle for proper long-press timing } + data^.continue_reading := (kb_tail <> kb_head); end else if (kb_current_key <> 0) and kb_key_held then begin { Key still physically held — keep reporting pressed for LVGL long-press repeat } data^.key := kb_current_key; @@ -1958,7 +1977,7 @@ var begin { Ctrl+D toggles debug overlay (press only) } if key_info.is_down_code and key_info.CTRL_DOWN and (key_info.key_code = ord('d')) then begin - app.uidebug.toggle; + if onDebugToggle <> nil then onDebugToggle; exit; end; @@ -2002,7 +2021,7 @@ begin end; { ============================================================ - 1024Hz timer tick — called from arch.x86.isr.tmr0 (~1ms per tick) + PIT timer tick — called from arch.platform.interrupts ============================================================ } procedure lvgl_timer_tick(data: void); begin @@ -2025,6 +2044,7 @@ begin debug.tracer.push_trace('driver.video.lvgl.init.enter'); tick_accumulator := 0; + tick_fraction := 0; kb_head := 0; kb_tail := 0; kb_current_key := 0; @@ -2068,7 +2088,7 @@ begin driver.hid.keyboard.hook(@lvgl_keyboard_hook); { Hook 1024Hz timer for accurate LVGL tick } - arch.x86.isr.tmr0.hook(uint32(@lvgl_timer_tick)); + arch.platform.interrupts.registerTimer(@lvgl_timer_tick); { Register for GPU mode-change notifications so LVGL resolution updates automatically } driver.video.gpu.registerModeChangeCallback(@lvglModeChanged); @@ -2112,16 +2132,21 @@ end; function lvgl_handler: uint32; var elapsed: uint32; + irqFlags: PtrUInt; scroll_delta: sint32; mx, my: sint32; pt: lv_point_t; target, walk: Plv_obj; begin { Feed accumulated ticks to LVGL } + irqFlags := arch.platform.cpu.lock; elapsed := tick_accumulator; tick_accumulator := 0; - if elapsed > 0 then - lv_tick_inc(elapsed); + arch.platform.cpu.unlock(irqFlags); + tick_fraction := tick_fraction + uint64(elapsed) * PIT_DIVISOR * 1000; + elapsed := tick_fraction div PIT_INPUT_HZ; + tick_fraction := tick_fraction mod PIT_INPUT_HZ; + if elapsed > 0 then lv_tick_inc(elapsed); { Process scroll wheel } scroll_delta := driver.hid.mouse.getMouseScroll; diff --git a/src/driver/video/driver.video.pas b/src/driver/video/driver.video.pas index d858287b..b10c635d 100644 --- a/src/driver/video/driver.video.pas +++ b/src/driver/video/driver.video.pas @@ -22,8 +22,8 @@ unit driver.video; interface uses - boot.mgr, io.syslog, - memory.heap, debug.tracer, core.gfx.color, driver.video.types, core.ds.hashmap, core.util, arch.x86.util, core.gfx.texture, driver.video.gpu, core.gfx.fonts; + io.syslog, + memory.heap, debug.tracer, core.gfx.color, driver.video.types, core.ds.hashmap, core.util, arch.platform.cpu, core.gfx.texture, driver.video.gpu, core.gfx.fonts; procedure init(); procedure DrawPixel(X : uint32; Y : uint32; Pixel : TRGB32); @@ -31,7 +31,7 @@ procedure DrawLine(x1,y1,x2,y2 : uint32; thickness : uint32; Color : TRGB32); procedure DrawRect(x1,y1,x2,y2 : uint32; line_thickness : uint32; Color : TRGB32); procedure FillRect(x1,y1,x2,y2 : uint32; line_thickness : uint32; Line_Color : TRGB32; Fill_Color : TRGB32); procedure Flush(); -procedure reinit(fb_addr, width, height, pitch : uint32; bpp : uint8); +procedure reinit(fb_addr : PtrUInt; width, height, pitch : uint32; bpp : uint8); function register(DriverIdentifier : pchar; EnableCallback : FEnableDriver) : boolean; function enable(DriverIdentifier : pchar) : boolean; @@ -42,12 +42,12 @@ function frontBufferBpp : uint8; function backBufferWidth : uint32; function backBufferHeight : uint32; function backBufferBpp : uint8; -function backBufferLocation : uint32; -function frontBufferLocation : uint32; +function backBufferLocation : PtrUInt; +function frontBufferLocation : PtrUInt; Procedure basicFDrawTexture(Buffer : PVideoBuffer; X : uint32; Y : uint32; Texture : PTexture); -{ Text drawing — renders 8x16 bitmap font glyphs directly to the +{ Text drawing — renders 8x16 bitmap font glyphs directly to the framebuffer via DrawPixel. Useful for panic screens and other contexts where LVGL is unavailable. } function DrawChar(X, Y : uint32; C : char; Color : TRGB32) : uint32; @@ -58,8 +58,9 @@ function DrawInt(X, Y : uint32; Value : uint32; Color : TRGB32) : uint32; implementation uses + driver.hid.mouse, driver.video.vesa8, driver.video.vesa16, driver.video.vesa24, driver.video.vesa32, - driver.video.lvgl, driver.video.doublebuffer, driver.video.vesa; + driver.video.doublebuffer; Procedure dummyFDrawPixel(Buffer : PVideoBuffer; X : uint32; Y : uint32; Pixel : TRGB32); begin @@ -206,7 +207,7 @@ var DriverMap : PHashMap; { ============================================================ - GPU mode-change callback — reinits front/back buffers + GPU mode-change callback — reinits front/back buffers ============================================================ } procedure videoModeChanged(const info : TGPUModeInfo); begin @@ -221,6 +222,7 @@ var begin debug.tracer.push_trace('driver.video.init.enter'); io.syslog.logln('VIDEO', 'Initializing Base Video Driver.'); + driver.hid.mouse.setBounds(0, 0); //Ensure the frontbuffer is empty & nil, ready for initialization by a driver. VideoInterface.FrontBuffer.Initialized:= false; VideoInterface.FrontBuffer.Width:= 0; @@ -255,23 +257,6 @@ begin debug.tracer.push_trace('driver.video.init.exit'); end; -procedure lateInit(); -begin - driver.video.vesa.init(@driver.video.register); - driver.video.doublebuffer.init(@driver.video.register); - - io.syslog.logln('VIDEO', 'VESA Enable.'); - driver.video.enable('VESA'); - - io.syslog.logln('VIDEO', 'Double Buffer Enable.'); - driver.video.enable('BASIC_DOUBLE_BUFFER'); - - { Initialize LVGL early so the boot.splash screen can use it } - io.syslog.logln('VIDEO', 'LVGL: INIT BEGIN.'); - lvgl_init(driver.video.frontBufferWidth, driver.video.frontBufferHeight); - io.syslog.logln('VIDEO', 'LVGL: INIT END.'); -end; - function register(DriverIdentifier : pchar; EnableCallback : FEnableDriver) : boolean; var Value : Void; @@ -297,6 +282,7 @@ begin Value:= core.ds.hashmap.get(DriverMap, DriverIdentifier); if (Value <> nil) then begin enable:= FEnableDriver(Value)(@VideoInterface); + driver.hid.mouse.setBounds(frontBufferWidth, frontBufferHeight); end; debug.tracer.push_trace('driver.video.enable.exit'); end; @@ -304,7 +290,9 @@ end; procedure DrawPixel(X : uint32; Y : uint32; Pixel : TRGB32); begin //debug.tracer.push_trace('driver.video.DrawPixel.enter'); - VideoInterface.DrawRoutines.DrawPixel(VideoInterface.DefaultBuffer, X, Y, Pixel); + if (X < VideoInterface.DefaultBuffer^.Width) and + (Y < VideoInterface.DefaultBuffer^.Height) then + VideoInterface.DrawRoutines.DrawPixel(VideoInterface.DefaultBuffer, X, Y, Pixel); //debug.tracer.push_trace('driver.video.DrawPixel.exit'); end; @@ -360,18 +348,18 @@ begin backBufferBpp:= VideoInterface.BackBuffer.BitsPerPixel; end; -function backBufferLocation : uint32; +function backBufferLocation : PtrUInt; begin backBufferLocation:= VideoInterface.BackBuffer.Location; end; -function frontBufferLocation : uint32; +function frontBufferLocation : PtrUInt; begin frontBufferLocation:= VideoInterface.FrontBuffer.Location; end; { ============================================================ - Text drawing — 8x16 bitmap font via Std_Font + Text drawing — 8x16 bitmap font via Std_Font ============================================================ } const @@ -445,7 +433,7 @@ begin DrawInt := DrawString(X, Y, @buf[0], Color); end; -procedure reinit(fb_addr, width, height, pitch : uint32; bpp : uint8); +procedure reinit(fb_addr : PtrUInt; width, height, pitch : uint32; bpp : uint8); begin debug.tracer.push_trace('driver.video.reinit.enter'); { Free old back buffer if allocated } @@ -468,8 +456,10 @@ begin { Set front buffer directly from GPU mode info } VideoInterface.FrontBuffer.Location := fb_addr; + VideoInterface.FrontBuffer.Pitch := pitch; VideoInterface.FrontBuffer.Width := width; VideoInterface.FrontBuffer.Height := height; + driver.hid.mouse.setBounds(width, height); VideoInterface.FrontBuffer.BitsPerPixel := bpp; if fb_addr <> 0 then VideoInterface.FrontBuffer.Initialized := true; @@ -488,8 +478,4 @@ begin debug.tracer.push_trace('driver.video.reinit.exit'); end; -initialization - boot.mgr.registerBoot('driver.video', @init, 'Video Driver Initialization', 'driver.video.gpu'); - boot.mgr.registerBoot('driver.video_late', @lateInit, 'Video Driver Late Initialization', 'driver.video.*'); - end. diff --git a/src/driver/video/driver.video.types.pas b/src/driver/video/driver.video.types.pas index eb8c500c..2db1015a 100644 --- a/src/driver/video/driver.video.types.pas +++ b/src/driver/video/driver.video.types.pas @@ -26,7 +26,7 @@ uses type //Arbitrary pointer to a raw driver.video buffer in memory - VideoBuffer = uint32; + VideoBuffer = PtrUInt; //Struct representing a Memory Mapped Video Buffer TVideoBuffer = record @@ -40,6 +40,7 @@ type Width : uint32; //Height of the buffer. Height : uint32; + Pitch : uint32; end; //Pointer to a driver.video buffer PVideoBuffer = ^TVideoBuffer; diff --git a/src/driver/video/driver.video.vesa.pas b/src/driver/video/driver.video.vesa.pas index 5c510a31..3b37ef7d 100644 --- a/src/driver/video/driver.video.vesa.pas +++ b/src/driver/video/driver.video.vesa.pas @@ -83,6 +83,7 @@ begin VideoBuffer^.BitsPerPixel:= BitsPerPixel; VideoBuffer^.Width:= Width; VideoBuffer^.Height:= Height; + VideoBuffer^.Pitch := Pitch; allocateVESAFrameBuffer(VideoBuffer^.Location, VideoBuffer^.Width, VideoBuffer^.Height, BitsPerPixel); if VideoBuffer^.Location <> 0 then VideoBuffer^.Initialized:= True; diff --git a/src/driver/video/driver.video.vesa16.pas b/src/driver/video/driver.video.vesa16.pas index 71c24062..73054557 100644 --- a/src/driver/video/driver.video.vesa16.pas +++ b/src/driver/video/driver.video.vesa16.pas @@ -22,7 +22,7 @@ unit driver.video.vesa16; interface uses - driver.video.types, driver.video.vesa, debug.tracer, core.gfx.color; + driver.video.types, debug.tracer, core.gfx.color; //Init the draw routines by providing what we support through the DrawRoutines struct. procedure init(DrawRoutines : PDrawRoutines); diff --git a/src/driver/video/driver.video.vesa24.pas b/src/driver/video/driver.video.vesa24.pas index fd5c7b1f..a9139cca 100644 --- a/src/driver/video/driver.video.vesa24.pas +++ b/src/driver/video/driver.video.vesa24.pas @@ -22,7 +22,7 @@ unit driver.video.vesa24; interface uses - driver.video.types, driver.video.vesa, debug.tracer, core.gfx.color; + driver.video.types, debug.tracer, core.gfx.color; //Init the draw routines by providing what we support through the DrawRoutines struct. procedure init(DrawRoutines : PDrawRoutines); diff --git a/src/driver/video/driver.video.vesa32.pas b/src/driver/video/driver.video.vesa32.pas index 436c178d..65af12ef 100644 --- a/src/driver/video/driver.video.vesa32.pas +++ b/src/driver/video/driver.video.vesa32.pas @@ -22,7 +22,7 @@ unit driver.video.vesa32; interface uses - driver.video.types, driver.video.vesa, debug.tracer, core.gfx.color; + driver.video.types, debug.tracer, core.gfx.color; //Init the draw routines by providing what we support through the DrawRoutines struct. procedure init(DrawRoutines : PDrawRoutines); @@ -37,7 +37,7 @@ var begin //debug.tracer.push_trace('vesa32.DrawPixel.enter'); Location:= Puint32(Buffer^.Location); - LocationIndex:= (Y * Buffer^.Width) + X; + LocationIndex:= (Y * (Buffer^.Pitch div 4)) + X; Location[LocationIndex]:= uint32(Pixel); //debug.tracer.push_trace('vesa32.DrawPixel.exit'); end; diff --git a/src/driver/video/driver.video.vesa8.pas b/src/driver/video/driver.video.vesa8.pas index 4d930976..cc55741d 100644 --- a/src/driver/video/driver.video.vesa8.pas +++ b/src/driver/video/driver.video.vesa8.pas @@ -22,7 +22,7 @@ unit driver.video.vesa8; interface uses - driver.video.types, driver.video.vesa, debug.tracer, core.gfx.color; + driver.video.types, debug.tracer, core.gfx.color; //Init the draw routines by providing what we support through the DrawRoutines struct. procedure init(DrawRoutines : PDrawRoutines); diff --git a/src/io/io.stdio.pas b/src/io/io.stdio.pas index e90cd5b5..f7e8d133 100644 --- a/src/io/io.stdio.pas +++ b/src/io/io.stdio.pas @@ -1,3 +1,15 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + { Stdio - Standard I/O abstraction for command-line programs. @@ -15,43 +27,18 @@ unit io.stdio; interface uses - boot.mgr, - memory.heap, core.strings, core.util, arch.x86.util, debug.tracer; + io.types, memory.heap, core.strings, core.util; type - { Output buffer — kalloc'd, growable } - POutBuf = ^TOutBuf; - TOutBuf = record - buf : pchar; - len : uint32; - cap : uint32; - end; - - { Parameter linked list } - PParamList = ^TParamList; - TParamList = record - Param : pchar; - Next : PParamList; - end; - - { Command buffer type } - TCommandBuffer = array[0..1023] of byte; - - { Command method — receives params plus stdin (read), stdout (write), stderr (write) } - TCommandMethod = procedure(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); - - { Command record } - PCommand = ^TCommand; - TCommand = record - registered : boolean; - hidden : boolean; - command : pchar; - method : TCommandMethod; - description : pchar; - end; - - { Halt callback } - THaltCallback = procedure(); + POutBuf = io.types.POutBuf; + TOutBuf = io.types.TOutBuf; + PParamList = io.types.PParamList; + TParamList = io.types.TParamList; + TCommandBuffer = io.types.TCommandBuffer; + TCommandMethod = io.types.TCommandMethod; + PCommand = io.types.PCommand; + TCommand = io.types.TCommand; + THaltCallback = io.types.THaltCallback; { Output buffer management } function createOutBuf(initial_cap: uint32): POutBuf; @@ -92,9 +79,6 @@ procedure freeParams(params: PParamList); function paramCount(params: PParamList): uint32; function getParam(index: uint32; params: PParamList): pchar; -{ Working directory } -function getWorkingDirectory: pchar; -procedure setWorkingDirectory(str: pchar); { Halt mechanism for async commands } function halt(id: uint32; cb: THaltCallback): boolean; @@ -107,11 +91,12 @@ var { Initialization } procedure init; +procedure UnitTest; implementation uses - core.version, driver.timer.rtc, driver.io.serial, driver.storage.vfs, io.syslog; + arch.platform.diagnostics, arch.platform.cpu; const INITIAL_CMD_CAP = 64; @@ -122,7 +107,6 @@ var CmdCap : uint32 = 0; { ---- Output buffer ---- } - function createOutBuf(initial_cap: uint32): POutBuf; var ob: POutBuf; @@ -130,10 +114,15 @@ var begin if initial_cap = 0 then cap := 1 else cap := initial_cap; ob := POutBuf(kalloc(SizeOf(TOutBuf))); + createOutBuf := nil; + if ob = nil then exit; ob^.buf := pchar(kalloc(cap)); + if ob^.buf = nil then begin + kfree(void(ob)); + exit; + end; ob^.len := 0; ob^.cap := cap; - memset(uint32(ob^.buf), 0, cap); createOutBuf := ob; end; @@ -152,29 +141,41 @@ begin buf^.buf[0] := #0; end; -procedure bufGrow(buf: POutBuf; needed: uint32); +function bufGrow(buf: POutBuf; needed: uint32): boolean; var - newCap: uint32; + newCap, required: uint32; newBuf: pchar; begin - if buf^.len + needed < buf^.cap then exit; + bufGrow := false; + if needed >= high(uint32) - buf^.len then exit; + required := buf^.len + needed + 1; + if required <= buf^.cap then begin + bufGrow := true; + exit; + end; newCap := buf^.cap; if newCap = 0 then newCap := 64; - while newCap <= buf^.len + needed do + while newCap < required do begin + if newCap > high(uint32) div 2 then begin + newCap := required; + break; + end; newCap := newCap * 2; + end; newBuf := pchar(kalloc(newCap)); - memset(uint32(newBuf), 0, newCap); - if buf^.len > 0 then - memcpy(uint32(buf^.buf), uint32(newBuf), buf^.len); + if newBuf = nil then exit; + memcpy(PtrUInt(buf^.buf), PtrUInt(newBuf), buf^.len); + newBuf[buf^.len] := #0; kfree(void(buf^.buf)); buf^.buf := newBuf; buf^.cap := newCap; + bufGrow := true; end; procedure bufWriteChar(buf: POutBuf; c: char); begin if buf = nil then exit; - bufGrow(buf, 2); + if not bufGrow(buf, 1) then exit; buf^.buf[buf^.len] := c; Inc(buf^.len); buf^.buf[buf^.len] := #0; @@ -182,14 +183,21 @@ end; procedure bufWriteStr(buf: POutBuf; s: pchar); var - i: uint32; + len, offset: uint32; + aliases: boolean; begin if (buf = nil) or (s = nil) then exit; - i := 0; - while s[i] <> #0 do begin - bufWriteChar(buf, s[i]); - Inc(i); - end; + len := stringSize(s); + { A source inside the buffer must follow it if growth relocates storage. } + aliases := (PtrUInt(s) >= PtrUInt(buf^.buf)) and + (PtrUInt(s) - PtrUInt(buf^.buf) < buf^.cap); + offset := 0; + if aliases then offset := PtrUInt(s) - PtrUInt(buf^.buf); + if not bufGrow(buf, len) then exit; + if aliases then s := buf^.buf + offset; + memcpy(PtrUInt(s), PtrUInt(buf^.buf + buf^.len), len); + inc(buf^.len, len); + buf^.buf[buf^.len] := #0; end; procedure bufWriteStrLn(buf: POutBuf; s: pchar); @@ -307,7 +315,6 @@ begin end; { ---- ANSI core.gfx.color helpers ---- } - procedure bufSetColor(buf: POutBuf; fg: uint8); begin { ESC[38;5;m } @@ -332,29 +339,26 @@ begin end; { ---- Dynamic command registry ---- } - -procedure growCommands; +function growCommands: boolean; var newCap: uint32; newArr: PCommand; - i: uint32; begin - newCap := CmdCap * 2; - newArr := PCommand(kalloc(newCap * SizeOf(TCommand))); - memset(uint32(newArr), 0, newCap * SizeOf(TCommand)); - { Copy existing } - for i := 0 to CmdCap - 1 do begin - PCommand(uint32(newArr) + i * SizeOf(TCommand))^ := - PCommand(uint32(Commands) + i * SizeOf(TCommand))^; - end; + growCommands := false; + if CmdCap > high(uint32) div sizeof(TCommand) div 2 then exit; + if CmdCap = 0 then newCap := INITIAL_CMD_CAP else newCap := CmdCap * 2; + newArr := PCommand(kalloc(newCap * sizeof(TCommand))); + if newArr = nil then exit; + memcpy(PtrUInt(Commands), PtrUInt(newArr), CmdCount * sizeof(TCommand)); kfree(void(Commands)); Commands := newArr; CmdCap := newCap; + growCommands := true; end; function getCmdPtr(index: uint32): PCommand; inline; begin - getCmdPtr := PCommand(uint32(Commands) + index * SizeOf(TCommand)); + getCmdPtr := PCommand(PtrUInt(Commands) + index * SizeOf(TCommand)); end; procedure registerCommand(command: pchar; method: TCommandMethod; description: pchar); @@ -366,7 +370,8 @@ procedure registerCommandEx(command: pchar; method: TCommandMethod; description: var cmd: PCommand; begin - if CmdCount >= CmdCap then growCommands; + if (command = nil) or (method = nil) then exit; + if CmdCount >= CmdCap then if not growCommands then exit; cmd := getCmdPtr(CmdCount); cmd^.registered := true; cmd^.hidden := hide; @@ -393,58 +398,53 @@ function findCommand(name: pchar): PCommand; var i: uint32; cmd: PCommand; - ua, ub: pchar; begin findCommand := nil; - ua := stringToUpper(name); + if (name = nil) or (CmdCount = 0) then exit; for i := 0 to CmdCount - 1 do begin cmd := getCmdPtr(i); - if cmd^.registered then begin - ub := stringToUpper(cmd^.command); - if stringEquals(ua, ub) then begin - kfree(void(ub)); - kfree(void(ua)); - findCommand := cmd; - exit; - end; - kfree(void(ub)); + if cmd^.registered and stringEqualsCI(name, cmd^.command) then begin + findCommand := cmd; + exit; end; end; - kfree(void(ua)); end; { ---- Parameter parsing ---- } - function getParams(var buf: TCommandBuffer): PParamList; var - start, finish: uint32; - size: uint32; - ptr: uint32; - root: PParamList; - current: PParamList; + start, finish, size: uint32; + root, current: PParamList; begin + getParams := nil; root := PParamList(kalloc(SizeOf(TParamList))); + if root = nil then exit; current := root; current^.next := nil; current^.Param := nil; - start := 0; finish := 0; - while buf[start] <> 0 do begin - while (char(buf[finish]) <> ' ') and (buf[finish] <> 0) do - Inc(finish); + while finish < sizeof(buf) do begin + while (finish < sizeof(buf)) and (buf[finish] = ord(' ')) do inc(finish); + if finish = sizeof(buf) then break; + if buf[finish] = 0 then break; + start := finish; + while (finish < sizeof(buf)) and (buf[finish] <> ord(' ')) and + (buf[finish] <> 0) do inc(finish); size := finish - start; - if size > 0 then begin - ptr := uint32(@buf[start]); - current^.Param := pchar(kalloc(size + 2)); - memset(uint32(current^.Param), 0, size + 2); - memcpy(uint32(ptr), uint32(current^.Param), size); - current^.next := PParamList(kalloc(SizeOf(TParamList))); - current := current^.next; - current^.next := nil; - current^.Param := nil; + current^.Param := stringNew(size); + if current^.Param = nil then begin + freeParams(root); + exit; end; - start := finish + 1; - Inc(finish); + memcpy(PtrUInt(@buf[start]), PtrUInt(current^.Param), size); + current^.next := PParamList(kalloc(SizeOf(TParamList))); + if current^.next = nil then begin + freeParams(root); + exit; + end; + current := current^.next; + current^.next := nil; + current^.Param := nil; end; getParams := root; end; @@ -499,78 +499,48 @@ begin getParam := search^.param; end; -{ ---- Working directory ---- } - -function getWorkingDirectory: pchar; -begin - getWorkingDirectory := driver.storage.vfs.GetWorkingDirectory; -end; - -procedure setWorkingDirectory(str: pchar); -begin - if str <> nil then - driver.storage.vfs.ChangeDirectory(str); -end; { ---- Halt mechanism ---- } - function halt(id: uint32; cb: THaltCallback): boolean; +var + flags: PtrUInt; begin - halt := false; + flags := arch.platform.cpu.lock; + halt := not Halted; if not Halted then begin - Halted := true; - halt := true; HaltID := id; HaltCB := cb; + Halted := true; end; + arch.platform.cpu.unlock(flags); end; function done(id: uint32): boolean; +var + flags: PtrUInt; + callback: THaltCallback; begin - done := false; - if Halted then begin - if id = HaltID then begin - if HaltCB <> nil then HaltCB(); - HaltCB := nil; - Halted := false; - HaltID := 0; - done := true; - end; + flags := arch.platform.cpu.lock; + done := Halted and (id = HaltID); + callback := nil; + if done then begin + callback := HaltCB; + HaltCB := nil; + Halted := false; + HaltID := 0; end; + arch.platform.cpu.unlock(flags); + if callback <> nil then callback(); end; { ---- Built-in commands ---- } -procedure cmd_version(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); -begin - bufWriteStr(stdout_buf, ' Asuro Version: '); - bufWriteStrLn(stdout_buf, core.version.VERSION); - bufWriteStr(stdout_buf, ' Compiled on: '); - bufWriteStr(stdout_buf, core.version.COMPILE_DATE); - bufWriteStr(stdout_buf, ' '); - bufWriteStrLn(stdout_buf, core.version.COMPILE_TIME); - bufWriteStrLn(stdout_buf, ' Compiled With: '); - bufWriteStr(stdout_buf, ' NASM - Version: '); - bufWriteStrLn(stdout_buf, core.version.NASM_VERSION); - bufWriteStr(stdout_buf, ' FPC - Version: '); - bufWriteStrLn(stdout_buf, core.version.FPC_VERSION); - bufWriteStr(stdout_buf, ' MAKE - Version: '); - bufWriteStrLn(stdout_buf, core.version.MAKE_VERSION); - bufWriteStr(stdout_buf, ' '); - bufWriteInt(stdout_buf, core.version.LINE_COUNT); - bufWriteStr(stdout_buf, ' lines, across '); - bufWriteInt(stdout_buf, core.version.FILE_COUNT); - bufWriteStrLn(stdout_buf, ' files.'); - bufWriteStr(stdout_buf, ' Baked Drivers: '); - bufWriteIntLn(stdout_buf, core.version.DRIVER_COUNT); - bufWriteStr(stdout_buf, ' Checksum: '); - bufWriteStrLn(stdout_buf, core.version.CHECKSUM); -end; procedure cmd_echo(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); var current: PParamList; begin + if params = nil then exit; current := params^.next; while (current <> nil) and (current^.param <> nil) do begin bufWriteStr(stdout_buf, current^.param); @@ -596,6 +566,7 @@ var len: uint32; cmd: PCommand; begin + if CmdCount = 0 then exit; longestCommand := 0; for i := 0 to CmdCount - 1 do begin cmd := getCmdPtr(i); @@ -619,63 +590,132 @@ begin end; end; -procedure cmd_time(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); + var - dt: TDateTime; + testCallbackCount: uint32; + testCallbackReentered: boolean; + +procedure testHaltCallback; begin - dt := getDateTime; - if dt.Day < 10 then bufWriteChar(stdout_buf, '0'); - bufWriteInt(stdout_buf, dt.Day); - bufWriteChar(stdout_buf, '/'); - if dt.Month < 10 then bufWriteChar(stdout_buf, '0'); - bufWriteInt(stdout_buf, dt.Month); - bufWriteChar(stdout_buf, '/'); - bufWriteInt(stdout_buf, dt.Century); - bufWriteInt(stdout_buf, dt.Year); - bufWriteChar(stdout_buf, ' '); - if dt.Hours < 10 then bufWriteChar(stdout_buf, '0'); - bufWriteInt(stdout_buf, dt.Hours); - bufWriteChar(stdout_buf, ':'); - if dt.Minutes < 10 then bufWriteChar(stdout_buf, '0'); - bufWriteInt(stdout_buf, dt.Minutes); - bufWriteChar(stdout_buf, ':'); - if dt.Seconds < 10 then bufWriteChar(stdout_buf, '0'); - bufWriteIntLn(stdout_buf, dt.Seconds); - bufWriteStr(stdout_buf, 'Weekday: '); - bufWriteStrLn(stdout_buf, WeekdayToString(dt.Weekday)); + inc(testCallbackCount); + testCallbackReentered := (not done(41)) and halt(42, nil); end; -procedure cmd_reboot(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); +{ Tests use an isolated command registry; production registrations are restored. } +procedure UnitTest; +type PCommandBuffer = ^TCommandBuffer; +var + input: PCommandBuffer; + params: PParamList; + output: POutBuf; + oldCommands: PCommand; + oldHalted: boolean; + oldHaltID: uint32; + oldHaltCB: THaltCallback; + oldCount, oldCap, i, passed, failed: uint32; + beforeFree: SizeUInt; + + procedure Assert(condition: boolean; name: pchar); + begin + if condition then inc(passed) else begin + inc(failed); + arch.platform.diagnostics.log('STDIO FAIL', name); + end; + end; begin - resetSystem; + passed := 0; + failed := 0; + beforeFree := lmm_total_free; + oldHalted := Halted; + oldHaltID := HaltID; + oldHaltCB := HaltCB; + Halted := false; + HaltID := 0; + HaltCB := nil; + testCallbackCount := 0; + testCallbackReentered := false; + Assert(halt(41, @testHaltCallback) and not halt(42, nil), 'halt reserves one owner'); + Assert(not done(42) and Halted, 'wrong completion preserves halt owner'); + Assert(done(41) and (testCallbackCount = 1) and testCallbackReentered and + Halted and (HaltID = 42), 'completion detaches before callback reentry'); + Assert(done(42) and not done(42), 'completion releases exactly once'); + Halted := oldHalted; + HaltID := oldHaltID; + HaltCB := oldHaltCB; + oldCommands := Commands; + oldCount := CmdCount; + oldCap := CmdCap; + Commands := nil; + CmdCount := 0; + CmdCap := 0; + Assert(findCommand('missing') = nil, 'empty registry lookup'); + for i := 0 to 69 do registerCommand('ECHO', @cmd_echo, 'test'); + Assert((getCommandCount = 70) and (getCommand(69) <> nil) and + (getCommand(70) = nil), 'registry growth and bounds'); + Assert(findCommand('echo') = getCommand(0), 'case-insensitive command lookup'); + output := createOutBuf(1); + input := PCommandBuffer(kalloc(sizeof(TCommandBuffer))); + if (output <> nil) and (input <> nil) then begin + bufWriteStr(output, 'grow'); + bufWriteStr(output, output^.buf); + Assert(stringEquals(output^.buf, 'growgrow'), 'self append across buffer relocation'); + bufClear(output); + bufWriteInt(output, low(integer)); + Assert(stringEquals(output^.buf, '-2147483648'), 'signed integer formatting'); + bufClear(output); + bufWriteHex(output, $89ABCDEF); + Assert(stringEquals(output^.buf, '0x89ABCDEF'), 'hexadecimal formatting'); + bufClear(output); + memset(PtrUInt(input), 0, sizeof(TCommandBuffer)); + memcpy(PtrUInt(pchar('echo one two')), PtrUInt(input), 13); + params := getParams(input^); + Assert((paramCount(params) = 2) and stringEquals(getParam(1, params), 'two') and + (getParam(2, params) = nil), 'parser terminates at final token'); + findCommand('EcHo')^.method(params, nil, output, nil); + Assert(stringEquals(output^.buf, 'one two '#10), 'registered ECHO dispatch'); + freeParams(params); + memset(PtrUInt(input), ord('x'), sizeof(TCommandBuffer)); + params := getParams(input^); + Assert((params <> nil) and (stringSize(params^.param) = sizeof(TCommandBuffer)), + 'parser bounded without terminator'); + freeParams(params); + memset(PtrUInt(input), ord(' '), sizeof(TCommandBuffer)); + input^[1023] := ord('z'); + params := getParams(input^); + Assert((params <> nil) and stringEquals(params^.param, 'z'), 'parser final byte'); + freeParams(params); + input^[0] := 0; + params := getParams(input^); + Assert((paramCount(params) = 0) and (getParam(0, params) = nil), 'empty parameters'); + freeParams(params); + Assert(getParam(0, nil) = nil, 'nil parameter lookup'); + end else Assert(false, 'stdio test allocation'); + freeOutBuf(output); + kfree(void(input)); + kfree(void(Commands)); + Commands := oldCommands; + CmdCount := oldCount; + CmdCap := oldCap; + Assert(lmm_total_free = beforeFree, 'stdio allocation ownership restored'); + arch.platform.diagnostics.testSummary('STDIO', 'STDIO_TESTS_PASSED', 'STDIO_TESTS_FAILED', passed, failed); end; { ---- Initialization ---- } - procedure init; begin - { Allocate initial command array } - CmdCap := INITIAL_CMD_CAP; - CmdCount := 0; - Commands := PCommand(kalloc(CmdCap * SizeOf(TCommand))); - memset(uint32(Commands), 0, CmdCap * SizeOf(TCommand)); + if Commands <> nil then exit; + if not growCommands then exit; + UnitTest; Halted := false; HaltID := 0; HaltCB := nil; { Register built-in commands } - registerCommand('VERSION', @cmd_version, 'Display the running version of Asuro.'); - registerCommand('CLEAR', @cmd_clear, 'Clear the Screen.'); + registerCommand('CLEAR', @cmd_clear, 'Clear the Screen.'); registerCommand('HELP', @cmd_help, 'Lists all registered commands and their description.'); registerCommand('ECHO', @cmd_echo, 'Echo''s text to the terminal.'); - registerCommand('TIME', @cmd_time, 'Print the current time.'); - registerCommand('REBOOT', @cmd_reboot, 'Reboot the system.'); + end; - io.syslog.logln('STDIO', 'Initialized.'); -end; - -initialization - boot.mgr.registerBoot('io.stdio', @init, 'Standard I/O Abstraction', BOOT_MGR_BARRIER_EARLY); end. diff --git a/src/io/io.syslog.pas b/src/io/io.syslog.pas index a19a0f00..35e6ed6e 100644 --- a/src/io/io.syslog.pas +++ b/src/io/io.syslog.pas @@ -11,8 +11,6 @@ unit io.syslog; interface -uses - boot.mgr; type TLogHook = procedure(msg: pchar); @@ -297,7 +295,4 @@ begin If we registered serialHook, driver.io.serial would get double output. } end; -initialization - boot.mgr.registerBoot('io.syslog', @init, 'Syslog Interface', 'driver.io.serial'); - end. diff --git a/src/io/io.types.pas b/src/io/io.types.pas new file mode 100644 index 00000000..0b4895dd --- /dev/null +++ b/src/io/io.types.pas @@ -0,0 +1,54 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared I/O data contracts without console, driver or runtime dependencies. + Types extracted from io.stdio by Kieron Morris. } +unit io.types; + +interface +type + { Output buffer — kalloc'd, growable } + POutBuf = ^TOutBuf; + TOutBuf = record + buf : pchar; + len : uint32; + cap : uint32; + end; + + { Parameter linked list } + PParamList = ^TParamList; + TParamList = record + Param : pchar; + Next : PParamList; + end; + + { Command buffer type } + TCommandBuffer = array[0..1023] of byte; + + { Command method — receives params plus stdin (read), stdout (write), stderr (write) } + TCommandMethod = procedure(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf); + + { Command record } + PCommand = ^TCommand; + TCommand = record + registered : boolean; + hidden : boolean; + command : pchar; + method : TCommandMethod; + description : pchar; + end; + + { Halt callback } + THaltCallback = procedure(); + +implementation +end. diff --git a/src/memory/memory.bitmap.pas b/src/memory/memory.bitmap.pas new file mode 100644 index 00000000..1889490b --- /dev/null +++ b/src/memory/memory.bitmap.pas @@ -0,0 +1,101 @@ +// Copyright 2021 Kieron Morris & Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared bitmap operations extracted from memory.heap. } +unit memory.bitmap; + +interface +function find_free_run(bmp: PuInt32; start, count, total: uint32): sint32; +procedure bitmap_set_range(bmp: PuInt32; start, count: uint32); +procedure bitmap_clear_range(bmp: PuInt32; start, count: uint32); +function bitmap_all_set(bmp: PuInt32; start, count: uint32): boolean; + +implementation + +function find_free_run(bmp: PuInt32; start, count, total: uint32): sInt32; +var + idx, run_len, dw_idx: uint32; + dw: uint32; +begin + find_free_run := -1; + if (count = 0) or (count > total) then exit; + idx := start; + while idx <= total - count do begin + dw_idx := idx shr 5; + dw := bmp[dw_idx]; + + { Fast skip: entire dword fully allocated } + if dw = $FFFFFFFF then begin + idx := (dw_idx + 1) shl 5; + continue; + end; + + { Check if bit at idx is free (0) } + if (dw and (uint32(1) shl (idx and 31))) <> 0 then begin + inc(idx); + continue; + end; + + { Free bit at idx - verify contiguous run of 'count' } + run_len := 0; + while run_len < count do begin + if idx + run_len >= total then break; + if (bmp[(idx + run_len) shr 5] and + (uint32(1) shl ((idx + run_len) and 31))) <> 0 then + break; + inc(run_len); + end; + + if run_len >= count then begin + find_free_run := sInt32(idx); + exit; + end; + + { Skip past the used bit that broke the run } + idx := idx + run_len + 1; + end; +end; + +procedure bitmap_set_range(bmp: PuInt32; start, count: uint32); +var + i: uint32; +begin + if count = 0 then exit; + for i := start to start + count - 1 do + bmp[i shr 5] := bmp[i shr 5] or (uint32(1) shl (i and 31)); +end; + +procedure bitmap_clear_range(bmp: PuInt32; start, count: uint32); +var + i: uint32; +begin + if count = 0 then exit; + for i := start to start + count - 1 do + bmp[i shr 5] := bmp[i shr 5] and not (uint32(1) shl (i and 31)); +end; + +{ Check that all bits in a range are set (for double-free detection). } +function bitmap_all_set(bmp: PuInt32; start, count: uint32): boolean; +var + i: uint32; +begin + bitmap_all_set := true; + if count = 0 then exit; + for i := start to start + count - 1 do begin + if (bmp[i shr 5] and (uint32(1) shl (i and 31))) = 0 then begin + bitmap_all_set := false; + exit; + end; + end; +end; + +end. diff --git a/src/memory/memory.heap.pas b/src/memory/memory.heap.pas index 44987c91..3ef56552 100644 --- a/src/memory/memory.heap.pas +++ b/src/memory/memory.heap.pas @@ -1,4 +1,5 @@ // Copyright 2021 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,9 +21,9 @@ tracks which 8-byte allocation units are free or allocated. Layout of a 4 MiB heap page: - [0x000000..0x000017] THeapPageHeader (24 bytes) - [0x000018..0x00FC17] Bitmap (64512 bytes = 516096 bits) - [0x00FC18..0x00FFFF] Padding (1000 bytes) + Header: 24 bytes on i386, 32 bytes on x86_64 + Bitmap: 64512 bytes immediately following the header + Padding: remainder of the first 64 KiB [0x010000..0x3FFFFF] Data area (4128768 bytes = 516096 x 8) Each allocation prepends a 16-byte size prefix (4 bytes for the unit @@ -40,13 +41,7 @@ unit memory.heap; interface -uses - boot.mgr, - arch.x86.memory.physical, - io.syslog, - debug.tracer, - core.util, arch.x86.util, core.panic, - arch.x86.memory.virtual; +uses memory.bitmap; const ALLOC_UNIT = 8; { bytes per allocation unit } @@ -76,167 +71,57 @@ var Search_Page : PHeapPageHeader; procedure init; -function kalloc(size : uint32) : void; -function klalloc(size : uint32) : void; -procedure klfree(address : uint32); -function kpalloc(address : uint32) : void; +procedure UnitTest; +{ Returned allocations are zero-filled, including reused storage. } +function kalloc(size : SizeUInt) : void; +function klalloc(size : SizeUInt) : void; +procedure klfree(address : PtrUInt); +function kpalloc(address : PtrUInt) : void; procedure kfree(area : void); { Statistics } -function lmm_total_free : uint32; +function lmm_total_free : SizeUInt; function lmm_page_count : uint32; implementation -{ ==== Internal helpers ==== } +uses arch.platform.memory, arch.platform.cpu, arch.platform.diagnostics; +procedure GPF; +begin + fatal('LMM', 'FAIL: heap invariant'); +end; + +procedure heap_log(message: pchar); +begin + arch.platform.diagnostics.log('LMM', message); +end; + +procedure heap_oom(message: pchar); +begin + fatal('OOM', message); +end; + +{ ==== Internal helpers ==== } function get_bitmap(hp: PHeapPageHeader): PuInt32; inline; begin - get_bitmap := PuInt32(uint32(hp) + uint32(sizeof(THeapPageHeader))); + get_bitmap := PuInt32(PtrUInt(hp) + uint32(sizeof(THeapPageHeader))); end; -function get_data_base(hp: PHeapPageHeader): uint32; inline; +function get_data_base(hp: PHeapPageHeader): PtrUInt; inline; begin - get_data_base := uint32(hp) + DATA_OFFSET; -end; - -{ Zero a memory region using rep stosd + rep stosb. } -procedure zero_mem(dest: Pointer; bytes: uint32); -var - d: Pointer; - b: uint32; -begin - d := dest; - b := bytes; - if b = 0 then exit; - asm - push edi - mov edi, d - mov ecx, b - shr ecx, 2 - xor eax, eax - cld - rep stosd - mov ecx, b - and ecx, 3 - rep stosb - pop edi - end; -end; - -{ Find a contiguous run of 'count' zero (free) bits in the bitmap - starting at bit index 'start'. Returns the starting index or -1. } -function find_free_run(bmp: PuInt32; start, count, total: uint32): sInt32; -var - idx, run_len, dw_idx: uint32; - dw: uint32; -begin - find_free_run := -1; - idx := start; - while idx + count <= total do begin - dw_idx := idx shr 5; - dw := bmp[dw_idx]; - - { Fast skip: entire dword fully allocated } - if dw = $FFFFFFFF then begin - idx := (dw_idx + 1) shl 5; - continue; - end; - - { Check if bit at idx is free (0) } - if (dw and (uint32(1) shl (idx and 31))) <> 0 then begin - inc(idx); - continue; - end; - - { Free bit at idx - verify contiguous run of 'count' } - run_len := 0; - while run_len < count do begin - if idx + run_len >= total then break; - if (bmp[(idx + run_len) shr 5] and - (uint32(1) shl ((idx + run_len) and 31))) <> 0 then - break; - inc(run_len); - end; - - if run_len >= count then begin - find_free_run := sInt32(idx); - exit; - end; - - { Skip past the used bit that broke the run } - idx := idx + run_len + 1; - end; -end; - -procedure bitmap_set_range(bmp: PuInt32; start, count: uint32); -var - i: uint32; -begin - for i := start to start + count - 1 do - bmp[i shr 5] := bmp[i shr 5] or (uint32(1) shl (i and 31)); -end; - -procedure bitmap_clear_range(bmp: PuInt32; start, count: uint32); -var - i: uint32; -begin - for i := start to start + count - 1 do - bmp[i shr 5] := bmp[i shr 5] and not (uint32(1) shl (i and 31)); -end; - -{ Check that all bits in a range are set (for double-free detection). } -function bitmap_all_set(bmp: PuInt32; start, count: uint32): boolean; -var - i: uint32; -begin - bitmap_all_set := true; - for i := start to start + count - 1 do begin - if (bmp[i shr 5] and (uint32(1) shl (i and 31))) = 0 then begin - bitmap_all_set := false; - exit; - end; - end; -end; - -{ Restore interrupt flag if it was enabled before cli. - Uses sti instead of popfd to avoid restoring dangerous EFLAGS bits - (IOPL, NT, VM, AC) that could cause arch.x86.fault.gpf or Bad-TSS on next IRET. } -procedure restore_if(saved: uint32); inline; -begin - if (saved and $200) <> 0 then - asm sti end; + get_data_base := PtrUInt(hp) + DATA_OFFSET; end; { ==== Page management ==== } - -function new_lmm_page: uint32; -var - i: uint32; -begin - push_trace('memory.heap.new_lmm_page'); - new_lmm_page := 0; - i := KERNEL_PAGE_NUMBER + 4; - while i < 1024 do begin - if arch.x86.memory.virtual.new_page(i) then begin - new_lmm_page := i shl 22; - pop_trace; - exit; - end; - inc(i); - end; - io.syslog.logln('LMM', 'ERROR: new_lmm_page failed.'); - pop_trace; -end; - function new_heap_page(current: PHeapPageHeader): PHeapPageHeader; var - addr: uint32; + addr: PtrUInt; bmp: PuInt32; i: uint32; begin push_trace('memory.heap.new_heap_page'); - addr := new_lmm_page; + addr := arch.platform.memory.allocateHeapPage; if addr = 0 then begin new_heap_page := nil; pop_trace; @@ -284,160 +169,120 @@ begin end; { Free the virtual page (returns physical block to PMM) } - arch.x86.memory.virtual.free_page_at_address(uint32(hp)); + arch.platform.memory.release(PtrUInt(hp), 1); pop_trace; end; { ==== Public API ==== } - procedure init; begin push_trace('memory.heap.init'); - io.syslog.logln('LMM', 'INIT BEGIN.'); + heap_log('INIT BEGIN.'); Root_Page := new_heap_page(nil); Search_Page := Root_Page; if Root_Page = nil then begin - io.syslog.logln('LMM', 'FATAL: Could not allocate root heap page!'); + heap_log('FATAL: Could not allocate root heap page!'); GPF; end; - io.syslog.logln('LMM', 'INIT END.'); + heap_log('INIT END.'); pop_trace; end; -function kpalloc(address: uint32): void; -var - block: uint16; +function kpalloc(address: PtrUInt): void; begin - push_trace('memory.heap.kpalloc'); - block := address shr 22; - if block >= 1024 then begin - io.syslog.logln('LMM', 'ERROR: kpalloc block out of range.'); - GPF; - kpalloc := nil; - pop_trace; - exit; - end; - io.syslog.log('LMM', 'kpalloc: identity-map block '); - io.syslog.writeintln(block); - force_alloc_block(block, 0); - map_page(block, block); - kpalloc := void(block shl 22); - pop_trace; + kpalloc := void(arch.platform.memory.identityReserve(address)); end; -procedure klfree(address: uint32); +procedure klfree(address: PtrUInt); var - page_num : uint16; num_pages : uint32; - i : uint32; begin push_trace('memory.heap.klfree'); num_pages := PuInt32(address)^; - if (num_pages = 0) or (num_pages > 256) then begin - io.syslog.logln('LMM', 'ERROR: klfree invalid page count.'); + if (num_pages = 0) or (num_pages > MAX_HEAP_REGIONS) then begin + heap_log('ERROR: klfree invalid page count.'); GPF; pop_trace; exit; end; - page_num := address shr 22; - for i := 0 to num_pages - 1 do - arch.x86.memory.virtual.free_page(page_num + uint16(i)); + arch.platform.memory.release(address, num_pages); pop_trace; end; -function klalloc(size: uint32): void; +function klalloc(size: SizeUInt): void; var - total_size : uint32; + total_size : SizeUInt; pages : uint32; - curr_page : uint16; - i : uint16; - miss : boolean; - base_addr : uint32; + base_addr : PtrUInt; begin push_trace('memory.heap.klalloc'); klalloc := void(nil); { Need extra 16 bytes for header: [page_count:4][magic:4][pad:8] } + if (size = 0) or (size > High(SizeUInt) - PAGE_SIZE_LMM - SIZE_PREFIX) then begin + pop_trace; + exit; + end; total_size := size + 16; + if ((total_size + PAGE_SIZE_LMM - 1) div PAGE_SIZE_LMM) > MAX_HEAP_REGIONS then begin + pop_trace; + exit; + end; pages := (total_size + PAGE_SIZE_LMM - 1) div PAGE_SIZE_LMM; if pages = 0 then pages := 1; - { Scan low virtual pages (below core.version space) for contiguous free range } - curr_page := 5; - while uint32(curr_page) + pages - 1 < KERNEL_PAGE_NUMBER do begin - miss := false; - for i := 0 to pages - 1 do begin - if not page_mappable(curr_page + i) then begin - miss := true; - curr_page := curr_page + i; - break; - end; - end; - if not miss then begin - for i := 0 to pages - 1 do - arch.x86.memory.virtual.new_page(curr_page + i); - base_addr := uint32(curr_page) shl 22; - zero_mem(Pointer(base_addr), 16); - { Header: [page_count:4][magic:4][pad:8] → user at +16 (16-aligned) } - PuInt32(base_addr)^ := pages; - PuInt32(base_addr + 4)^ := LARGE_ALLOC_MAGIC; - klalloc := void(base_addr + 16); - pop_trace; - exit; - end; - inc(curr_page); + base_addr := arch.platform.memory.allocateLarge(pages); + if base_addr <> 0 then begin + arch.platform.cpu.zero(Pointer(base_addr), SIZE_PREFIX); + PuInt32(base_addr)^ := pages; + PuInt32(base_addr + 4)^ := LARGE_ALLOC_MAGIC; + klalloc := void(base_addr + SIZE_PREFIX); + pop_trace; + exit; end; - io.syslog.logln('LMM', 'ERROR: klalloc no contiguous pages.'); + heap_log('ERROR: klalloc no contiguous pages.'); pop_trace; end; -function kalloc(size: uint32): void; [public, alias: 'kernel_kalloc']; +function kalloc(size: SizeUInt): void; [public, alias: 'kernel_kalloc']; var units_needed : uint32; hp : PHeapPageHeader; bmp : PuInt32; idx : sInt32; - alloc_base : uint32; - saved_flags : uint32; + alloc_base : PtrUInt; + saved_flags : PtrUInt; begin { Disable interrupts - save EFLAGS to local then cli. Uses pushfd;cli;pop (stack-balanced) instead of bare pushfd/popfd across the function, which is unsafe because FPC does not track stack pointer changes inside asm blocks. } - asm - pushfd - cli - pop saved_flags - end; + saved_flags := arch.platform.cpu.lock; kalloc := nil; - if size = 0 then begin - restore_if(saved_flags); + if (size = 0) or (size > High(SizeUInt) - PAGE_SIZE_LMM - SIZE_PREFIX) then begin + arch.platform.cpu.unlock(saved_flags); exit; end; - { Units needed: user size + 8-byte prefix, rounded up to ALLOC_UNIT } - units_needed := (size + SIZE_PREFIX + ALLOC_UNIT - 1) div ALLOC_UNIT; - {$IFDEF DEBUG_LMM} - inc(units_needed); { extra unit for guard sentinel } - {$ENDIF} - - { Large allocations that exceed a single heap page } - if units_needed > TOTAL_UNITS then begin + { Keep every allocation start 16-byte aligned, including odd sizes. } + if size > TOTAL_UNITS * ALLOC_UNIT - SIZE_PREFIX - 16 then begin kalloc := klalloc(size); if kalloc = nil then begin - restore_if(saved_flags); - core.panic.panic('OOM', 'kalloc: large allocation failed', nil); + arch.platform.cpu.unlock(saved_flags); + heap_oom('kalloc: large allocation failed'); exit; end; - zero_mem(Pointer(kalloc), size); - restore_if(saved_flags); + arch.platform.cpu.zero(Pointer(kalloc), size); + arch.platform.cpu.unlock(saved_flags); exit; end; + units_needed := ((size + SIZE_PREFIX + {$IFDEF DEBUG_LMM}8 + {$ENDIF}15) div 16) * 2; + hp := Search_Page; while true do begin { Only try pages with enough free units } @@ -467,7 +312,7 @@ begin kalloc := void(alloc_base + SIZE_PREFIX); { Zero the user-visible area } - zero_mem(Pointer(kalloc), size); + arch.platform.cpu.zero(Pointer(kalloc), size); {$IFDEF DEBUG_LMM} { Write guard sentinel at end of allocated region } @@ -475,7 +320,7 @@ begin {$ENDIF} Search_Page := hp; - restore_if(saved_flags); + arch.platform.cpu.unlock(saved_flags); exit; end; end; @@ -484,8 +329,8 @@ begin if hp^.NextPage = nil then begin hp := new_heap_page(hp); if hp = nil then begin - restore_if(saved_flags); - core.panic.panic('OOM', 'kalloc: out of memory', nil); + arch.platform.cpu.unlock(saved_flags); + heap_oom('kalloc: out of memory'); exit; end; end else begin @@ -494,7 +339,7 @@ begin Search_Page := hp; end; - restore_if(saved_flags); + arch.platform.cpu.unlock(saved_flags); end; procedure kfree(area: void); [public, alias: 'kernel_kfree']; @@ -502,63 +347,60 @@ var hp : PHeapPageHeader; bmp : PuInt32; prefix_val : uint32; - alloc_base : uint32; + alloc_base : PtrUInt; unit_idx : uint32; - base : uint32; - saved_flags : uint32; + base : PtrUInt; + saved_flags : PtrUInt; begin if area = nil then exit; { Disable interrupts - stack-balanced save to local variable } - asm - pushfd - cli - pop saved_flags - end; + saved_flags := arch.platform.cpu.lock; { Check for large allocation: magic sentinel at area - 12 (klalloc header is [page_count:4][MAGIC:4][pad:8][user_data]) } - if PuInt32(uint32(area) - 12)^ = LARGE_ALLOC_MAGIC then begin - base := uint32(area) - 16; + if PuInt32(PtrUInt(area) - 12)^ = LARGE_ALLOC_MAGIC then begin + base := PtrUInt(area) - 16; klfree(base); - restore_if(saved_flags); + arch.platform.cpu.unlock(saved_flags); exit; end; { Read the size prefix (SIZE_PREFIX bytes before the returned pointer) } - prefix_val := PuInt32(uint32(area) - SIZE_PREFIX)^; + prefix_val := PuInt32(PtrUInt(area) - SIZE_PREFIX)^; { Normal heap allocation } - hp := PHeapPageHeader((uint32(area) shr 22) shl 22); + hp := PHeapPageHeader((PtrUInt(area) shr 22) shl 22); bmp := get_bitmap(hp); { Compute allocation base and unit index } - alloc_base := uint32(area) - SIZE_PREFIX; + alloc_base := PtrUInt(area) - SIZE_PREFIX; unit_idx := (alloc_base - get_data_base(hp)) div ALLOC_UNIT; { Bounds check } - if (prefix_val = 0) or (unit_idx + prefix_val > hp^.TotalUnits) then begin - io.syslog.logln('LMM', 'ERROR: kfree invalid range.'); + if (prefix_val = 0) or (unit_idx >= hp^.TotalUnits) or + (prefix_val > hp^.TotalUnits - unit_idx) then begin + heap_log('ERROR: kfree invalid range.'); GPF; - restore_if(saved_flags); + arch.platform.cpu.unlock(saved_flags); exit; end; {$IFDEF DEBUG_LMM} { Guard byte overrun detection } if PuInt32(alloc_base + prefix_val * ALLOC_UNIT - 4)^ <> GUARD_MAGIC then begin - io.syslog.logln('LMM', 'ERROR: kfree guard corrupted - buffer overrun!'); + heap_log('ERROR: kfree guard corrupted - buffer overrun!'); GPF; - restore_if(saved_flags); + arch.platform.cpu.unlock(saved_flags); exit; end; {$ENDIF} { Double-free detection } if not bitmap_all_set(bmp, unit_idx, prefix_val) then begin - io.syslog.logln('LMM', 'ERROR: kfree double free!'); + heap_log('ERROR: kfree double free!'); GPF; - restore_if(saved_flags); + arch.platform.cpu.unlock(saved_flags); exit; end; @@ -573,12 +415,11 @@ begin { If page is completely free, return it to VMM/PMM } try_release_page(hp); - restore_if(saved_flags); + arch.platform.cpu.unlock(saved_flags); end; { ==== Statistics ==== } - -function lmm_total_free: uint32; +function lmm_total_free: SizeUInt; var hp: PHeapPageHeader; begin @@ -602,7 +443,57 @@ begin end; end; -initialization - boot.mgr.registerBoot('memory.heap', @init, 'Heap Memory Manager', 'arch.*.memory.*'); +procedure UnitTest; +var + a, b, large: void; + beforeFree: SizeUInt; + beforeFrames: uint64; + passed, failed: uint32; -end. \ No newline at end of file + procedure Assert(condition: boolean; name: pchar); + begin + if condition then begin + inc(passed); + heap_log(name); + end + else begin + inc(failed); + heap_log('FAIL: heap test'); + heap_log(name); + end; + end; +begin + passed := 0; + failed := 0; + beforeFree := lmm_total_free; + beforeFrames := arch.platform.memory.freePhysicalBytes; + a := kalloc(1); + b := kalloc(17); + Assert((a <> nil) and (b <> nil) and ((PtrUInt(a) and 15) = 0) and + ((PtrUInt(b) and 15) = 0), 'PASS: heap odd sizes remain 16-byte aligned'); + Assert((PUInt8(a)^ = 0) and (PUInt8(PtrUInt(b) + 16)^ = 0), 'PASS: heap allocation zeroed'); + PUInt8(a)^ := 123; + PUInt8(PtrUInt(b) + 16)^ := 45; + kfree(a); + kfree(b); + kfree(nil); + a := kalloc(1); + Assert(PUInt8(a)^ = 0, 'PASS: heap reused storage zeroed'); + kfree(a); + large := kalloc(PAGE_SIZE_LMM + 17); + Assert((large <> nil) and ((PtrUInt(large) and 15) = 0), 'PASS: heap large allocation aligned'); + PUInt8(large)^ := 87; + PUInt8(PtrUInt(large) + PAGE_SIZE_LMM + 16)^ := 91; + Assert((PUInt8(large)^ = 87) and (PUInt8(PtrUInt(large) + PAGE_SIZE_LMM + 16)^ = 91), + 'PASS: heap large allocation first/last bytes writable'); + kfree(large); + Assert((kalloc(0) = nil) and (kalloc(High(SizeUInt)) = nil), 'PASS: heap rejects zero/overflow sizes'); + Assert((lmm_total_free = beforeFree) and (lmm_page_count = 1), 'PASS: heap free accounting restored'); + Assert(arch.platform.memory.freePhysicalBytes = beforeFrames, 'PASS: heap backing frames restored'); + arch.platform.diagnostics.testSummary('LMM', 'HEAP_TESTS_PASSED', 'HEAP_TESTS_FAILED', passed, failed); + if failed <> 0 then GPF; +end; + + + +end. diff --git a/src/memory/memory.raw.pas b/src/memory/memory.raw.pas new file mode 100644 index 00000000..51e45c6f --- /dev/null +++ b/src/memory/memory.raw.pas @@ -0,0 +1,68 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Allocation-free scalar memory operations with native addresses. } +unit memory.raw; + +interface +procedure fill(dest: pointer; value: uint8; bytes: SizeUInt); +procedure move(source, dest: pointer; bytes: SizeUInt); + +implementation +type PNative = ^PtrUInt; + +procedure fill(dest: pointer; value: uint8; bytes: SizeUInt); +var + address, pattern: PtrUInt; +begin + address := PtrUInt(dest); + pattern := (High(PtrUInt) div 255) * value; + while bytes >= sizeof(PtrUInt) do begin + PNative(address)^ := pattern; + inc(address, sizeof(PtrUInt)); + dec(bytes, sizeof(PtrUInt)); + end; + while bytes <> 0 do begin + PUInt8(address)^ := value; + inc(address); + dec(bytes); + end; +end; + +procedure move(source, dest: pointer; bytes: SizeUInt); +var + src, dst: PtrUInt; +begin + src := PtrUInt(source); + dst := PtrUInt(dest); + if (src = dst) or (bytes = 0) then exit; + if (dst > src) and (dst - src < bytes) then begin + while bytes <> 0 do begin + dec(bytes); + PUInt8(dst + bytes)^ := PUInt8(src + bytes)^; + end; + end else begin + while bytes >= sizeof(PtrUInt) do begin + PNative(dst)^ := PNative(src)^; + inc(dst, sizeof(PtrUInt)); + inc(src, sizeof(PtrUInt)); + dec(bytes, sizeof(PtrUInt)); + end; + while bytes <> 0 do begin + PUInt8(dst)^ := PUInt8(src)^; + inc(dst); + inc(src); + dec(bytes); + end; + end; +end; +end. diff --git a/src/proc/proc.mgr.pas b/src/proc/proc.mgr.pas index ad933bd0..b3cb6368 100644 --- a/src/proc/proc.mgr.pas +++ b/src/proc/proc.mgr.pas @@ -1,4 +1,5 @@ // Copyright 2026 Kieron Morris +// Copyright 2026 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,18 +28,10 @@ unit proc.mgr; interface uses - boot.mgr, - arch.x86.bda, - driver.storage.fdtable, - core.ds.lists, - memory.heap, - proc.types, - core.rand, - io.stdio, - core.strings, - io.syslog, - debug.tracer, - core.util, arch.x86.util; + core.ds.dynamic, memory.heap, proc.types, proc.scheduler, + io.types, driver.storage.fdtable, arch.platform.cpu, arch.common.legacyirq, + arch.platform.process, arch.platform.diagnostics, + io.stdio, core.strings; { Core API } function create(name : pchar; @@ -46,6 +39,9 @@ function create(name : pchar; localData : void; priority : uint8) : PProcessContext; +function createSuspended(name: pchar; entry: TProcessEntryPoint; + localData: void; + priority: uint8): PProcessContext; function findByID(pid : uint32) : PProcessContext; function findByName(name : pchar) : PProcessContext; procedure sendMessage(pid : uint32; msg : TProcessSysMsg; data : void); @@ -102,8 +98,86 @@ var implementation -{ ---- Internal helpers ---- } +type PProcessSlot = ^PtrUInt; + PProcessState = ^TProcessState; +procedure log(message: pchar); +begin + arch.platform.diagnostics.log('PROCMGR', message); +end; + +procedure releaseFileTable(ctx: PProcessContext); +begin + if ctx^.FDTable <> nil then fd_table_free(PFDTable(ctx^.FDTable)); + ctx^.FDTable := nil; +end; + +function rootDirectory: pchar; +begin + rootDirectory := pchar(kalloc(2)); + if rootDirectory <> nil then begin + rootDirectory[0] := '/'; + rootDirectory[1] := #0; + end; +end; + +function nameEquals(left, right: pchar): boolean; +var + i: uint32; +begin + nameEquals := false; + if (left = nil) or (right = nil) then exit; + i := 0; + while i < 32 do begin + if left[i] <> right[i] then exit; + if left[i] = #0 then begin + nameEquals := true; + exit; + end; + inc(i); + end; +end; +var + nextProcessID: uint32; + reaperProcess: PProcessContext; + reapPending: boolean; + +procedure requestReap; +begin + { Called with IRQs excluded. Coalesces completed tasks into one worker wake. } + reapPending := true; + if (reaperProcess <> nil) and (reaperProcess^.State = psAwaiting) then + reaperProcess^.State := psReady; +end; + +procedure reaperEntry(ctx: PProcessContext); +var + flags: PtrUInt; +begin + while true do begin + flags := arch.platform.cpu.lock; + reapPending := false; + arch.platform.cpu.unlock(flags); + reapFinished; + flags := arch.platform.cpu.lock; + if not reapPending then ctx^.State := psAwaiting; + arch.platform.cpu.unlock(flags); + proc_await; + end; +end; + +function newProcessID: uint32; +var + flags: PtrUInt; +begin + flags := arch.platform.cpu.lock; + { IDs are never recycled; reject exhaustion rather than aliasing live IDs. } + newProcessID := nextProcessID; + if nextProcessID <> 0 then inc(nextProcessID); + arch.platform.cpu.unlock(flags); +end; + +{ ---- Internal helpers ---- } procedure copyName(dest : pchar; src : pchar; maxLen : uint32); var i : uint32; @@ -120,34 +194,42 @@ end; procedure process_trampoline; forward; { ---- Process creation ---- } - function createInternal(name : pchar; entry : TProcessEntryPoint; localData : void; - priority : uint8; runnable : boolean) : PProcessContext; + priority : uint8; + runnable : boolean) : PProcessContext; var ctx : PProcessContext; - slotPtr : ^uint32; + slotPtr : PProcessSlot; stack : void; - sp : uint32; prio : uint8; + savedFlags: PtrUInt; begin push_trace('proc.mgr.create'); createInternal := nil; + if (entry = nil) or (name = nil) or (Processes = nil) then begin + pop_trace; + exit; + end; if priority = 0 then prio := 1 else prio := priority; { Allocate process context } ctx := PProcessContext(kalloc(SizeOf(TProcessContext))); if ctx = nil then begin - io.syslog.logln('PROCMGR', 'Failed to allocate process context.'); + log('Failed to allocate process context.'); pop_trace; exit; end; - memset(uint32(ctx), 0, SizeOf(TProcessContext)); { Identity } - ctx^.ProcessID := rand32; + ctx^.ProcessID := newProcessID; + if ctx^.ProcessID = 0 then begin + kfree(void(ctx)); + pop_trace; + exit; + end; copyName(@ctx^.Name[0], name, 32); ctx^.ParentID := 0; if CurrentProcess <> nil then @@ -175,7 +257,7 @@ begin { Resources } ctx^.Resources := void(DL_New(SizeOf(TResourceBinding))); if ctx^.Resources = nil then begin - io.syslog.logln('PROCMGR', 'Failed to allocate resource list.'); + log('Failed to allocate resource list.'); kfree(void(ctx)); pop_trace; exit; @@ -184,7 +266,7 @@ begin { Per-process file descriptor table } ctx^.FDTable := void(fd_table_new); if ctx^.FDTable = nil then begin - io.syslog.logln('PROCMGR', 'Failed to allocate FD table.'); + log('Failed to allocate FD table.'); DL_Free(PDList(ctx^.Resources)); kfree(void(ctx)); pop_trace; @@ -192,10 +274,10 @@ begin end; { Per-process working directory } - ctx^.Cwd := stringCopy('/'); + ctx^.Cwd := rootDirectory; if ctx^.Cwd = nil then begin - io.syslog.logln('PROCMGR', 'Failed to allocate working directory.'); - fd_table_free(PFDTable(ctx^.FDTable)); + log('Failed to allocate working directory.'); + releaseFileTable(ctx); DL_Free(PDList(ctx^.Resources)); kfree(void(ctx)); pop_trace; @@ -205,89 +287,46 @@ begin { Allocate per-process core.version stack } stack := kalloc(PROCESS_STACK_SIZE); if stack = nil then begin - io.syslog.logln('PROCMGR', 'Failed to allocate process stack.'); + log('Failed to allocate process stack.'); kfree(void(ctx^.Cwd)); - fd_table_free(PFDTable(ctx^.FDTable)); + releaseFileTable(ctx); DL_Free(PDList(ctx^.Resources)); kfree(void(ctx)); pop_trace; exit; end; ctx^.StackBase := stack; - ctx^.StackTop := uint32(stack) + PROCESS_STACK_SIZE; + ctx^.StackTop := PtrUInt(stack) + PROCESS_STACK_SIZE; - { Build fake interrupt frame on the stack so the first context switch - IRET's into process_trampoline. - - Stack layout (growing downward): - [StackTop - 4] EFLAGS ($0202 = IF set) - [StackTop - 8] CS ($08 = core.version code) - [StackTop - 12] EIP (-> process_trampoline) - [StackTop - 16] EAX (0) - [StackTop - 20] ECX (0) - [StackTop - 24] EDX (0) - [StackTop - 28] EBX (0) - [StackTop - 32] ESP (ignored by POPAD) - [StackTop - 36] EBP (0) - [StackTop - 40] ESI (0) - [StackTop - 44] EDI (0) - [StackTop - 48] DS ($10 = core.version data, padded to 32-bit) - [StackTop - 52] ES ($10) - [StackTop - 56] FS ($10) - [StackTop - 60] GS ($10) - SavedESP = StackTop - 60 - } - sp := ctx^.StackTop; - - { IRET frame } - sp := sp - 4; PuInt32(sp)^ := $0202; { EFLAGS: IF=1 } - sp := sp - 4; PuInt32(sp)^ := $08; { CS } - sp := sp - 4; PuInt32(sp)^ := uint32(@process_trampoline); { EIP } - - { POPAD frame (8 registers) } - sp := sp - 4; PuInt32(sp)^ := 0; { EAX } - sp := sp - 4; PuInt32(sp)^ := 0; { ECX } - sp := sp - 4; PuInt32(sp)^ := 0; { EDX } - sp := sp - 4; PuInt32(sp)^ := 0; { EBX } - sp := sp - 4; PuInt32(sp)^ := 0; { ESP (ignored by POPAD) } - sp := sp - 4; PuInt32(sp)^ := 0; { EBP } - sp := sp - 4; PuInt32(sp)^ := 0; { ESI } - sp := sp - 4; PuInt32(sp)^ := 0; { EDI } - - { Segment registers (pushed as 32-bit values) } - sp := sp - 4; PuInt32(sp)^ := $10; { DS } - sp := sp - 4; PuInt32(sp)^ := $10; { ES } - sp := sp - 4; PuInt32(sp)^ := $10; { FS } - sp := sp - 4; PuInt32(sp)^ := $10; { GS } - - ctx^.SavedESP := sp; + ctx^.SavedStack := arch.platform.process.prepare(pointer(stack), PROCESS_STACK_SIZE, + @process_trampoline); { Add to process table } - asm pushf; cli end; - slotPtr := DL_Add(Processes); + savedFlags := arch.platform.cpu.lock; + slotPtr := PProcessSlot(DL_Add(Processes)); if slotPtr = nil then begin - asm popf end; - io.syslog.logln('PROCMGR', 'Failed to allocate process table slot.'); + arch.platform.cpu.unlock(savedFlags); + log('Failed to allocate process table slot.'); if ctx^.StackBase <> nil then kfree(ctx^.StackBase); if ctx^.Cwd <> nil then kfree(void(ctx^.Cwd)); - if ctx^.FDTable <> nil then - fd_table_free(PFDTable(ctx^.FDTable)); + if ctx^.FDTable <> nil then begin + releaseFileTable(ctx); + end; if ctx^.Resources <> nil then DL_Free(PDList(ctx^.Resources)); kfree(void(ctx)); pop_trace; exit; end; - slotPtr^ := uint32(ctx); + slotPtr^ := PtrUInt(ctx); { Mark as ready for scheduling } if runnable then ctx^.State := psReady; - asm popf end; + arch.platform.cpu.unlock(savedFlags); - io.syslog.log('PROCMGR', 'Created process: '); - io.syslog.writestringln(name); + log(name); pop_trace; createInternal := ctx; @@ -296,11 +335,19 @@ end; { Public creation retains its ready-on-return behavior. Commands need their arguments and streams installed before the scheduler may enter them. } function create(name : pchar; entry : TProcessEntryPoint; - localData : void; priority : uint8) : PProcessContext; + localData : void; + priority : uint8) : PProcessContext; begin create := createInternal(name, entry, localData, priority, true); end; +function createSuspended(name: pchar; entry: TProcessEntryPoint; + localData: void; + priority: uint8): PProcessContext; +begin + createSuspended := createInternal(name, entry, localData, priority, false); +end; + { ---- Process trampoline ---- } { This is the EIP that IRET jumps to on the first schedule of a process. It calls the process entry point, and when it returns, marks the process @@ -315,24 +362,21 @@ begin end; { Wait for the scheduler to reap us } while true do begin - asm - hlt - end; + arch.platform.cpu.idle; end; end; { ---- Find ---- } - function findByID(pid : uint32) : PProcessContext; var i : uint32; - slotPtr : ^uint32; + slotPtr : PProcessSlot; ctx : PProcessContext; begin findByID := nil; - if Processes = nil then exit; + if DL_Size(Processes) = 0 then exit; for i := 0 to DL_Size(Processes) - 1 do begin - slotPtr := DL_Get(Processes, i); + slotPtr := PProcessSlot(DL_Get(Processes, i)); if slotPtr <> nil then begin ctx := PProcessContext(slotPtr^); if ctx^.ProcessID = pid then begin @@ -346,16 +390,16 @@ end; function findByName(name : pchar) : PProcessContext; var i : uint32; - slotPtr : ^uint32; + slotPtr : PProcessSlot; ctx : PProcessContext; begin findByName := nil; - if Processes = nil then exit; + if DL_Size(Processes) = 0 then exit; for i := 0 to DL_Size(Processes) - 1 do begin - slotPtr := DL_Get(Processes, i); + slotPtr := PProcessSlot(DL_Get(Processes, i)); if slotPtr <> nil then begin ctx := PProcessContext(slotPtr^); - if stringEquals(@ctx^.Name[0], name) then begin + if nameEquals(@ctx^.Name[0], name) then begin findByName := ctx; exit; end; @@ -364,75 +408,76 @@ begin end; { ---- Messaging ---- } - -procedure sendMessage(pid : uint32; msg : TProcessSysMsg; data : void); +procedure sendMessage(pid: uint32; msg: TProcessSysMsg; data: void); var - ctx : PProcessContext; + ctx: PProcessContext; + savedFlags: PtrUInt; begin + savedFlags := arch.platform.cpu.lock; ctx := findByID(pid); if ctx <> nil then begin - ctx^.PendingMsg := msg; ctx^.MsgData := data; - { If the process is awaiting, wake it up on any message } - if ctx^.State = psAwaiting then - ctx^.State := psReady; - { If suspended and receiving smResume, wake it } - if (ctx^.State = psSuspended) and (msg = smResume) then - ctx^.State := psReady; + ctx^.PendingMsg := msg; + if (ctx^.State = psAwaiting) or + ((ctx^.State = psSuspended) and (msg = smResume)) then ctx^.State := psReady; end; + arch.platform.cpu.unlock(savedFlags); end; procedure terminate(pid : uint32); begin if pid = 0 then exit; { Cannot terminate idle } + if (reaperProcess <> nil) and (pid = reaperProcess^.ProcessID) then exit; sendMessage(pid, smTerminate, nil); end; -procedure kill(pid : uint32); +procedure kill(pid: uint32); var - ctx : PProcessContext; + ctx: PProcessContext; + savedFlags: PtrUInt; begin - if pid = 0 then exit; { Cannot kill idle } + if pid = 0 then exit; + savedFlags := arch.platform.cpu.lock; ctx := findByID(pid); - if ctx <> nil then begin - asm pushf; cli end; + if (ctx <> nil) and (ctx <> reaperProcess) then begin ctx^.State := psFinished; - ctx^.ExitCode := $FFFFFFFF; - asm popf end; + ctx^.ExitCode := High(uint32); + requestReap; end; + arch.platform.cpu.unlock(savedFlags); end; { ---- Process self-management ---- } - procedure proc_yield; +var + savedFlags: PtrUInt; begin if CurrentProcess <> nil then begin - asm pushf; cli end; + savedFlags := arch.platform.cpu.lock; CurrentProcess^.TicksUsed := CurrentProcess^.Quantum; - asm popf end; + arch.platform.cpu.unlock(savedFlags); { Next timer tick will preempt us } - asm - hlt - end; + arch.platform.cpu.idle; end; end; -procedure proc_sleep_ms(ms : uint32); +procedure proc_sleep_ms(ms: uint32); var - ticks : uint32; - start, now, elapsed : uint32; + remaining: uint64; + previous, now, delta: uint32; begin - ticks := (ms * 1024) div 1000; - if ticks = 0 then ticks := 1; - start := Counters.c32; + remaining := (uint64(ms) * PIT_INPUT_HZ + uint64(PIT_DIVISOR) * 1000 - 1) div (PIT_DIVISOR * 1000); + if remaining = 0 then remaining := 1; + previous := arch.platform.process.timerTicks; repeat proc_yield; - now := Counters.c32; - if now >= start then - elapsed := now - start - else - elapsed := ($FFFFFFFF - start) + now + 1; - until elapsed >= ticks; + now := arch.platform.process.timerTicks; + if now >= previous then delta := now - previous + else delta := (High(uint32) - previous) + now + 1; + previous := now; + if remaining <= delta then exit; + remaining := remaining - delta; + until false; end; procedure proc_await; @@ -441,20 +486,20 @@ begin This procedure just spin-waits until an ISR sets the state back to something other than psAwaiting. } if CurrentProcess <> nil then begin - while CurrentProcess^.State = psAwaiting do - asm hlt end; + while PProcessState(@CurrentProcess^.State)^ = psAwaiting do + arch.platform.cpu.idle; end; end; procedure proc_suspend; +var + savedFlags: PtrUInt; begin if CurrentProcess <> nil then begin - asm pushf; cli end; + savedFlags := arch.platform.cpu.lock; CurrentProcess^.State := psSuspended; - asm popf end; - asm - hlt - end; + arch.platform.cpu.unlock(savedFlags); + arch.platform.cpu.idle; end; end; @@ -464,65 +509,72 @@ begin CurrentProcess^.ExitCode := exitCode; CurrentProcess^.State := psFinished; while true do begin - asm - hlt - end; + arch.platform.cpu.idle; end; end; end; { ---- Resource binding ---- } - -procedure bindResource(ctx : PProcessContext; kind : TResourceKind; handle : void; cleanup : TResourceCleanup); +procedure bindResource(ctx: PProcessContext; kind: TResourceKind; handle: void; cleanup: TResourceCleanup); var - resList : PDList; - binding : PResourceBinding; + binding: PResourceBinding; + savedFlags: PtrUInt; begin if ctx = nil then exit; - resList := PDList(ctx^.Resources); - if resList = nil then exit; - binding := PResourceBinding(DL_Add(resList)); - binding^.Kind := kind; - binding^.Handle := handle; - binding^.Cleanup := cleanup; -end; - -procedure unbindResource(ctx : PProcessContext; handle : void); -var - resList : PDList; - binding : PResourceBinding; - i : uint32; -begin - if ctx = nil then exit; - resList := PDList(ctx^.Resources); - if resList = nil then exit; - for i := 0 to DL_Size(resList) - 1 do begin - binding := PResourceBinding(DL_Get(resList, i)); - if binding^.Handle = handle then begin - if binding^.Cleanup <> nil then - binding^.Cleanup(binding^.Handle); - DL_Delete(resList, i); - exit; + savedFlags := arch.platform.cpu.lock; + if ctx^.Resources <> nil then begin + binding := PResourceBinding(DL_Add(PDList(ctx^.Resources))); + if binding <> nil then begin + binding^.Kind := kind; + binding^.Handle := handle; + binding^.Cleanup := cleanup; end; end; + arch.platform.cpu.unlock(savedFlags); end; -procedure unbindResourceNoCleanup(ctx : PProcessContext; handle : void); +procedure unbindResource(ctx: PProcessContext; handle: void); var - resList : PDList; - binding : PResourceBinding; - i : uint32; + resList: PDList; + binding: PResourceBinding; + i: uint32; + cleanup: TResourceCleanup; + savedFlags: PtrUInt; begin if ctx = nil then exit; + savedFlags := arch.platform.cpu.lock; resList := PDList(ctx^.Resources); - if resList = nil then exit; - for i := 0 to DL_Size(resList) - 1 do begin + cleanup := nil; + if DL_Size(resList) > 0 then for i := 0 to DL_Size(resList) - 1 do begin + binding := PResourceBinding(DL_Get(resList, i)); + if binding^.Handle = handle then begin + cleanup := binding^.Cleanup; + DL_Delete(resList, i); + break; + end; + end; + arch.platform.cpu.unlock(savedFlags); + if assigned(cleanup) then cleanup(handle); +end; + +procedure unbindResourceNoCleanup(ctx: PProcessContext; handle: void); +var + resList: PDList; + binding: PResourceBinding; + i: uint32; + savedFlags: PtrUInt; +begin + if ctx = nil then exit; + savedFlags := arch.platform.cpu.lock; + resList := PDList(ctx^.Resources); + if DL_Size(resList) > 0 then for i := 0 to DL_Size(resList) - 1 do begin binding := PResourceBinding(DL_Get(resList, i)); if binding^.Handle = handle then begin DL_Delete(resList, i); - exit; + break; end; end; + arch.platform.cpu.unlock(savedFlags); end; procedure unbindAllResources(ctx : PProcessContext); @@ -530,12 +582,18 @@ var resList : PDList; binding : PResourceBinding; i : sint32; + savedFlags: PtrUInt; begin if ctx = nil then exit; + savedFlags := arch.platform.cpu.lock; resList := PDList(ctx^.Resources); - if resList = nil then exit; + if resList = nil then begin + arch.platform.cpu.unlock(savedFlags); + exit; + end; { Detach early so cleanup callbacks that re-enter find nothing } ctx^.Resources := nil; + arch.platform.cpu.unlock(savedFlags); for i := sint32(DL_Size(resList)) - 1 downto 0 do begin binding := PResourceBinding(DL_Get(resList, uint32(i))); if binding <> nil then begin @@ -564,6 +622,7 @@ function runCommand(name : pchar; method : TCommandMethod; params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf) : PProcessContext; var ctx : PProcessContext; + savedFlags: PtrUInt; begin ctx := createInternal(name, @legacy_entry, void(method), 1, false); if ctx <> nil then begin @@ -573,15 +632,14 @@ begin ctx^.StdErr := stderr_buf; { Publish only after all fields read by legacy_entry are initialized. psCreated is also runnable, so creation must use psSuspended. } - asm pushf; cli end; + savedFlags := arch.platform.cpu.lock; ctx^.State := psReady; - asm popf end; + arch.platform.cpu.unlock(savedFlags); end; runCommand := ctx; end; { ---- Query ---- } - function processCount : uint32; begin if Processes = nil then @@ -592,115 +650,56 @@ end; function getProcessByIndex(idx : uint32) : PProcessContext; var - slotPtr : ^uint32; + slotPtr : PProcessSlot; begin getProcessByIndex := nil; if Processes = nil then exit; if idx >= DL_Size(Processes) then exit; - slotPtr := DL_Get(Processes, idx); + slotPtr := PProcessSlot(DL_Get(Processes, idx)); if slotPtr <> nil then getProcessByIndex := PProcessContext(slotPtr^); end; { ---- Reaping ---- } - procedure reapFinished; var - i : sint32; - slotPtr : ^uint32; - ctx : PProcessContext; - parent : PProcessContext; + i: sint32; + ctx: PProcessContext; + savedFlags: PtrUInt; begin - if Processes = nil then exit; - { Iterate in reverse so index shifting from DL_Delete is safe } - for i := sint32(DL_Size(Processes)) - 1 downto 0 do begin - slotPtr := DL_Get(Processes, uint32(i)); - if slotPtr = nil then continue; - ctx := PProcessContext(slotPtr^); - if (ctx^.State = psFinished) or (ctx^.State = psError) then begin - { Don't reap the currently running process } - if ctx = CurrentProcess then continue; - { Never reap the idle process } - if ctx^.ProcessID = 0 then continue; - { Notify parent (if it exists) that a child has exited } - if ctx^.ParentID <> 0 then begin - parent := findByID(ctx^.ParentID); - if parent <> nil then - sendMessage(parent^.ProcessID, smChildExited, void(ctx^.ProcessID)); + { The reaper calls this in process context, never from the timer ISR. + Detach under IRQ exclusion before callbacks can re-enter the registry. } + i := sint32(DL_Size(Processes)) - 1; + while i >= 0 do begin + savedFlags := arch.platform.cpu.lock; + ctx := getProcessByIndex(uint32(i)); + if (ctx <> nil) and (ctx <> CurrentProcess) and (ctx^.ProcessID <> 0) and + ((ctx^.State = psFinished) or (ctx^.State = psError)) then + if not fd_table_begin_close(PFDTable(ctx^.FDTable)) then begin + ctx := nil; { final FD completion wakes the reaper } end; - { Clean up file descriptors } - if ctx^.FDTable <> nil then begin - fd_table_free(PFDTable(ctx^.FDTable)); - ctx^.FDTable := nil; - end; - { Free per-process CWD } - if ctx^.Cwd <> nil then begin - kfree(void(ctx^.Cwd)); - ctx^.Cwd := nil; - end; - { Clean up resources } - unbindAllResources(ctx); - { Free stack } - if ctx^.StackBase <> nil then - kfree(ctx^.StackBase); - { Remove from table } + if (ctx <> nil) and (ctx <> CurrentProcess) and (ctx^.ProcessID <> 0) and + ((ctx^.State = psFinished) or (ctx^.State = psError)) then begin DL_Delete(Processes, uint32(i)); - { Free context } + arch.platform.cpu.unlock(savedFlags); + releaseFileTable(ctx); + if ctx^.Cwd <> nil then kfree(void(ctx^.Cwd)); + unbindAllResources(ctx); + if ctx^.StackBase <> nil then kfree(ctx^.StackBase); + if ctx^.ParentID <> 0 then + sendMessage(ctx^.ParentID, smChildExited, void(PtrUInt(ctx^.ProcessID))); kfree(void(ctx)); - end; + end else arch.platform.cpu.unlock(savedFlags); + dec(i); end; end; { ---- Scheduler ---- } - function scheduler_pick_next : PProcessContext; -var - count : uint32; - i : uint32; - idx : uint32; - slotPtr : ^uint32; - ctx : PProcessContext; begin - { Reap dead processes first } - reapFinished; - - count := DL_Size(Processes); - - { Check if current process still has quantum left } - if CurrentProcess^.State = psRunning then begin - CurrentProcess^.TicksUsed := CurrentProcess^.TicksUsed + 1; - if CurrentProcess^.TicksUsed < CurrentProcess^.Quantum then begin - scheduler_pick_next := CurrentProcess; - exit; - end; - { Quantum expired, move to ready } - CurrentProcess^.State := psReady; - CurrentProcess^.TicksUsed := 0; - end; - - { Round-robin: scan from SchedulerIndex, skipping idle } - if SchedulerIndex >= count then - SchedulerIndex := 0; - - for i := 0 to count - 1 do begin - idx := (SchedulerIndex + i) mod count; - slotPtr := DL_Get(Processes, idx); - if slotPtr = nil then continue; - ctx := PProcessContext(slotPtr^); - if ctx^.ProcessID = 0 then continue; { Skip idle in round-robin } - if (ctx^.State = psReady) or (ctx^.State = psCreated) then begin - ctx^.State := psRunning; - ctx^.TicksUsed := 0; - SchedulerIndex := (idx + 1) mod count; - scheduler_pick_next := ctx; - exit; - end; - end; - - { No runnable process found — fall back to idle } - IdleProcess^.State := psRunning; - IdleProcess^.TicksUsed := 0; - scheduler_pick_next := IdleProcess; + if (CurrentProcess^.State = psFinished) or (CurrentProcess^.State = psError) then requestReap; + scheduler_pick_next := proc.scheduler.pickNext(CurrentProcess, IdleProcess, + DL_Size(Processes), SchedulerIndex, @getProcessByIndex); end; { ---- Terminal commands ---- } @@ -720,7 +719,7 @@ end; procedure terminal_command_ps(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); var i : uint32; - slotPtr : ^uint32; + slotPtr : PProcessSlot; ctx : PProcessContext; stStr : pchar; begin @@ -729,9 +728,9 @@ begin bufWriteStrLn(stdout_buf, 'PID NAME STATE PRI'); bufWriteStrLn(stdout_buf, '---------- ------------------------- ----------- ---'); - if Processes <> nil then begin + if (Processes <> nil) and (DL_Size(Processes) > 0) then begin for i := 0 to DL_Size(Processes) - 1 do begin - slotPtr := DL_Get(Processes, i); + slotPtr := PProcessSlot(DL_Get(Processes, i)); if slotPtr = nil then continue; ctx := PProcessContext(slotPtr^); @@ -820,22 +819,23 @@ begin pop_trace; end; -{ ---- Init ---- } +{ ---- Init ---- } procedure init; var - slotPtr : ^uint32; + slotPtr : PProcessSlot; begin push_trace('proc.mgr.init'); - io.syslog.logln('PROCMGR', 'INIT BEGIN.'); + log('INIT BEGIN.'); + nextProcessID := 1; + fd_set_idle_hook(@requestReap); { Create process table } - Processes := DL_New(SizeOf(uint32)); + Processes := DL_New(SizeOf(PtrUInt)); SchedulerIndex := 0; { Create idle process — PID 0, unkillable, uses core.version main stack } IdleProcess := PProcessContext(kalloc(SizeOf(TProcessContext))); - memset(uint32(IdleProcess), 0, SizeOf(TProcessContext)); IdleProcess^.ProcessID := 0; copyName(@IdleProcess^.Name[0], 'Idle', 32); IdleProcess^.State := psRunning; @@ -844,19 +844,21 @@ begin IdleProcess^.PendingMsg := smNone; IdleProcess^.Resources := void(DL_New(SizeOf(TResourceBinding))); IdleProcess^.FDTable := void(fd_table_new); - IdleProcess^.Cwd := stringCopy('/'); + IdleProcess^.Cwd := rootDirectory; { No stack allocation — idle runs on the core.version's original stack } - slotPtr := DL_Add(Processes); - slotPtr^ := uint32(IdleProcess); + slotPtr := PProcessSlot(DL_Add(Processes)); + slotPtr^ := PtrUInt(IdleProcess); CurrentProcess := IdleProcess; + reapPending := false; + reaperProcess := createInternal('reaper', @reaperEntry, nil, 1, false); + if reaperProcess <> nil then reaperProcess^.State := psAwaiting; - io.syslog.logln('PROCMGR', 'Idle process created (PID 0).'); + log('Idle process created (PID 0).'); - io.syslog.logln('PROCMGR', 'INIT END.'); + log('INIT END.'); pop_trace; end; -initialization - boot.mgr.registerBoot('proc.mgr', @Init, 'Process Manager', 'driver.storage.fs*'); + end. diff --git a/src/proc/proc.scheduler.pas b/src/proc/proc.scheduler.pas new file mode 100644 index 00000000..7899069f --- /dev/null +++ b/src/proc/proc.scheduler.pas @@ -0,0 +1,75 @@ +// Copyright 2026 Aaron Hance +// Copyright 2026 Kieron Morris +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ Shared round-robin/quantum policy extracted from proc.mgr (Kieron Morris). + Caller excludes interrupts and owns process lifetime/reaping. } +unit proc.scheduler; + +interface +uses proc.types; +type TGetProcess = function(index: uint32): PProcessContext; +function pickNext(current, idle: PProcessContext; count: uint32; + var cursor: uint32; + getProcess: TGetProcess): PProcessContext; + +implementation + +function pickNext(current, idle: PProcessContext; count: uint32; + var cursor: uint32; + getProcess: TGetProcess): PProcessContext; +var + i : uint32; + idx : uint32; + ctx : PProcessContext; +begin + { Check if current process still has quantum left } + if (current <> nil) and (current^.State = psRunning) then begin + current^.TicksUsed := current^.TicksUsed + 1; + if current^.TicksUsed < current^.Quantum then begin + pickNext := current; + exit; + end; + { Quantum expired, move to ready } + current^.State := psReady; + current^.TicksUsed := 0; + end; + + { Round-robin: scan from cursor, skipping idle } + if cursor >= count then + cursor := 0; + + if count > 0 then for i := 0 to count - 1 do begin + idx := (cursor + i) mod count; + ctx := getProcess(idx); + if ctx = nil then continue; + if ctx^.ProcessID = 0 then continue; { Skip idle in round-robin } + if (ctx^.State = psReady) or (ctx^.State = psCreated) then begin + ctx^.State := psRunning; + ctx^.TicksUsed := 0; + cursor := (idx + 1) mod count; + pickNext := ctx; + exit; + end; + end; + + { No runnable process found — fall back to idle } + if idle = nil then begin + pickNext := nil; + exit; + end; + idle^.State := psRunning; + idle^.TicksUsed := 0; + pickNext := idle; +end; + +end. diff --git a/src/proc/proc.types.pas b/src/proc/proc.types.pas index dc4848ea..0691a825 100644 --- a/src/proc/proc.types.pas +++ b/src/proc/proc.types.pas @@ -1,3 +1,15 @@ +// Copyright 2026 Aaron Hance +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + { ProcTypes - Type definitions for the preemptive process management system. @@ -12,7 +24,7 @@ unit proc.types; interface uses - io.stdio; + io.types; const { 8 KB per-process core.version stack } @@ -90,9 +102,9 @@ type EntryPoint : TProcessEntryPoint; { Context switch state } - SavedESP : uint32; + SavedStack : PtrUInt; StackBase : void; - StackTop : uint32; + StackTop : PtrUInt; { Scheduling } Priority : uint8; diff --git a/src/svc/svc.gfxd.pas b/src/svc/svc.gfxd.pas index ca7faa1e..f12f832a 100644 --- a/src/svc/svc.gfxd.pas +++ b/src/svc/svc.gfxd.pas @@ -22,7 +22,6 @@ procedure init; implementation uses - boot.mgr, driver.video.desktop, app.uidebug, driver.video.lvgl, @@ -31,7 +30,7 @@ uses proc.mgr, proc.types, io.syslog, - arch.x86.isr.tmr0; + arch.platform.interrupts; { Render process entry point — runs as a normal scheduled process with interrupts enabled. Continuously drives the full render pipeline so @@ -60,8 +59,8 @@ begin driver.video.desktop.update; app.uidebug.update; lvgl_handler; - lvgl_unlock; driver.video.Flush; + lvgl_unlock; end; if SHOULD_CRASH then begin asm @@ -72,7 +71,7 @@ begin end; end; -procedure tick; +procedure tick(data: void); begin { This function is called on every timer tick (interrupt 32) via the ISR hooks. It can be used to trigger periodic tasks without needing a dedicated process. } @@ -81,12 +80,9 @@ end; procedure init; begin - arch.x86.isr.tmr0.hook(uint32(@tick)); { Register the tick handler to run on every timer interrupt } + arch.platform.interrupts.registerTimer(@tick); { Register the tick handler to run on every timer interrupt } proc.mgr.create('gfxd', @render_loop, nil, 5); io.syslog.logln('gfxd', 'Render process spawned.'); end; -Initialization - boot.mgr.registerBoot('svc.gfxd', @init, 'GFX Daemon', BOOT_MGR_BARRIER_LATE); - end. diff --git a/src/test/test.pas b/src/test/test.pas index 3f239a19..f0e10898 100644 --- a/src/test/test.pas +++ b/src/test/test.pas @@ -29,6 +29,9 @@ implementation uses core.strings, + core.ds.lists, + core.ds.hashmap, + driver.storage.iorequest, driver.storage.path, driver.storage.fs.mgr, app.diskutil, driver.bus.usb.types, driver.bus.usb.core, @@ -69,6 +72,11 @@ procedure runAllTests(); begin { Run unit tests } core.strings.UnitTest; + core.ds.lists.TestAllLists; + core.ds.hashmap.UnitTest; + driver.storage.iorequest.UnitTest; + driver.storage.path.UnitTest; + driver.storage.fs.mgr.UnitTest; app.diskutil.UnitTest; driver.bus.usb.types.UnitTest; driver.bus.usb.core.UnitTest; diff --git a/toolchain/compile.sh b/toolchain/compile.sh index 485e29a6..a68949c0 100644 --- a/toolchain/compile.sh +++ b/toolchain/compile.sh @@ -1,56 +1,30 @@ #!/usr/bin/env bash -ERRCOUNT=0 -echo " " -echo "=======================" -echo " " -echo "Asuro Compilation" -echo " " - -#Compile Stub.asm -rm -f lib/* - -runOrFail() { - local binary=$1 - local errorText=$2 - if $binary; then - echo "Success." - else - echo "$errorText" - ERRCOUNT=$((ERRCOUNT+1)) - fi -} - +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e TOOLCHAIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$TOOLCHAIN_DIR/target.sh" +mkdir -p "$OBJ_DIR" "$BIN_DIR" +# Preserve generated objects belonging to other targets. +find "$OBJ_DIR" -maxdepth 1 -type f \( -name '*.o' -o -name '*.ppu' -o -name '*.a' \) -delete -declare -a run_steps=( - "compile_stub.sh" "Failed to compile stub!" - "compile_vergen.sh" "Versions failed to compile" - "compile_lvgl.sh" "Failed to compile LVGL!" - "compile_icons.sh" "Failed to generate icons!" - "compile_wasuro.sh" "Failed to pull Wasuro!" - "compile_sources.sh" "Failed to compile FPC Sources!" - "compile_initcalls.sh" "Failed to generate INITFINAL table!" - "compile_link.sh" "Failed linking!" - "compile_isogen.sh" "Failed to create ISO!" - "compile_docs.sh" "Failed to generate documentation!" -) - -for ((i=0; i<${#run_steps[@]}; i+=2)) -do - if [ "$ERRCOUNT" -eq "0" ] - then - script="${TOOLCHAIN_DIR}/${run_steps[$i]}" - message="${run_steps[$i+1]}" - runOrFail "$script" "$message" - fi +for step in "${BUILD_STEPS[@]}"; do + if bash "$TOOLCHAIN_DIR/compile_$step.sh"; then + echo "Success." + else + echo "Failed build step: $step" >&2 + if [ -n "$FINISH_SCRIPT" ]; then bash "$TOOLCHAIN_DIR/$FINISH_SCRIPT" failed; fi + exit 1 + fi done - -#Call generate final artifacts based on failure or success of the above. -if [ "$ERRCOUNT" -ne "0" ] -then - . "${TOOLCHAIN_DIR}/compile_finish.sh" "failed" -else - . "${TOOLCHAIN_DIR}/compile_finish.sh" "success" -fi - -cd .. \ No newline at end of file +if [ -n "$FINISH_SCRIPT" ]; then bash "$TOOLCHAIN_DIR/$FINISH_SCRIPT" success; fi +echo "No errors. Image: $ISO_FILE" diff --git a/toolchain/compile_icons.sh b/toolchain/compile_icons.sh index 41e571c0..45ff541e 100644 --- a/toolchain/compile_icons.sh +++ b/toolchain/compile_icons.sh @@ -3,10 +3,12 @@ # Runs gen_file_icons.py to produce asuro_icons_{32,64,128}.c in lvglh/, # then compiles each into a .o in lib/ for linking. set -e +# Target configuration updates: Copyright 2026 Aaron Hance. +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" TOOLCHAIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONF_DIR="$(pwd)/lvglh" -OUT_DIR="$(pwd)/lib" +OUT_DIR="$(pwd)/$OBJ_DIR" FONT_CACHE="$(pwd)/img" LVGL_DIR="/tmp/lvgl" CACHE_DIR="/code/lvgl" @@ -20,8 +22,8 @@ else LVGL_INC="${CACHE_DIR}" fi -CC="gcc" -CFLAGS="-m32 -march=i686 -ffreestanding \ +CC="$C_COMPILER" +CFLAGS="-ffreestanding \ -fno-builtin -fno-stack-protector -fno-pic -fno-pie \ -O2 -Wall -Wno-unused-function -Wno-unused-variable \ -I${CONF_DIR} \ @@ -44,7 +46,7 @@ for src in "${CONF_DIR}"/asuro_icons_*.c; do [ -f "$src" ] || continue base="$(basename "${src%.c}")" echo " Compiling ${base}.c ..." - if ! $CC $CFLAGS -c "$src" -o "${OUT_DIR}/${base}.o" 2>&1; then + if ! "$CC" "${C_TARGET_OPTIONS[@]}" $CFLAGS -c "$src" -o "${OUT_DIR}/${base}.o" 2>&1; then echo " FAILED: ${base}.c" ERRORS=$((ERRORS + 1)) fi diff --git a/toolchain/compile_initcalls.sh b/toolchain/compile_initcalls.sh index 58b4bdc3..f1e550f2 100644 --- a/toolchain/compile_initcalls.sh +++ b/toolchain/compile_initcalls.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Target configuration updates: Copyright 2026 Aaron Hance. +set -e +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" echo " " echo "=======================" echo " " @@ -18,11 +21,9 @@ echo " " INIT_SYMBOLS=() -for obj in lib/*.o; do - # Skip non-FPC objects that will never contain Pascal init sections - case "$(basename "$obj")" in - stub.o|splash_tga.o|initcalls.o) continue ;; - esac +for obj in "$OBJ_DIR/"*.o; do + [ -f "$obj" ] || continue + [ "$obj" = "$OBJ_DIR/initcalls.o" ] && continue # nm --defined-only: only symbols defined in this object (not extern refs) # grep ' T INIT\$_\$': global text symbols matching FPC's naming convention @@ -35,7 +36,7 @@ done COUNT=${#INIT_SYMBOLS[@]} echo "Found $COUNT unit initialization section(s)." -CFILE="/tmp/initcalls.c" +CFILE="$BIN_DIR/initcalls.c" # ---- Generate the C source file ---- @@ -99,6 +100,6 @@ cat "$CFILE" echo "--- End of generated C source ---" echo " " -# Compile to a 32-bit ELF object file -gcc -m32 -c -fno-pic -ffreestanding -o lib/initcalls.o "$CFILE" -echo "Compiled lib/initcalls.o" +# Compile for the selected target +"$C_COMPILER" "${C_TARGET_OPTIONS[@]}" -c -fno-pic -ffreestanding -o "$OBJ_DIR/initcalls.o" "$CFILE" +echo "Compiled $OBJ_DIR/initcalls.o" diff --git a/toolchain/compile_isogen.sh b/toolchain/compile_isogen.sh index 4af8522b..0729f2f7 100644 --- a/toolchain/compile_isogen.sh +++ b/toolchain/compile_isogen.sh @@ -1,8 +1,21 @@ #!/usr/bin/env bash -echo " " -echo "=======================" -echo " " +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" echo "Creating ISO..." -echo " " -cp bin/kernel.bin iso/boot/asuro.bin -grub-mkrescue -o Asuro.iso iso \ No newline at end of file +mkdir -p "$ISO_ROOT/boot/grub" +if [ "$(realpath "$GRUB_CONFIG")" != "$(realpath "$ISO_ROOT/boot/grub/grub.cfg")" ]; then + cp "$GRUB_CONFIG" "$ISO_ROOT/boot/grub/grub.cfg" +fi +cp "$BIN_DIR/kernel.bin" "$ISO_ROOT/boot/asuro.bin" +grub-mkrescue -o "$ISO_FILE" "$ISO_ROOT" diff --git a/toolchain/compile_link.sh b/toolchain/compile_link.sh index f72b2798..60803b18 100644 --- a/toolchain/compile_link.sh +++ b/toolchain/compile_link.sh @@ -1,22 +1,32 @@ #!/usr/bin/env bash -echo " " -echo "=======================" -echo " " +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" echo "Linking..." -echo " " -objstring=""; -for object in `find lib/ -name "*.o"`; do - if [ "$object" != "lib/stub.o" ] - then - objstring=$objstring$object" "; - fi -done; -objstring=lib/stub.o" "$objstring -echo "Object Files: "$objstring -echo " " - -# Find libgcc for i386 (needed by LVGL compiled with gcc) -LIBGCC=$(gcc -m32 -print-libgcc-file-name) -echo "libgcc: ${LIBGCC}" - -ld -m elf_i386 -s --gc-sections --no-warn-execstack -Ttoolchain/linker.script -o bin/kernel.bin $objstring --start-group lib/liblvgl.a ${LIBGCC} --end-group \ No newline at end of file +objects=() +boot_object="$OBJ_DIR/${ASM_ENTRY//\//_}" +boot_object="${boot_object%.asm}.o" +objects+=("$boot_object") +while IFS= read -r -d '' object; do + if [ "$object" != "$boot_object" ]; then objects+=("$object"); fi +done < <(find "$OBJ_DIR" -maxdepth 1 -type f -name '*.o' -print0 | LC_ALL=C sort -z) +libraries=() +while IFS= read -r -d '' library; do libraries+=("$library"); done \ + < <(find "$OBJ_DIR" -maxdepth 1 -type f -name '*.a' -print0 | LC_ALL=C sort -z) +if "$LINK_COMPILER_RUNTIME"; then + libraries+=("$("$C_COMPILER" "${C_TARGET_OPTIONS[@]}" -print-libgcc-file-name)") +fi +ld -m "$LD_FORMAT" "${LINK_OPTIONS[@]}" --gc-sections --no-warn-execstack \ + -T"$LINKER_SCRIPT" -o "$BIN_DIR/kernel.bin" "${objects[@]}" \ + --start-group "${libraries[@]}" --end-group +"${KERNEL_VALIDATOR[@]}" "$BIN_DIR/kernel.bin" diff --git a/toolchain/compile_lvgl.sh b/toolchain/compile_lvgl.sh index 4632fb60..51f17a2a 100644 --- a/toolchain/compile_lvgl.sh +++ b/toolchain/compile_lvgl.sh @@ -3,17 +3,19 @@ # Clone & compile in /tmp (fast container-local fs). # Cache liblvgl.a on host mount (/code/lvgl/) to skip rebuild. set -e +# Target configuration updates: Copyright 2026 Aaron Hance. +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" LVGL_VERSION="v9.2.2" LVGL_REPO="https://github.com/lvgl/lvgl.git" LVGL_DIR="/tmp/lvgl" -OBJ_DIR="/tmp/lvgl_obj" +LVGL_OBJ_DIR="/tmp/lvgl_obj_$ASURO_ARCH" CONF_DIR="$(pwd)/lvglh" -OUT_DIR="$(pwd)/lib" +OUT_DIR="$(pwd)/$OBJ_DIR" CACHE_DIR="/code/lvgl" -CC="gcc" -CFLAGS="-m32 -march=i686 -ffreestanding \ +CC="$C_COMPILER" +CFLAGS="-ffreestanding \ -fno-builtin -fno-stack-protector -fno-pic -fno-pie \ -O2 -Wall -Wno-unused-function -Wno-unused-variable \ -I${CONF_DIR} \ @@ -28,10 +30,10 @@ echo "Compiling LVGL..." echo " " # If cached liblvgl.a exists on host mount, just copy it and skip everything -if [ -f "${CACHE_DIR}/liblvgl.a" ]; then +if [ -f "${CACHE_DIR}/${LVGL_CACHE_NAME}" ]; then echo "Found cached liblvgl.a in ${CACHE_DIR}, copying to ${OUT_DIR}..." - cp "${CACHE_DIR}/liblvgl.a" "${OUT_DIR}/liblvgl.a" - echo "(Delete lvgl/liblvgl.a to force a full rebuild.)" + cp "${CACHE_DIR}/${LVGL_CACHE_NAME}" "${OUT_DIR}/liblvgl.a" + echo "(Delete ${CACHE_DIR}/${LVGL_CACHE_NAME} to force a full rebuild.)" echo " " echo "LVGL compilation complete (cached)." echo " " @@ -50,18 +52,18 @@ TOTAL=$(echo "$SOURCES" | wc -l) echo "Found ${TOTAL} LVGL source files." # Compile each .c file to .o (container-local) -rm -rf "$OBJ_DIR" -mkdir -p "$OBJ_DIR" +rm -rf "$LVGL_OBJ_DIR" +mkdir -p "$LVGL_OBJ_DIR" COUNT=0 ERRORS=0 for src in $SOURCES; do COUNT=$((COUNT + 1)) rel="${src#${LVGL_DIR}/src/}" - obj_path="${OBJ_DIR}/${rel%.c}.o" + obj_path="${LVGL_OBJ_DIR}/${rel%.c}.o" mkdir -p "$(dirname "$obj_path")" - if ! $CC $CFLAGS -c "$src" -o "$obj_path" 2>&1; then + if ! "$CC" "${C_TARGET_OPTIONS[@]}" $CFLAGS -c "$src" -o "$obj_path" 2>&1; then echo "FAILED: $rel" ERRORS=$((ERRORS + 1)) fi @@ -79,7 +81,7 @@ if [ "$ERRORS" -ne 0 ]; then fi # Archive into static library -OBJECTS=$(find "$OBJ_DIR" -name '*.o') +OBJECTS=$(find "$LVGL_OBJ_DIR" -name '*.o') ar rcs "${OUT_DIR}/liblvgl.a" $OBJECTS echo "Created ${OUT_DIR}/liblvgl.a" @@ -99,7 +101,7 @@ if [ -n "$LVGLH_SOURCES" ]; then obj_path="${LVGLH_OBJ_DIR}/${rel%.c}.o" mkdir -p "$(dirname "$obj_path")" echo " [lvglh] $rel" - if ! $CC $CFLAGS -c "$src" -o "$obj_path" 2>&1; then + if ! "$CC" "${C_TARGET_OPTIONS[@]}" $CFLAGS -c "$src" -o "$obj_path" 2>&1; then echo " FAILED: $rel" HERRORS=$((HERRORS + 1)) fi @@ -120,11 +122,7 @@ fi # Cache liblvgl.a and source to host mount for next build echo "Caching LVGL to host mount..." mkdir -p "$CACHE_DIR" -rm -rf ${CACHE_DIR}/* ${CACHE_DIR}/.[!.]* ${CACHE_DIR}/..?* 2>/dev/null || true -cp "${OUT_DIR}/liblvgl.a" "${CACHE_DIR}/liblvgl.a" -cp -a "$LVGL_DIR/src" "${CACHE_DIR}/src" -cp -a "$LVGL_DIR"/*.h "${CACHE_DIR}/" 2>/dev/null || true -cp -a "$OBJ_DIR" "${CACHE_DIR}/obj" +cp "${OUT_DIR}/liblvgl.a" "${CACHE_DIR}/${LVGL_CACHE_NAME}" echo "Done." echo " " diff --git a/toolchain/compile_sources.sh b/toolchain/compile_sources.sh index b6b86a34..c2816be3 100644 --- a/toolchain/compile_sources.sh +++ b/toolchain/compile_sources.sh @@ -1,13 +1,28 @@ #!/usr/bin/env bash +# Multi-architecture build updates: Copyright 2026 Aaron Hance. +set -e +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" echo " " echo "=======================" echo " " echo "Compiling FPC Sources..." echo " " -# Build -Fu flags for all directories under src/, wasuro/ and compat/ -FU_PATHS="-Fusrc -Fuwasuro -Fucompat" -for dir in $(find src wasuro compat -type d 2>/dev/null); do - FU_PATHS="$FU_PATHS -Fu$dir" -done +# Discover unit directories using the existing source roots. Architecture +# filtering follows the namespace convention; no unit directory is added by hand. +source_paths=() +while IFS= read -r -d '' dir; do + case "$dir" in + src/arch/*) + namespace="${dir#src/arch/}" + namespace="${namespace%%/*}" + if [[ "$namespace" != "$ASURO_ARCH" && "$namespace" != common ]]; then + continue + fi + ;; + esac + source_paths+=("-Fu$dir") +done < <(find src wasuro compat -type d -print0 | LC_ALL=C sort -zu) -fpc -Aelf -gw -g -gl -n -v0e -O3 -OpPENTIUM3 -Si -Sc -Sg -Xd -CX -XXs -CfSSE -CfSSE2 -Rintel -Pi386 -Tlinux -FElib/ $FU_PATHS src/asuro.pas \ No newline at end of file +exec "$PASCAL_COMPILER" -n -Si -Sc -Sg -Xd -CX -Rintel -Tlinux \ + "${PASCAL_OPTIONS[@]}" "${source_paths[@]}" \ + -FU"$OBJ_DIR" -FE"$BIN_DIR" "$PASCAL_ENTRY" diff --git a/toolchain/compile_stub.sh b/toolchain/compile_stub.sh index efb7d588..aecb77e6 100644 --- a/toolchain/compile_stub.sh +++ b/toolchain/compile_stub.sh @@ -1,8 +1,21 @@ #!/usr/bin/env bash -echo " " -echo "=======================" -echo " " -echo "Compiling Stub..." -echo " " -nasm -f elf src/arch/x86/boot/stub.asm -o lib/stub.o -nasm -f elf src/boot/splash_tga.asm -o lib/splash_tga.o \ No newline at end of file +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" +echo "Compiling assembly..." +# Include only standalone assembly under the target and enabled shared roots. +# Relative source paths keep equal basenames in separate directories distinct. +while IFS= read -r -d '' source_file; do + object="$OBJ_DIR/${source_file//\//_}" + nasm -f "$NASM_FORMAT" "$source_file" -o "${object%.asm}.o" +done < <(find "$ARCH_SOURCE_ROOT" "${ASM_SHARED_ROOTS[@]}" -type f -name '*.asm' -print0 | LC_ALL=C sort -z) diff --git a/toolchain/compile_sumgen.sh b/toolchain/compile_sumgen.sh index 42d4d228..f4584299 100644 --- a/toolchain/compile_sumgen.sh +++ b/toolchain/compile_sumgen.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash +# Target configuration updates: Copyright 2026 Aaron Hance. +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" echo " " echo "=======================" echo " " echo "Generating Checksum Badge..." echo " " -checksum=$(md5sum Asuro.iso | awk '{print $1}') +checksum=$(md5sum "$ISO_FILE" | awk '{print $1}') wget -q https://img.shields.io/badge/checksum-$checksum-important.svg -O release/checksum.svg 2>/dev/null \ No newline at end of file diff --git a/toolchain/compile_vergen.sh b/toolchain/compile_vergen.sh index 5382c513..f852cddd 100644 --- a/toolchain/compile_vergen.sh +++ b/toolchain/compile_vergen.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash set -e +# Target configuration updates: Copyright 2026 Aaron Hance. +source "$(dirname "${BASH_SOURCE[0]}")/target.sh" TOOLCHAIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" echo " " echo "=======================" @@ -12,8 +14,8 @@ outfile="src/core/core.version.pas" file="toolchain/version" { # this script requires semver tool - wget -q https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver -O bin/semver && chmod +x bin/semver - export PATH="$(pwd)/bin:$PATH" + wget -q https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver -O "$BIN_DIR/semver" && chmod +x "$BIN_DIR/semver" + export PATH="$(pwd)/$BIN_DIR:$PATH" } tagref=$(git describe --tags) revision=$(git rev-parse --short=8 HEAD) @@ -26,7 +28,7 @@ build=$(semver get build $tagref) linecount=$("${TOOLCHAIN_DIR}/loc.sh" | awk '{print $1}') sourcecount=$(find src -type f | wc -l) drivercount=$(find src/driver -type f | wc -l) -fpcversion=$(fpc -h | grep -m 1 version | awk '{print $5}') +fpcversion=$("$PASCAL_COMPILER" -h | grep -m 1 version | awk '{print $5}') makeversion=$(make -v | grep GNU | awk '{print $3}' | grep -v GNU) nasmversion=$(nasm -v | awk '{print $3'}) compiledate=$(date +"%d/%m/%y") diff --git a/toolchain/target.sh b/toolchain/target.sh new file mode 100644 index 00000000..848497c8 --- /dev/null +++ b/toolchain/target.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Load a target profile by convention rather than branching in build steps. +ASURO_ARCH="${ASURO_ARCH:-x86}" +if [[ ! "$ASURO_ARCH" =~ ^[a-zA-Z0-9_]+$ ]]; then + echo "Invalid ASURO_ARCH" >&2; exit 1 +fi +profile="$(dirname "${BASH_SOURCE[0]}")/targets/$ASURO_ARCH.sh" +if [ ! -f "$profile" ]; then echo "Unsupported ASURO_ARCH: $ASURO_ARCH" >&2; exit 1; fi +ARCH_SOURCE_ROOT="src/arch/$ASURO_ARCH" +TARGET_TOOLCHAIN_DIR="toolchain/$ASURO_ARCH" +OBJ_DIR="lib/$ASURO_ARCH" +BIN_DIR="bin/$ASURO_ARCH" +ISO_ROOT="$BIN_DIR/iso" +ISO_FILE="Asuro-$ASURO_ARCH.iso" +GRUB_CONFIG=iso/boot/grub/grub.cfg +ASM_ENTRY="$ARCH_SOURCE_ROOT/boot/stub.asm" +C_COMPILER=gcc +LINK_OPTIONS=() +LINK_COMPILER_RUNTIME=false +ASM_SHARED_ROOTS=() +FINISH_SCRIPT= +LVGL_CACHE_NAME="liblvgl-$ASURO_ARCH.a" +source "$profile" +export ASURO_ARCH OBJ_DIR BIN_DIR ISO_ROOT ISO_FILE diff --git a/toolchain/targets/x86.sh b/toolchain/targets/x86.sh new file mode 100644 index 00000000..1a16f43a --- /dev/null +++ b/toolchain/targets/x86.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Legacy output locations are retained for the existing VirtualBox workflow. +OBJ_DIR=lib +BIN_DIR=bin +ISO_ROOT=iso +ISO_FILE=Asuro.iso +PASCAL_COMPILER=fpc +PASCAL_ENTRY=src/asuro.pas +PASCAL_OPTIONS=(-Aelf -gw -g -gl -v0e -O3 -OpPENTIUM3 -XXs -CfSSE -CfSSE2 -Pi386) +C_TARGET_OPTIONS=(-m32 -march=i686) +NASM_FORMAT=elf +LD_FORMAT=elf_i386 +LINKER_SCRIPT=toolchain/linker.script +LINK_OPTIONS=(-s) +LINK_COMPILER_RUNTIME=true +ASM_SHARED_ROOTS=(src/boot) +BUILD_STEPS=(stub vergen lvgl icons wasuro sources initcalls link isogen docs) +FINISH_SCRIPT=compile_finish.sh +LVGL_CACHE_NAME=liblvgl.a + +KERNEL_VALIDATOR=(grub-file --is-x86-multiboot) diff --git a/toolchain/targets/x86_64.sh b/toolchain/targets/x86_64.sh new file mode 100644 index 00000000..bce58839 --- /dev/null +++ b/toolchain/targets/x86_64.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +PASCAL_COMPILER=ppcx64 +PASCAL_ENTRY="$ARCH_SOURCE_ROOT/arch.$ASURO_ARCH.boot.pas" +PASCAL_OPTIONS=(-O2) +C_TARGET_OPTIONS=(-m64 -mno-red-zone) +NASM_FORMAT=elf64 +LD_FORMAT=elf_x86_64 +LINKER_SCRIPT="$TARGET_TOOLCHAIN_DIR/linker.script" +# Native desktop and runtime sources follow the selected entry unit's dependencies. +BUILD_STEPS=(stub vergen lvgl sources initcalls link isogen) +case "${ASURO_BOOT_TEST:-none}" in + none) ;; + ud|pf|df) PASCAL_OPTIONS+=("-dASURO_TEST_${ASURO_BOOT_TEST^^}") ;; + *) echo "Unknown ASURO_BOOT_TEST" >&2; exit 1 ;; +esac + +KERNEL_VALIDATOR=(grub-file --is-x86-multiboot) diff --git a/toolchain/x86_64/linker.script b/toolchain/x86_64/linker.script new file mode 100644 index 00000000..664ee988 --- /dev/null +++ b/toolchain/x86_64/linker.script @@ -0,0 +1,31 @@ +/* Copyright 2026 Aaron Hance + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(loader) +SECTIONS +{ + /* Bootstrap-only low mapping. Final kernel address-space policy is a + separate milestone; do not inherit the i386 0xC0000000 offset. */ + . = 0x100000; + kernel_start = .; + .multiboot : { KEEP(*(.multiboot)) } + .text ALIGN(4096) : { *(.text .text.*) } + .rodata ALIGN(4096) : { *(.rodata .rodata.*) } + .data ALIGN(4096) : { *(.data .data.*) } + load_end = .; + .bss ALIGN(4096) (NOLOAD) : { *(COMMON) *(.bss .bss.*) } + . = ALIGN(4096); + kernel_end = .; + ASSERT(kernel_end < 0x100000000, "Bootstrap must fit below 4 GiB") + /DISCARD/ : { *(.comment) *(.note*) *(.eh_frame*) } +} diff --git a/toolchain/x86_64/test_boot.ps1 b/toolchain/x86_64/test_boot.ps1 new file mode 100644 index 00000000..d4b66a67 --- /dev/null +++ b/toolchain/x86_64/test_boot.ps1 @@ -0,0 +1,222 @@ +# Copyright 2026 Aaron Hance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Run from the workspace root after building the selected ASURO_BOOT_TEST. +# Restore temporary optical medium and optional RAM changes after the test. +param( + [string[]]$TerminalCommands = @(), + [string[]]$OpenPrograms = @(), + [switch]$NetworkInput, + [switch]$DesktopInput, + [ValidateSet('none', 'ud', 'pf', 'df')][string]$Test = 'none', + [ValidateRange(0, 8192)][int]$MemoryMB = 0, + [string]$Controller = 'AHCI', + [int]$Port = 1, + [int]$Device = 0, + [ValidateRange(5, 45)][int]$TimeoutSeconds = 35 +) +$ErrorActionPreference = 'Stop' +$config = Get-Content -LiteralPath .\localenv.json -Raw | ConvertFrom-Json +$vm = $config.virtualbox.MachineName +$serialPath = $config.virtualbox.LogLocation +if (-not $serialPath) { throw 'VirtualBox serial LogLocation is required.' } +$isoPath = (Resolve-Path -LiteralPath .\Asuro-x86_64.iso).Path +$state = & VBoxManage.exe showvminfo $vm --machinereadable +if ($LASTEXITCODE -ne 0) { throw 'Cannot inspect VirtualBox VM.' } +if ($state -notcontains 'VMState="poweroff"') { + throw 'The configured VM must already be powered off before boot validation.' +} +$mediumKey = '"{0}-{1}-{2}"=' -f $Controller, $Port, $Device +$mediumLine = @($state | Where-Object { $_.StartsWith($mediumKey) }) +if ($mediumLine.Count -ne 1) { throw 'Cannot identify original optical medium.' } +$originalMedium = $mediumLine[0].Substring($mediumKey.Length).Trim('"').Replace('\\', '\') +if ($originalMedium -notin @('none', 'emptydrive') -and $originalMedium -notmatch '(?i)\.iso$') { + throw 'Selected port is not an ISO/empty optical drive; refusing to replace it.' +} +if ($originalMedium -eq 'none') { $originalMedium = 'emptydrive' } +$evidence = Join-Path (Get-Location) 'bin\x86_64' +New-Item -ItemType Directory -Path $evidence -Force | Out-Null +$originalMemory = [int](($state | Where-Object { $_ -match '^memory=' }) -replace '^memory=', '') +$originalNetwork = (($state | Where-Object { $_ -match '^nic1=' }) -replace '^nic1=', '').Trim('"') +if ($originalMemory -le 0) { throw 'Cannot identify original VM RAM size.' } +function Open-DesktopProgram($mouse, [string]$name) { + $mouse.PutMouseEvent(-2000, -2000, 0, 0, 0) + Start-Sleep -Milliseconds 200 + $mouse.PutMouseEvent(240, 0, 0, 0, 0) + for ($step = 0; $step -lt 12; $step++) { + $mouse.PutMouseEvent(0, 100, 0, 0, 0) + Start-Sleep -Milliseconds 100 + } + $mouse.PutMouseEvent(0, -32, 0, 0, 0) + Start-Sleep -Milliseconds 200 + $mouse.PutMouseEvent(0, 0, 0, 0, 1) + Start-Sleep -Milliseconds 200 + $mouse.PutMouseEvent(0, 0, 0, 0, 0) + & VBoxManage.exe controlvm $vm keyboardputstring $name + Start-Sleep -Seconds 1 + & VBoxManage.exe controlvm $vm keyboardputscancode 1c 9c + Start-Sleep -Seconds 1 +} +$started = $false +try { + if ($NetworkInput) { + & VBoxManage.exe modifyvm $vm --nic1 nat + if ($LASTEXITCODE -ne 0) { throw 'Cannot set test NAT network.' } + Start-Sleep -Milliseconds 600 + } + if ($MemoryMB -ne 0) { + & VBoxManage.exe modifyvm $vm --memory $MemoryMB + if ($LASTEXITCODE -ne 0) { throw 'Cannot set test RAM size.' } + Start-Sleep -Milliseconds 600 + } + & VBoxManage.exe storageattach $vm --storagectl $Controller --port $Port --device $Device --type dvddrive --medium $isoPath + if ($LASTEXITCODE -ne 0) { throw 'Cannot attach bootstrap ISO.' } + # VBoxSVC releases configuration sessions asynchronously. + Start-Sleep -Milliseconds 500 + for ($attempt = 0; $attempt -lt 5; $attempt++) { + $launchOutput = & .\virtualbox-wrapper.ps1 -command up -Headless -NoMonitor 2>&1 + if ($LASTEXITCODE -eq 0) { + $started = $true + break + } + if (($launchOutput -join "`n") -notmatch 'already locked') { + throw "Cannot start test VM: $launchOutput" + } + Start-Sleep -Seconds 1 + } + if (-not $started) { throw "VirtualBox session remained locked: $launchOutput" } + $deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds) + $output = '' + $marker = if ($Test -eq 'none') { 'desktop ready' } else { 'HALTED: exception diagnostics complete' } + do { + if (Test-Path -LiteralPath $serialPath) { + $output = Get-Content -LiteralPath $serialPath -Raw + } + if ($output -and ($output.Contains($marker) -or $output.Contains('FAIL:'))) { break } + Start-Sleep -Milliseconds 200 + } while ([DateTime]::UtcNow -lt $deadline) + $output | Set-Content -LiteralPath (Join-Path $evidence "boot-$Test-$MemoryMB.log") + & VBoxManage.exe debugvm $vm getregisters rip rsp cs cr0 cr2 cr3 cr4 efer tr | + Set-Content -LiteralPath (Join-Path $evidence "registers-$Test-$MemoryMB.log") + if ($LASTEXITCODE -ne 0) { throw 'Cannot capture CPU registers.' } + if (($DesktopInput -or $NetworkInput -or $TerminalCommands.Count -gt 0 -or $OpenPrograms.Count -gt 0) -and $Test -eq 'none' -and $output.Contains($marker)) { + $vbox = New-Object -ComObject VirtualBox.VirtualBox + $session = New-Object -ComObject VirtualBox.Session + $machine = $vbox.FindMachine($vm) + $machine.LockMachine($session, 1) + try { + Start-Sleep -Seconds 1 + $mouse = $session.Console.Mouse + Open-DesktopProgram $mouse 'Terminal' + $mouse.PutMouseEvent(100, -400, 0, 0, 0) + Start-Sleep -Milliseconds 200 + $mouse.PutMouseEvent(0, 0, 0, 0, 1) + Start-Sleep -Milliseconds 200 + $mouse.PutMouseEvent(0, 0, 0, 0, 0) + Start-Sleep -Seconds 1 + foreach ($character in 'echo native64'.ToCharArray()) { + & VBoxManage.exe controlvm $vm keyboardputstring ([string]$character) + Start-Sleep -Milliseconds 150 + } + Start-Sleep -Seconds 2 + & VBoxManage.exe controlvm $vm keyboardputscancode 1c 9c + Start-Sleep -Seconds 3 + & VBoxManage.exe controlvm $vm keyboardputstring 'echo burst64' + & VBoxManage.exe controlvm $vm keyboardputscancode 1c 9c + Start-Sleep -Seconds 3 + foreach ($command in $TerminalCommands) { + & VBoxManage.exe controlvm $vm keyboardputstring $command + & VBoxManage.exe controlvm $vm keyboardputscancode 1c 9c + Start-Sleep -Seconds 3 + } + if ($NetworkInput) { + & VBoxManage.exe controlvm $vm keyboardputstring 'dhclient' + & VBoxManage.exe controlvm $vm keyboardputscancode 1c 9c + Start-Sleep -Seconds 5 + & VBoxManage.exe controlvm $vm keyboardputstring 'ifconfig' + & VBoxManage.exe controlvm $vm keyboardputscancode 1c 9c + Start-Sleep -Seconds 2 + & VBoxManage.exe controlvm $vm keyboardputstring 'ping 10.0.2.2' + & VBoxManage.exe controlvm $vm keyboardputscancode 1c 9c + Start-Sleep -Seconds 12 + } + foreach ($program in $OpenPrograms) { + Open-DesktopProgram $mouse $program + Start-Sleep -Seconds 3 + $safeName = $program -replace '[^A-Za-z0-9]', '-' + & VBoxManage.exe controlvm $vm screenshotpng (Join-Path $evidence "app-$safeName-$MemoryMB.png") + if ($LASTEXITCODE -ne 0) { throw "Cannot capture $program." } + } + } finally { + $session.UnlockMachine() + } + } + & VBoxManage.exe controlvm $vm screenshotpng (Join-Path $evidence "screen-$Test-$MemoryMB.png") + $output = Get-Content -LiteralPath $serialPath -Raw + $output | Set-Content -LiteralPath (Join-Path $evidence "boot-$Test-$MemoryMB.log") + Write-Output $output + if (-not $output -or -not $output.Contains($marker) -or $output.Contains('FAIL:')) { + throw 'Boot did not reach its expected completion marker.' + } + if ($Test -eq 'none' -and $output.Contains('HALTED:')) { + throw 'The kernel halted after starting the desktop.' + } + foreach ($required in @('PASS: native unit initialization runs once', 'EXCEPTION_TESTS_PASSED=0000000000000002', 'EXCEPTION_TESTS_FAILED=0000000000000000', + 'PASS: VFS reads ELF64 kernel through AHCI', 'VFS_TESTS_FAILED=0000000000000000', + 'STORMETA_TESTS_FAILED=0000000000000000', 'AHCI_TESTS_FAILED=0000000000000000', 'USBTYPES_TESTS_FAILED=0000000000000000', 'USBCORE_TESTS_FAILED=0000000000000000', + 'USBMSC_TESTS_FAILED=0000000000000000', 'USBKEYBOARD_TESTS_FAILED=0000000000000000', 'USBMOUSE_TESTS_FAILED=0000000000000000', 'USBHUB_TESTS_FAILED=0000000000000000', 'OHCI_TESTS_FAILED=0000000000000000', + 'Device enumerated at address 1', 'IOREQ_TESTS_FAILED=0000000000000000', 'CFIFO_TESTS_FAILED=0000000000000000', 'FSMGR_TESTS_FAILED=0000000000000000', 'PATH_TESTS_FAILED=0000000000000000', 'FNV1A_TESTS_FAILED=0000000000000000', 'HASHMAP_TESTS_FAILED=0000000000000000', 'LISTS_TESTS_FAILED=0000000000000000', 'PMM_TESTS_FAILED=0000000000000000', 'VMM_TESTS_FAILED=0000000000000000', + 'HEAP_TESTS_FAILED=0000000000000000', 'IRQ_TESTS_FAILED=0000000000000000', 'CONTEXT_TESTS_FAILED=0000000000000000', 'PROCESS_TESTS_FAILED=0000000000000000', 'FD_TESTS_FAILED=0000000000000000', 'STRINGS_TESTS_FAILED=0000000000000000', 'STDIO_TESTS_FAILED=0000000000000000')) { + if (-not $output.Contains($required)) { throw "Missing check: $required" } + } + if ($MemoryMB -gt 4096 -and -not $output.Contains('PASS: AHCI DMA above 4 GiB matches every disk sector')) { + throw 'High physical memory test was skipped.' + } + if ($NetworkInput -and -not $output.Contains('Process ACK.')) { + throw 'Network test did not receive a DHCP acknowledgement.' + } + if ($Test -ne 'none') { + $vector = @{ud = '6'; pf = 'E'; df = '8'}[$Test] + $errorCode = if ($Test -eq 'pf') { '2' } else { '0' } + foreach ($required in @("VECTOR=000000000000000$vector", "ERROR=000000000000000$errorCode", 'CS=0000000000000018')) { + if (-not $output.Contains($required)) { throw "Missing diagnostic: $required" } + } + if ($Test -eq 'pf' -and -not $output.Contains('CR2=0000000100000000')) { + throw 'Page fault did not preserve the expected CR2.' + } + if ($Test -eq 'df' -and (-not $output.Contains('STACK=double-fault IST1') -or + -not $output.Contains('RSP=0000000100001000'))) { + throw 'Double fault did not switch from the broken stack to IST1.' + } + } + Write-Output "PASS: VirtualBox x86_64 boot test '$Test'" +} finally { + try { + if ($started) { + & .\virtualbox-wrapper.ps1 -command down + if ($LASTEXITCODE -ne 0) { throw 'Cannot stop test VM.' } + } + } finally { + & VBoxManage.exe storageattach $vm --storagectl $Controller --port $Port --device $Device --type dvddrive --medium $originalMedium + if ($LASTEXITCODE -ne 0) { throw 'Cannot restore original optical medium.' } + if ($MemoryMB -ne 0) { + Start-Sleep -Milliseconds 500 + & VBoxManage.exe modifyvm $vm --memory $originalMemory + if ($LASTEXITCODE -ne 0) { throw 'Cannot restore original VM RAM size.' } + } + if ($NetworkInput) { + Start-Sleep -Milliseconds 600 + & VBoxManage.exe modifyvm $vm --nic1 $originalNetwork + if ($LASTEXITCODE -ne 0) { throw 'Cannot restore original network mode.' } + } + } +} diff --git a/virtualbox-wrapper.ps1 b/virtualbox-wrapper.ps1 index 1eb2c0a5..0768f3a7 100644 --- a/virtualbox-wrapper.ps1 +++ b/virtualbox-wrapper.ps1 @@ -10,7 +10,10 @@ You need a local git-ignored localenv.json file with the following content: param ( [Parameter(Mandatory=$true)] [ValidateSet('up', 'down')] - [String]$Command + [String]$Command, + # Optional modes for bounded, script-driven boot validation (Aaron Hance). + [switch]$Headless, + [switch]$NoMonitor ) $Config = Get-Content .\localenv.json | ConvertFrom-Json @@ -24,10 +27,23 @@ if ($Command -eq 'up') { Clear-Content $LogLocation } - $MonitorJob = Start-Job -ArgumentList $MachineName -ScriptBlock { - param($MachineName) + if ($NoMonitor) { + if ($Headless) { + VBoxManage.exe startvm $MachineName --type headless + } else { + VBoxManage.exe startvm $MachineName + } + exit $LASTEXITCODE + } + + $MonitorJob = Start-Job -ArgumentList $MachineName, $Headless.IsPresent -ScriptBlock { + param($MachineName, $Headless) Write-Output "Starting $MachineName" - VBoxManage.exe startvm $MachineName + if ($Headless) { + VBoxManage.exe startvm $MachineName --type headless + } else { + VBoxManage.exe startvm $MachineName + } $running=$true while($running) { $status=(VBoxManage.exe list runningvms) @@ -56,4 +72,4 @@ if ($Command -eq 'up') { } elseif ($Command -eq 'down') { Write-Output "Stopping $MachineName" VBoxManage.exe controlvm $MachineName poweroff -} \ No newline at end of file +}