├── .cargo └── config.toml ├── .github └── img │ └── r2-kernel-boot.png ├── .gitignore ├── .gitlab-ci.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── bochsrc.txt ├── build.rs ├── docs └── ABI_OVERVIEW.md ├── iso └── boot │ ├── boot.asm │ └── grub │ └── grub.cfg ├── linker.ld ├── rust-toolchain.toml ├── sonar-project.properties ├── src ├── abi │ ├── idt.rs │ ├── mod.rs │ └── syscall.rs ├── acpi │ ├── mod.rs │ └── shutdown.rs ├── app │ ├── chat │ │ ├── engine.rs │ │ ├── mod.rs │ │ └── tcp.rs │ ├── editor.rs │ ├── ether.rs │ ├── http_udp.rs │ ├── mod.rs │ ├── snake │ │ ├── engine.rs │ │ ├── level.rs │ │ ├── menu.rs │ │ ├── mod.rs │ │ └── score.rs │ └── tcp_handler.rs ├── audio │ ├── beep.rs │ ├── midi.rs │ └── mod.rs ├── debug.rs ├── fs │ ├── fat12 │ │ ├── block.rs │ │ ├── check.rs │ │ ├── entry.rs │ │ ├── fs.rs │ │ ├── mod.rs │ │ └── table.rs │ └── mod.rs ├── init │ ├── ascii.rs │ ├── boot.rs │ ├── color.rs │ ├── config.rs │ ├── cpu.rs │ ├── font.rs │ ├── fs.rs │ ├── heap.rs │ ├── idt.rs │ ├── mod.rs │ ├── pit.rs │ ├── result.rs │ └── video.rs ├── input │ ├── cmd.rs │ ├── elf.rs │ ├── elf.rs.bak │ ├── irq.rs │ ├── keyboard.rs │ ├── mod.rs │ └── port.rs ├── int.rs ├── main.rs ├── mem │ ├── bump.rs │ ├── c.rs │ ├── heap.rs │ ├── mod.rs │ └── pages.rs ├── multiboot2.rs ├── net │ ├── arp.rs │ ├── ethernet.rs │ ├── icmp.rs │ ├── ipv4.rs │ ├── mod.rs │ ├── pci.rs │ ├── rtl8139.rs │ ├── serial.rs │ ├── slip.rs │ ├── tcp.rs │ └── udp.rs ├── task │ ├── context.asm │ ├── mod.rs │ ├── pipe.rs │ └── process.rs ├── time │ ├── acpi.rs │ ├── mod.rs │ └── rtc.rs ├── tui │ ├── app.rs │ ├── mod.rs │ ├── screen.rs │ └── widget.rs ├── vga │ ├── buffer.rs │ ├── mod.rs │ ├── screen.rs │ └── write.rs └── video │ ├── macros.rs │ ├── mod.rs │ ├── mode.rs │ └── vga.rs ├── terminus-font.psf ├── utils └── midi.py └── x86_64-r2.json /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [unstable] 2 | build-std-features = ["compiler-builtins-mem", "panic_immediate_abort"] 3 | build-std = ["core", "compiler_builtins"] 4 | 5 | [build] 6 | target = "x86_64-r2.json" 7 | 8 | [target.x86_64-r2] 9 | runner = "qemu-system-x86_64" 10 | rustflags = [ 11 | "-C", "link-arg=-Tlinker.ld" 12 | ] 13 | 14 | # [target.'cfg(target_os = "none")'] 15 | # runner = "bootimage runner" 16 | # rustflags = [ 17 | # "-C", "link-arg=-Tlinker.ld" 18 | # ] 19 | -------------------------------------------------------------------------------- /.github/img/r2-kernel-boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krustowski/rou2exOS/f5801dc8a39f14b3c100237784590363b9c20fe2/.github/img/r2-kernel-boot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /iso/boot/kernel.* 3 | /iso/boot/*.o 4 | /kernel.* 5 | /iso/boot/kernel* 6 | /r2.iso 7 | /fat.img 8 | /*.bin 9 | /*.elf 10 | /*.out 11 | /*.o 12 | *.o 13 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | workflow: 2 | rules: 3 | - when: always 4 | 5 | stages: 6 | - test 7 | 8 | combined-coverage-sonarqube-check: 9 | stage: test 10 | when: manual 11 | rules: 12 | - when: always 13 | allow_failure: true 14 | script: 15 | - make sonar_check 16 | 17 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 10 | 11 | [[package]] 12 | name = "bit_field" 13 | version = "0.10.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "2.9.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" 22 | 23 | [[package]] 24 | name = "kernel" 25 | version = "0.9.6" 26 | dependencies = [ 27 | "spin", 28 | "x86_64", 29 | ] 30 | 31 | [[package]] 32 | name = "lock_api" 33 | version = "0.4.13" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 36 | dependencies = [ 37 | "autocfg", 38 | "scopeguard", 39 | ] 40 | 41 | [[package]] 42 | name = "rustversion" 43 | version = "1.0.22" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 46 | 47 | [[package]] 48 | name = "scopeguard" 49 | version = "1.2.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 52 | 53 | [[package]] 54 | name = "spin" 55 | version = "0.10.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" 58 | dependencies = [ 59 | "lock_api", 60 | ] 61 | 62 | [[package]] 63 | name = "volatile" 64 | version = "0.4.6" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" 67 | 68 | [[package]] 69 | name = "x86_64" 70 | version = "0.15.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "0f042214de98141e9c8706e8192b73f56494087cc55ebec28ce10f26c5c364ae" 73 | dependencies = [ 74 | "bit_field", 75 | "bitflags", 76 | "rustversion", 77 | "volatile", 78 | ] 79 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel" 3 | description = "" 4 | version = "0.9.6" 5 | edition = "2021" 6 | authors = ["krusty "] 7 | 8 | [dependencies] 9 | x86_64 = { version = "0.15.2", default-features = false, features = ["instructions", "abi_x86_interrupt"] } 10 | spin = "0.10.0" 11 | 12 | [features] 13 | kernel_text = [] 14 | kernel_graphics = [] 15 | 16 | [profile.dev] 17 | panic = "abort" 18 | 19 | [profile.release] 20 | lto = false 21 | panic = "abort" 22 | debug = true 23 | 24 | [[bin]] 25 | name = "kernel" 26 | path = "src/main.rs" 27 | test = false 28 | doctest = false 29 | bench = false 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 krusty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | @ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 3 | @. "${HOME}/.cargo/env" && \ 4 | rustup install nightly && \ 5 | rustup default nightly && \ 6 | rustup target add x86_64-unknown-none && \ 7 | rustup component add rust-src llvm-tools-preview && \ 8 | cargo install bootimage 9 | 10 | # 11 | # BUILD 12 | # 13 | 14 | build: compile_kernel build_iso 15 | 16 | #@cargo rustc --release --target x86_64-r2.json -- -C relocation-model=static --emit=obj 17 | compile_kernel: 18 | @cargo build \ 19 | --features kernel_text \ 20 | --target-dir target/kernel_text \ 21 | --release \ 22 | -Z build-std=core,compiler_builtins \ 23 | --target x86_64-r2.json 24 | @cp target/kernel_text/x86_64-r2/release/kernel.elf iso/boot/kernel_text.elf 25 | @cargo build \ 26 | --features kernel_graphics \ 27 | --target-dir target/kernel_graphics \ 28 | --release \ 29 | -Z build-std=core,compiler_builtins \ 30 | --target x86_64-r2.json 31 | @cp target/kernel_graphics/x86_64-r2/release/kernel.elf iso/boot/kernel_graphics.elf 32 | 33 | build_iso: 34 | @grub-mkrescue \ 35 | -o r2.iso iso/ \ 36 | --modules="multiboot2 video video_bochs video_cirrus gfxterm all_video" 37 | 38 | build_floppy: 39 | @dd \ 40 | if=/dev/zero \ 41 | of=fat.img \ 42 | bs=512 \ 43 | count=2880 44 | @mkfs.fat \ 45 | -F 12 \ 46 | fat.img 47 | @echo "Hello from floppy!" > /tmp/hello.txt 48 | @mcopy -i fat.img /tmp/hello.txt ::HELLO.TXT 49 | @mcopy -i fat.img ./print.bin ::PRINT.BIN 50 | @mcopy -i fat.img ./print.elf ::PRINT.ELF 51 | @mcopy -i fat.img ./go.elf ::GO.ELF 52 | @mcopy -i fat.img ./sh.elf ::SH.ELF 53 | @mcopy -i fat.img ./icmpresp.elf ::ICMPRESP.ELF 54 | @mcopy -i fat.img ./garn.elf ::GARN.ELF 55 | 56 | # 57 | # RUN 58 | # 59 | 60 | run: 61 | @qemu-system-x86_64 \ 62 | -serial pty \ 63 | -drive format=raw,file=target/x86_64-r2/debug/bootimage-x86_64-r2.bin 64 | 65 | run_iso: 66 | @qemu-system-x86_64 \ 67 | -boot d \ 68 | -m 2G \ 69 | -vga std \ 70 | -cdrom r2.iso \ 71 | -serial pty 72 | 73 | run_iso_usb: 74 | @qemu-system-x86_64 \ 75 | -m 2G \ 76 | -vga std \ 77 | -hdb /dev/sdb \ 78 | -serial pty 79 | 80 | run_iso_net: 81 | @qemu-system-x86_64 \ 82 | -boot d \ 83 | -m 2G \ 84 | -vga std \ 85 | -cdrom r2.iso \ 86 | -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ 87 | -device rtl8139,netdev=net0 \ 88 | -serial pty 89 | 90 | PTY_NUMBER ?= pty 91 | run_iso_pty: 92 | @qemu-system-x86_64 \ 93 | -boot d \ 94 | -m 2G \ 95 | -vga std \ 96 | -cdrom r2.iso \ 97 | -serial ${PTY_NUMBER} 98 | 99 | run_iso_floppy: build_floppy 100 | @qemu-system-x86_64 \ 101 | -boot d \ 102 | -m 2G \ 103 | -vga std \ 104 | -cdrom r2.iso \ 105 | -fda fat.img \ 106 | -serial pty 107 | 108 | run_iso_floppy_drive: 109 | @sudo qemu-system-x86_64 \ 110 | -boot d \ 111 | -m 2G \ 112 | -vga std \ 113 | -cdrom r2.iso \ 114 | -serial pty \ 115 | -blockdev host_device,node-name=floppy1,filename=/dev/sda \ 116 | -device floppy,drive=floppy1 117 | 118 | run_iso_debug: 119 | @qemu-system-x86_64 \ 120 | -boot d \ 121 | -m 4G \ 122 | -cdrom r2.iso \ 123 | -fda fat.img \ 124 | -no-reboot \ 125 | -no-shutdown \ 126 | -serial stdio \ 127 | -audiodev pa,id=snd0 \ 128 | -machine pcspk-audiodev=snd0 129 | 130 | run_iso_debug_int: 131 | @qemu-system-x86_64 \ 132 | -boot d \ 133 | -m 4G \ 134 | -cdrom r2.iso \ 135 | -fda fat.img \ 136 | -no-reboot \ 137 | -no-shutdown \ 138 | -serial stdio \ 139 | -d int,cpu_reset,page \ 140 | -audiodev pa,id=snd0 \ 141 | -machine pcspk-audiodev=snd0 142 | 143 | # 144 | # HELPERS 145 | # 146 | 147 | clean: 148 | @cargo clean 149 | 150 | clippy: 151 | @cargo clippy \ 152 | --release \ 153 | --target x86_64-r2.json \ 154 | --no-default-features \ 155 | -- -D warnings 156 | 157 | 158 | ifeq (${SONAR_HOST_URL}${SONAR_TOKEN},) 159 | sonar_check: 160 | else 161 | sonar_check: 162 | @docker run --rm \ 163 | --dns ${DNS_NAMESERVER} \ 164 | -e SONAR_HOST_URL="${SONAR_HOST_URL}" \ 165 | -e SONAR_TOKEN="${SONAR_TOKEN}" \ 166 | -v ".:/usr/src" \ 167 | sonarsource/sonar-scanner-cli 168 | endif 169 | 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rou2exOS Rusted Edition 2 | 3 | A second iteration of the RoureXOS operating system, rewritten in Rust. 4 | 5 | + [Original RoureXOS (a blog post)](https://krusty.space/projects/rourexos/) 6 | + [rou2exOS Rusted Edition (a blog post)](https://blog.vxn.dev/rou2exos-rusted-edition) 7 | 8 | The goal of this project is to make the rou2exOS kernel to follow the microkernel architecture. For purposes of the external program development, there are two key links to consider opening when thinking about extending the system: 9 | 10 | + [ABI specification document](/docs/ABI_OVERVIEW.md) 11 | + [Syscall client implementation examples](https://github.com/krustowski/rou2exOS-apps) (aka `rou2exOS` Apps) 12 | 13 | To run the OS, you can use the attached ISO image from any [Release](https://github.com/krustowski/rou2exOS/releases), and run it in QEMU emulator. The system was also tested on the x86_64 baremetal (booted from the USB flash disk). To enable the filesystem functionalities, attach a IMG file to QEMU as well (in virtual floppy drive A). 14 | 15 | ``` 16 | qemu-system-x86_64 -boot d -cdrom r2.iso -fda fat.img 17 | ``` 18 | 19 | ## Preview 20 | 21 | ![rou2exOS startup](/.github/img/r2-kernel-boot.png) 22 | 23 | ## How to build and run from source 24 | 25 | ```shell 26 | # install Rust and its dependencies 27 | make init 28 | 29 | # make sure you have `xorriso`, `net-tools` and `grub2-tools` (or just grub-tools) 30 | # installed (Linux) 31 | dnf install xorriso net-tools grub2-tools qemu qemu-common qemu-system-x86 mtools lld 32 | 33 | # compile the kernel and stage2 bootloader, link it into an ELF binary and bake into an ISO 34 | # image with GRUB bootloader 35 | make build 36 | 37 | # run the QEMU emulation with ISO image 38 | make run_iso 39 | 40 | # create a floppy image and attach it to virtual machine (will enable filesystem-related features) 41 | # please do note that the floppy image is overwritten every time you hit this target 42 | make run_iso_floppy 43 | 44 | # (alternative) run the kernel exclusively only (needs the `bootloader` dependency in 45 | # Cargo.toml to be added) 46 | cargo bootimage 47 | make run 48 | ``` 49 | 50 | ## How to test ICMP/SLIP 51 | 52 | Start a virtual machine to receive the `pty` handle: 53 | 54 | ``` 55 | make run_iso 56 | 57 | char device redirected to /dev/pts/3 (label serial0) 58 | ``` 59 | 60 | Listen for SLIP packets and create a `sl0` interface: 61 | 62 | ``` 63 | sudo slattach -L -p slip -s 115200 /dev/pts/3 64 | sudo ifconfig sl0 192.168.3.1 pointopoint 192.168.3.2 up 65 | ``` 66 | 67 | Catch packets using `tcpdump`: 68 | 69 | ``` 70 | sudo tcpdump -i sl0 71 | ``` 72 | 73 | Run the `response` command in the system shell to handle ICMP 74 | ```rou2exOS 75 | response 76 | ``` 77 | 78 | Now you should be able to ping the machine from your machine 79 | ``` 80 | ping 192.168.3.2 81 | ``` 82 | 83 | ## How to convert and import a font (graphics mode) 84 | 85 | Download a font (ideally in PSF format), add the selected font file into `src/video/fonts` as `console.psf`. 86 | 87 | To convert a font from BDF format use the `bdf2psf` utility (should be available in the Linux distro repos). We especially want to render the font to Framebuffer (`--fb` flag). An example command syntax is shown below. 88 | 89 | ```shell 90 | bdf2psf --fb terminus-font-4.49.1/ter-u16b.bdf /usr/share/bdf2psf/standard.equivalents /usr/share/bdf2psf/ascii.set 256 console.psf 91 | ``` 92 | 93 | -------------------------------------------------------------------------------- /bochsrc.txt: -------------------------------------------------------------------------------- 1 | megs: 32 2 | romimage: file=/usr/share/bochs/BIOS-bochs-latest 3 | vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest 4 | boot: a 5 | floppya: 1_44="boot.img", status=inserted 6 | log: bochslog.txt 7 | cpu: reset_on_triple_fault=1 8 | mouse: enabled=0 9 | display_library: sdl2 10 | 11 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | macro_rules! nasm { 4 | ($inp:literal, $out:literal) => { 5 | let res = Command::new("nasm") 6 | .arg("-f") 7 | .arg("elf64") 8 | .arg("-o") 9 | .arg($out) 10 | .arg($inp) 11 | .status() 12 | .unwrap() 13 | .success(); 14 | assert!(res); 15 | println!("cargo::rustc-link-arg={}", $out); 16 | }; 17 | } 18 | 19 | fn main() { 20 | nasm!("iso/boot/boot.asm", "iso/boot/boot.o"); 21 | nasm!("src/task/context.asm", "src/task/context.o"); 22 | } -------------------------------------------------------------------------------- /iso/boot/boot.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; boot.asm 3 | ; NASM syntax - stage2 stub to pre-set GDT, IDT and memory paging before jumping into 64bit long mode 4 | ; 5 | 6 | BITS 32 7 | 8 | section .bss 9 | align 4096 10 | 11 | dma: 12 | resb 4096 13 | 14 | p4_table: 15 | resb 4096 16 | p3_table: 17 | resb 4096 18 | p2_table: 19 | resb 4096 20 | 21 | p3_fb_table: 22 | resb 4096 23 | 24 | align 16 25 | ist1_stack: 26 | resb 4096 27 | ist1_stack_top: 28 | 29 | tss64: 30 | resb 104 31 | 32 | align 4 33 | multiboot_magic: 34 | resq 1 35 | multiboot_ptr: 36 | resq 1 37 | 38 | ; 39 | ; Text Section + Kernel Entry Point 40 | ; 41 | 42 | section .text 43 | align 4 44 | 45 | extern __stack_bottom 46 | extern __stack_top 47 | 48 | extern kernel_main 49 | global start 50 | 51 | global multiboot_magic 52 | global multiboot_ptr 53 | 54 | global tss64 55 | global dma 56 | 57 | global p4_table 58 | global p3_table 59 | global p2_table 60 | 61 | global gdt_start 62 | global gdt_end 63 | global gdt_descriptor 64 | global gdt_tss_descriptor 65 | global idt_ptr 66 | 67 | global debug_flag 68 | debug_flag: 69 | db 0 ; 1 = enabled 70 | 71 | start: 72 | mov [multiboot_magic], eax 73 | mov [multiboot_ptr], ebx 74 | 75 | mov eax, p4_table 76 | mov cr3, eax 77 | 78 | cli 79 | 80 | call set_up_page_tables 81 | 82 | mov dword [0xb8000 + 80], 0x2f4b2f4f 83 | 84 | ;call load_tss 85 | call load_gdt 86 | call load_idt 87 | 88 | mov ax, 0x10 89 | mov ds, ax 90 | mov es, ax 91 | mov fs, ax 92 | mov gs, ax 93 | mov ss, ax 94 | 95 | call enable_paging 96 | 97 | ; Load 64-bit code segment and jump 98 | jmp 0x08:long_mode_entry 99 | 100 | jmp $ 101 | 102 | load_gdt: 103 | lgdt [gdt_descriptor] 104 | ret 105 | 106 | load_tss: 107 | ; Set IST1 stack pointer 108 | mov eax, ist1_stack_top 109 | mov [gdt_tss_descriptor + 36], eax 110 | 111 | mov ax, 0x28 112 | ltr ax 113 | ret 114 | 115 | load_idt: 116 | lidt [idt_ptr] 117 | ret 118 | 119 | ; 120 | ; Global Descriptor Table + Task State Segment 121 | ; 122 | 123 | section .rodata 124 | align 8 125 | 126 | gdt_start: 127 | ; Null descriptor 128 | dq 0x0000000000000000 129 | 130 | ; Kernel code segment (offset 0x08) 131 | dq 0x00AF9A000000FFFF 132 | 133 | ; Kernel data segment (offset 0x10) 134 | dq 0x00AF92000000FFFF 135 | 136 | ; User code segment (offset 0x18) 137 | dq 0x00affa000000ffff 138 | 139 | ; User data segment (offset 0x20) 140 | dq 0x00aff2000000ffff 141 | 142 | gdt_tss_descriptor: 143 | dw 0x0067 ; limit 144 | dw 0 ; base low 16 (will patch) 145 | db 0 ; base mid 8 (will patch) 146 | db 0x89 ; access byte 147 | db 0 ; flags + limit high nibble 148 | db 0 ; base high 8 149 | dd 0 ; base upper 32 bits 150 | dd 0 ; reserved 151 | 152 | gdt_end: 153 | 154 | gdt_descriptor: 155 | dw gdt_end - gdt_start - 1 156 | dq gdt_start 157 | 158 | ; 159 | ; Interrupt Descriptor Table 160 | ; 161 | 162 | ; Define a 256-entry dummy IDT (null handlers) 163 | idt_ptr: 164 | dw idt_end - idt_start - 1 165 | dq idt_start 166 | 167 | extern page_fault_handler 168 | idt_start: 169 | ; Page fault handler (vector 0x0E) 170 | dq page_fault_handler 171 | dw 0x08 ; Code segment (kernel code segment) 172 | db 0 173 | db 0x8E ; Type: interrupt gate (present, DPL = 0) 174 | dw 0 175 | dd 0 176 | dq 0 177 | 178 | times 256 dq 0 179 | idt_end: 180 | 181 | ; 182 | ; Page Tables Zeroing & Mapping 183 | ; 184 | 185 | section .text 186 | 187 | zero_table: 188 | mov ecx, 512 189 | xor eax, eax 190 | 191 | .zero_loop: 192 | mov [edi], eax 193 | add edi, 8 194 | loop .zero_loop 195 | 196 | ret 197 | 198 | set_up_page_tables: 199 | lea edi, [p4_table] 200 | call zero_table 201 | 202 | lea edi, [p3_table] 203 | call zero_table 204 | 205 | lea edi, [p2_table] 206 | call zero_table 207 | 208 | ; Map P4[0] → P3 209 | mov eax, p3_table 210 | or eax, 0b111 211 | mov [p4_table + 0 * 8], eax 212 | mov dword [p4_table + 0 * 8 + 4], 0 213 | 214 | mov eax, p3_fb_table 215 | or eax, 0b111 216 | mov [p4_table + 1 * 8], eax 217 | mov dword [p4_table + 1 * 8 + 4], 0 218 | 219 | ; Map P3[0] → P2 220 | mov eax, p2_table 221 | or eax, 0b111 222 | mov [p3_table + 0 * 8], eax 223 | mov dword [p3_table + 0 * 8 + 4], 0 224 | 225 | ; Identity map 1 GiB (512 runs) using huge pages 226 | 227 | xor ecx, ecx 228 | .map_2mib: 229 | mov eax, 0x200000 230 | mul ecx 231 | or eax, 0b10000011 232 | mov [p2_table + ecx * 8], eax 233 | mov dword [p2_table + ecx * 8 + 4], 0 234 | 235 | inc ecx 236 | cmp ecx, 512 237 | jne .map_2mib 238 | 239 | mov ecx, 1 240 | .map_1gib: 241 | mov eax, 0x40000000 242 | mul ecx 243 | or eax, 0b10000011 244 | mov [p3_table + ecx * 8], eax 245 | mov dword [p3_table + ecx * 8 + 4], 0 246 | 247 | inc ecx 248 | cmp ecx, 3 249 | jne .map_1gib 250 | 251 | ; Allow CPL=3 access at 0x600_000--0xA00_000 252 | 253 | mov eax, 0x600000 254 | or eax, 0b11100111 255 | mov [p2_table + 3 * 8], eax 256 | mov dword [p2_table + 3 * 8 + 4], 0 257 | 258 | mov eax, 0x800000 259 | or eax, 0b11100111 260 | mov [p2_table + 4 * 8], eax 261 | mov dword [p2_table + 4 * 8 + 4], 0 262 | 263 | ret 264 | 265 | enable_paging: 266 | mov eax, cr4 267 | or eax, 1 << 5 268 | mov cr4, eax 269 | 270 | mov ecx, 0xC0000080 271 | rdmsr 272 | or eax, 1 << 8 273 | wrmsr 274 | 275 | ;mov eax, p4_table 276 | ;mov cr3, eax 277 | 278 | mov eax, cr0 279 | or eax, 1 << 31 280 | mov cr0, eax 281 | 282 | ret 283 | 284 | ; 285 | ; Long Mode Entry Point 286 | ; 287 | 288 | BITS 64 289 | 290 | section .text 291 | 292 | long_mode_entry: 293 | ; TLB flush 294 | mov rax, cr3 295 | mov cr3, rax 296 | 297 | mov ax, 0x10 298 | mov ds, ax 299 | mov es, ax 300 | mov fs, ax 301 | mov gs, ax 302 | mov ss, ax 303 | 304 | ; Clear the stack 305 | mov rsp, __stack_top 306 | 307 | mov rsi, [multiboot_ptr] 308 | mov rdi, [multiboot_magic] 309 | 310 | call kernel_main 311 | 312 | hlt 313 | jmp $ 314 | 315 | -------------------------------------------------------------------------------- /iso/boot/grub/grub.cfg: -------------------------------------------------------------------------------- 1 | set timeout=5 2 | set default=0 3 | 4 | #insmod gfxterm 5 | insmod all_video 6 | #insmod multiboot2 7 | 8 | menuentry "rou2exOS Rusted Edition (text mode)" { 9 | multiboot2 /boot/kernel_text.elf 10 | boot 11 | } 12 | 13 | menuentry "rou2exOS Rusted Edition (graphics)" { 14 | multiboot2 /boot/kernel_graphics.elf grafix 15 | boot 16 | } 17 | 18 | -------------------------------------------------------------------------------- /linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(start) 2 | 3 | SECTIONS { 4 | . = 0x100000; /* 1 MB offset */ 5 | 6 | .multiboot2_header : ALIGN(4K) { 7 | KEEP(*(.multiboot2_header)) 8 | } 9 | 10 | .text : { 11 | *(.text.entry) 12 | *(.text*) 13 | } 14 | 15 | .rodata : { 16 | *(.rodata*) 17 | } 18 | 19 | .data : { 20 | *(.data*) 21 | *(.dma) 22 | } 23 | 24 | .bss : { 25 | *(.bss*) 26 | *(COMMON) 27 | } 28 | 29 | .gdt : { 30 | *(.gdt) 31 | } 32 | 33 | .idt : { 34 | *(.idt) 35 | } 36 | 37 | /* Stack */ 38 | . = ALIGN(16); 39 | __stack_bottom = .; 40 | . = . + 64K; 41 | __stack_top = .; 42 | 43 | /* Heap */ 44 | . = ALIGN(16); 45 | __heap_start = .; 46 | . = . + 64K; 47 | __heap_end = .; 48 | 49 | . = ALIGN(4K); 50 | p4_table = .; . = . + 4K; 51 | p3_fb_table = .; . = . + 4K; 52 | 53 | /* Experimental, TBD */ 54 | . = 0x650000; 55 | .user_task : { 56 | *(.user_task*) 57 | } 58 | 59 | . = 0x80000; 60 | .dma : ALIGN(512) { 61 | KEEP(*(.dma)) 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | targets = [ 4 | "x86_64-unknown-none", 5 | ] 6 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=vxn_r2_AZcbtYeeFAl1ZBDE-xQZ 2 | sonar.qualitygate.wait=true 3 | 4 | -------------------------------------------------------------------------------- /src/abi/idt.rs: -------------------------------------------------------------------------------- 1 | use x86_64::{registers::control::Cr2, structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}}; 2 | 3 | use crate::{abi::syscall::{syscall_80h, syscall_handler}, input::keyboard::keyboard_loop}; 4 | 5 | #[link_section = ".data.idt"] 6 | static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new(); 7 | 8 | // 9 | // 10 | // 11 | 12 | /*static mut IDT: Mutex = Mutex::new({ 13 | unsafe { 14 | let mut idt = InterruptDescriptorTable::new(); 15 | 16 | // Rust exception handlers 17 | idt.page_fault.set_handler_fn(page_fault_handler); 18 | idt.double_fault.set_handler_fn(double_fault_handler); 19 | idt.invalid_opcode.set_handler_fn(invalid_opcode_handler); 20 | 21 | // Custom int 0x7f ISR (manually set the handler address) 22 | idt[0x7f].set_handler_addr(VirtAddr::new(int7f_isr as u64)) 23 | .set_privilege_level(x86_64::PrivilegeLevel::Ring3) // if needed 24 | .set_code_selector(SegmentSelector::new(0x08, x86_64::PrivilegeLevel::Ring3)) 25 | .set_present(true); 26 | 27 | idt 28 | } 29 | });*/ 30 | 31 | #[no_mangle] 32 | #[link_section = ".text"] 33 | extern "x86-interrupt" fn page_fault_handler(stack_frame: InterruptStackFrame, error_code: PageFaultErrorCode) { 34 | error!("EXCEPTION: PAGE FAULT"); 35 | warn!("\nAccessed Address: "); 36 | 37 | match Cr2::read() { 38 | Ok(addr) => { 39 | printn!(addr.as_u64()); 40 | } 41 | Err(_) => { 42 | warn!("Cannot read CR2"); 43 | } 44 | } 45 | 46 | warn!("\nError Code: "); 47 | printn!(error_code.bits()); 48 | warn!("\nStack frame: "); 49 | printn!(stack_frame.stack_pointer.as_u64()); 50 | print!("\n\n"); 51 | 52 | keyboard_loop(); 53 | } 54 | 55 | extern "x86-interrupt" fn general_protection_fault_handler(_frame: InterruptStackFrame, error_code: u64) { 56 | error!("EXCEPTION: GENERAL PROTECTION FAULT"); 57 | 58 | warn!("\nError code: "); 59 | printn!(error_code); 60 | print!("\n\n"); 61 | 62 | keyboard_loop(); 63 | } 64 | 65 | extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) { 66 | error!("EXCEPTION: INVALID OPCODE"); 67 | 68 | warn!("\nRIP: "); 69 | printn!(stack_frame.instruction_pointer.as_u64()); 70 | warn!("\nStack frame: "); 71 | printn!(stack_frame.stack_pointer.as_u64()); 72 | print!("\n\n"); 73 | 74 | // Try to skip the invalid opcode 75 | //frame.rip += 3; 76 | keyboard_loop(); 77 | } 78 | 79 | extern "x86-interrupt" fn double_fault_handler(stack_frame: InterruptStackFrame, error_code: u64) -> ! { 80 | error!("EXCEPTION: DOUBLE FAULT"); 81 | 82 | warn!("\nError Code: "); 83 | printn!(error_code); 84 | warn!("\nStack frame: "); 85 | printn!(stack_frame.stack_pointer.as_u64()); 86 | print!("\n\nRecovering the shell...", crate::video::vga::Color::White); 87 | print!("\n\n"); 88 | 89 | // Recover the shell 90 | keyboard_loop(); 91 | } 92 | 93 | pub fn load_idt() { 94 | #[expect(static_mut_refs)] 95 | unsafe { IDT.load() }; 96 | } 97 | 98 | #[no_mangle] 99 | extern "x86-interrupt" fn timer_handler(_stack: InterruptStackFrame) { 100 | // Acknowledge the PIC 101 | crate::input::port::write(0x20, 0x20); 102 | 103 | crate::task::schedule(); // Switch tasks 104 | } 105 | 106 | extern "x86-interrupt" fn keyboard_handler(_stack: InterruptStackFrame) { 107 | let scancode = crate::input::port::read_u8(0x60); 108 | 109 | unsafe { 110 | #[expect(static_mut_refs)] // this is bad but i cant figure out how to fix 111 | for s in crate::input::irq::RECEPTORS.iter() { 112 | if s.pid != 0 { 113 | s.push_irq(scancode); 114 | } 115 | } 116 | } 117 | 118 | // Acknowledge the PIC 119 | crate::input::port::write(0x20, 0x20); 120 | } 121 | 122 | extern "x86-interrupt" fn floppy_drive_handler(_stack: InterruptStackFrame) { 123 | // Acknowledge the PIC 124 | //crate::input::port::write(0x20, 0x20); 125 | } 126 | 127 | #[expect(static_mut_refs)] 128 | /// https://phrack.org/issues/59/4 129 | pub fn install_isrs() { 130 | unsafe { IDT.invalid_opcode.set_handler_fn(invalid_opcode_handler) }; 131 | unsafe { IDT.double_fault.set_handler_fn(double_fault_handler) }; 132 | unsafe { IDT.general_protection_fault.set_handler_fn(general_protection_fault_handler) }; 133 | unsafe { IDT.page_fault.set_handler_fn(page_fault_handler) }; 134 | 135 | unsafe { IDT[0x20].set_handler_fn(timer_handler) }; 136 | unsafe { IDT[0x21].set_handler_fn(keyboard_handler) }; 137 | unsafe { IDT[0x26].set_handler_fn(floppy_drive_handler) }; 138 | unsafe { IDT[0x7f].set_handler_fn(syscall_handler).set_privilege_level(x86_64::PrivilegeLevel::Ring3) }; 139 | unsafe { IDT[0x80].set_handler_fn(syscall_80h) }; 140 | } 141 | -------------------------------------------------------------------------------- /src/abi/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod idt; 2 | pub mod syscall; 3 | -------------------------------------------------------------------------------- /src/acpi/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod shutdown; 2 | -------------------------------------------------------------------------------- /src/acpi/shutdown.rs: -------------------------------------------------------------------------------- 1 | pub fn shutdown() { 2 | unsafe { 3 | // ACPI shutdown port (common for Bochs/QEMU/VirtualBox) 4 | const SLP_TYPA: u16 = 0x2000; 5 | const SLP_EN: u16 = 1 << 13; 6 | 7 | // Fallback PM1a control port address 8 | const PM1A_CNT_PORT: u16 = 0x604; 9 | 10 | // Write shutdown command 11 | let value = SLP_TYPA | SLP_EN; 12 | 13 | core::arch::asm!( 14 | "out dx, ax", 15 | in("dx") PM1A_CNT_PORT, 16 | in("ax") value, 17 | ); 18 | } 19 | 20 | // Freeze in case of the shutdown failure (no ACPI) 21 | loop { 22 | unsafe { 23 | core::arch::asm!("hlt"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/chat/engine.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | net::ipv4::receive_loop_tcp, 3 | vga::write::{ 4 | string, 5 | } 6 | }; 7 | 8 | const MAX_PEERS: usize = 4; 9 | const MAX_LINE_LEN: usize = 256; 10 | 11 | static mut PEERS: [Option; MAX_PEERS] = [None, None, None, None]; 12 | static mut LINE_BUF: [u8; MAX_LINE_LEN] = [0; MAX_LINE_LEN]; 13 | static mut LINE_LEN: usize = 0; 14 | 15 | struct Peer { 16 | stream: TcpStream, 17 | recv_buf: [u8; MAX_LINE_LEN], 18 | recv_len: usize, 19 | } 20 | 21 | impl Peer { 22 | fn new(stream: TcpStream) -> Self { 23 | Peer { 24 | stream, 25 | recv_buf: [0; MAX_LINE_LEN], 26 | recv_len: 0, 27 | } 28 | } 29 | 30 | fn try_read_line(&mut self) -> Option<&str> { 31 | while let Some(byte) = self.stream.try_read_byte() { 32 | if self.recv_len < MAX_LINE_LEN { 33 | self.recv_buf[self.recv_len] = byte; 34 | self.recv_len += 1; 35 | } 36 | if byte == b'\n' { 37 | let msg = core::str::from_utf8(&self.recv_buf[..self.recv_len]).ok()?; 38 | self.recv_len = 0; 39 | return Some(msg); 40 | } 41 | } 42 | None 43 | } 44 | 45 | fn send_line(&mut self, line: &str) { 46 | let _ = self.stream.write_bytes(line.as_bytes()); 47 | } 48 | } 49 | 50 | fn add_peer(p: Peer) { 51 | for slot in unsafe { PEERS.iter_mut() } { 52 | if slot.is_none() { 53 | *slot = Some(p); 54 | return; 55 | } 56 | } 57 | } 58 | 59 | fn broadcast_line(line: &str) { 60 | for peer in unsafe { PEERS.iter_mut() } { 61 | if let Some(p) = peer { 62 | p.send_line(line); 63 | } 64 | } 65 | print_line(line); 66 | } 67 | 68 | 69 | // 70 | // 71 | // 72 | 73 | fn chat_main(my_port: u16, peer_ips: &[IpAddr]) { 74 | let listener = TcpListener::bind(my_port); 75 | 76 | // Connect to peers 77 | for &ip in peer_ips { 78 | if let Some(stream) = TcpStream::connect(ip, my_port) { 79 | add_peer(Peer::new(stream)); 80 | } 81 | } 82 | 83 | loop { 84 | // Accept new incoming connections 85 | if let Some(new_stream) = listener.accept() { 86 | add_peer(Peer::new(new_stream)); 87 | } 88 | 89 | unsafe { 90 | // Read from keyboard 91 | if let Some(ch) = keyboard_poll_char() { 92 | if ch == '\n' { 93 | let line = unsafe { core::str::from_utf8_unchecked(&LINE_BUF[..LINE_LEN]) }; 94 | broadcast_line(line); 95 | LINE_LEN = 0; 96 | } else if LINE_LEN < MAX_LINE_LEN { 97 | LINE_BUF[LINE_LEN] = ch as u8; 98 | LINE_LEN += 1; 99 | } 100 | } 101 | } 102 | 103 | // Check peers for incoming messages 104 | for peer in unsafe { PEERS.iter_mut() } { 105 | if let Some(p) = peer { 106 | if let Some(msg) = p.try_read_line() { 107 | print_line(msg); 108 | // Optional: relay to others 109 | // broadcast_line(msg); 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /src/app/chat/mod.rs: -------------------------------------------------------------------------------- 1 | //pub mod engine; 2 | pub mod tcp; 3 | -------------------------------------------------------------------------------- /src/app/ether.rs: -------------------------------------------------------------------------------- 1 | use crate::{net::ethernet}; 2 | 3 | pub fn handle_packet() { 4 | fn callback(_packet: &[u8]) -> u8 { 5 | 1 6 | } 7 | if ethernet::receive_loop(callback) == 1 { 8 | println!("Reveived an Ethernet frame!") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/http_udp.rs: -------------------------------------------------------------------------------- 1 | use crate::net::ipv4; 2 | use crate::net::udp; 3 | 4 | pub fn udp_handler(ipv4_header: &ipv4::Ipv4Header, ipv4_payload: &[u8]) -> u8 { 5 | if ipv4_header.protocol != 17 { 6 | return 2; 7 | } 8 | 9 | if let Some((src_port, dst_port, payload)) = udp::parse_packet(ipv4_payload) { 10 | if dst_port == 80 && payload.starts_with(b"GET /") { 11 | let http_response = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from rou2exOS Rusted Edition!"; 12 | 13 | let mut udp_buf = [0u8; 512]; 14 | let udp_len = udp::create_packet( 15 | [192, 168, 3, 2], // Local IP 16 | ipv4_header.source_ip, 17 | dst_port, // From our 80/8080 18 | src_port, // Back to client 19 | http_response, 20 | &mut udp_buf, 21 | ); 22 | 23 | let udp_slice = udp_buf.get(..udp_len).unwrap_or(&[]); 24 | 25 | // Now calculate checksum 26 | let checksum = udp::get_checksum( 27 | [192, 168, 3, 2], // Source IP 28 | [192, 168, 3, 1], // Destination IP 29 | udp_slice, 30 | ); 31 | 32 | // Insert checksum into udp_buf 33 | if let Some(slice) = udp_buf.get_mut(6..8) { 34 | slice.copy_from_slice(&checksum.to_be_bytes()); 35 | } 36 | 37 | let mut ipv4_buf = [0u8; 1500]; 38 | 39 | let udp_slice = udp_buf.get(..udp_len).unwrap_or(&[]); 40 | let ipv4_len = ipv4::create_packet( 41 | [192, 168, 3, 2], 42 | ipv4_header.source_ip, 43 | 17, // UDP 44 | udp_slice, 45 | &mut ipv4_buf, 46 | ); 47 | 48 | // Send the IPv4 packet 49 | let ipv4_slice = ipv4_buf.get(..ipv4_len).unwrap_or(&[]); 50 | ipv4::send_packet(ipv4_slice); 51 | 52 | return 0; 53 | } 54 | } 55 | 2 56 | } 57 | -------------------------------------------------------------------------------- /src/app/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod chat; 2 | pub mod editor; 3 | pub mod ether; 4 | pub mod http_udp; 5 | pub mod snake; 6 | pub mod tcp_handler; 7 | -------------------------------------------------------------------------------- /src/app/snake/level.rs: -------------------------------------------------------------------------------- 1 | use crate::{fs::fat12::{block::Floppy, fs::Filesystem}, init::config::PATH_CLUSTER}; 2 | use super::engine::Point; 3 | 4 | pub const MAX_WALLS: usize = 128; 5 | 6 | pub fn load_level_from_file(filename: &[u8; 7]) -> ([Point; N], usize) { 7 | let mut buf = [0u8; 512]; 8 | let mut walls = [Point { x: 0, y: 0 }; N]; 9 | let mut count = 0; 10 | 11 | let floppy = Floppy::init(); 12 | 13 | if let Ok(fs) = Filesystem::new(&floppy) { 14 | unsafe { 15 | fs.for_each_entry(PATH_CLUSTER, |entry| { 16 | if entry.name[0] == 0x00 || entry.name[0] == 0xE5 || entry.attr & 0x10 != 0 { 17 | return; 18 | } 19 | 20 | if entry.name.starts_with(filename) && entry.ext.starts_with(b"TXT") { 21 | fs.read_file(entry.start_cluster, &mut buf); 22 | } 23 | }); 24 | } 25 | } 26 | 27 | let mut i = 0; 28 | let size = 512; 29 | 30 | while i < size && count < N { 31 | let mut x = 0usize; 32 | let mut y = 0usize; 33 | 34 | // Parse x 35 | while i < size && buf[i] != b',' { 36 | if buf[i].is_ascii_digit() { 37 | x = x * 10 + (buf[i] - b'0') as usize; 38 | } 39 | i += 1; 40 | } 41 | i += 1; // skip ',' 42 | 43 | // Parse y 44 | while i < size && buf[i] != b'\n' { 45 | if buf[i].is_ascii_digit() { 46 | y = y * 10 + (buf[i] - b'0') as usize; 47 | } 48 | i += 1; 49 | } 50 | i += 1; // skip '\n' 51 | 52 | if x < 80 && y < 25 { 53 | walls[count] = Point { x, y }; 54 | count += 1; 55 | } 56 | } 57 | 58 | (walls, count) 59 | } 60 | 61 | pub fn load_level_by_number(level_number: u8) -> ([Point; N], usize) { 62 | let mut filename = *b"LEVEL00"; 63 | 64 | // Convert level number into two-digit ASCII (01 to 99) 65 | let tens = b'0' + (level_number / 10); 66 | let ones = b'0' + (level_number % 10); 67 | filename[5] = tens; 68 | filename[6] = ones; 69 | 70 | load_level_from_file::(&filename) 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/app/snake/menu.rs: -------------------------------------------------------------------------------- 1 | use crate::{input::keyboard::move_cursor, vga::buffer::VGA_BUFFER}; 2 | use crate::input::keyboard::{keyboard_read_scancode}; 3 | use crate::app::snake::{engine::run, score::{load_high_scores_fat12, render_scores_window}}; 4 | 5 | const WIDTH: isize = 80; 6 | // const HEIGHT: isize = 25; 7 | 8 | const KEY_UP: u8 = 0x48; 9 | const KEY_DOWN: u8 = 0x50; 10 | // const KEY_LEFT: u8 = 0x4B; 11 | // const KEY_RIGHT: u8 = 0x4D; 12 | 13 | const KEY_ESC: u8 = 0x01; 14 | const KEY_ENTER: u8 = 0x1C; 15 | 16 | const MENU_WINDOW_X: usize = 26; 17 | const MENU_WINDOW_Y: usize = 6; 18 | const MENU_WINDOW_WIDTH: usize = 27; 19 | const MENU_WINDOW_HEIGHT: usize = 12; 20 | 21 | pub static mut SELECTED: usize = 0; 22 | 23 | pub fn menu_loop() { 24 | move_cursor(30, 0); 25 | clear_screen!(); 26 | 27 | let menu = ["New game", "High scores", "Exit to shell"]; 28 | 29 | draw_window(MENU_WINDOW_X, MENU_WINDOW_Y, MENU_WINDOW_WIDTH, MENU_WINDOW_HEIGHT, Some("Snake")); 30 | 31 | loop { 32 | unsafe { 33 | let scancode = keyboard_read_scancode(); 34 | 35 | match scancode { 36 | KEY_DOWN => { 37 | SELECTED = (SELECTED + 1) % menu.len(); 38 | } 39 | KEY_UP => { 40 | if SELECTED == 0 { 41 | SELECTED = menu.len() - 1; 42 | } else { 43 | SELECTED -= 1; 44 | } 45 | } 46 | KEY_ENTER => { 47 | if handle_enter() { 48 | return; 49 | } 50 | } 51 | KEY_ESC => { 52 | clear_screen!(); 53 | return; 54 | } 55 | _ => {} 56 | } 57 | } 58 | 59 | draw_menu(32, 9, &menu); 60 | } 61 | } 62 | 63 | fn handle_enter() -> bool { 64 | unsafe { 65 | match SELECTED { 66 | 0 => { 67 | clear_screen!(); 68 | run(); 69 | 70 | clear_screen!(); 71 | draw_window(MENU_WINDOW_X, MENU_WINDOW_Y, MENU_WINDOW_WIDTH, MENU_WINDOW_HEIGHT, Some("Snake")); 72 | } 73 | 1 => { 74 | if let Some(scores) = load_high_scores_fat12() { 75 | clear_screen!(); 76 | 77 | SELECTED = 0; 78 | render_scores_window(&scores); 79 | 80 | clear_screen!(); 81 | draw_window(MENU_WINDOW_X, MENU_WINDOW_Y, MENU_WINDOW_WIDTH, MENU_WINDOW_HEIGHT, Some("Snake")); 82 | } 83 | // 84 | } 85 | _ => { 86 | clear_screen!(); 87 | SELECTED = 0; 88 | //move_cursor_index(vga_index); 89 | return true; 90 | } 91 | } 92 | } 93 | false 94 | } 95 | 96 | // Draw the window frame with a title 97 | pub fn draw_window(x: usize, y: usize, width: usize, height: usize, title: Option<&str>) { 98 | let attr = 0x0E; // white on black 99 | 100 | // Corners 101 | write_char(x, y, 0xC9, attr); // ╔ 102 | write_char(x + width - 1, y, 0xBB, attr); // ╗ 103 | write_char(x, y + height - 1, 0xC8, attr); // ╚ 104 | write_char(x + width - 1, y + height - 1, 0xBC, attr); // ╝ 105 | 106 | // Horizontal borders 107 | for i in 1..(width - 1) { 108 | write_char(x + i, y, 0xCD, attr); // ═ 109 | write_char(x + i, y + height - 1, 0xCD, attr); // ═ 110 | } 111 | 112 | // Vertical borders 113 | for i in 1..(height - 1) { 114 | write_char(x, y + i, 0xBA, attr); // ║ 115 | write_char(x + width - 1, y + i, 0xBA, attr); // ║ 116 | } 117 | 118 | // Optional centered title 119 | if let Some(title) = title { 120 | let start = x + (width - title.len()) / 2; 121 | 122 | write_char(start - 2, y, b'[', 0x0E); 123 | write_char(start - 1, y, b' ', 0x0E); 124 | 125 | let mut j = 0; 126 | for (i, byte) in title.bytes().enumerate() { 127 | write_char(start + i, y, byte, 0x0E); // yellow on blue 128 | j += 1; 129 | } 130 | 131 | write_char(start + j, y, b' ', 0x0E); 132 | write_char(start + j + 1, y, b']', 0x0E); 133 | } 134 | } 135 | 136 | // Write a character at (x, y) with a color attribute 137 | fn write_char(x: usize, y: usize, chr: u8, attr: u8) { 138 | let offset = 2 * (y * WIDTH as usize + x); 139 | unsafe { 140 | core::ptr::write_volatile(VGA_BUFFER.add(offset), chr); 141 | core::ptr::write_volatile(VGA_BUFFER.add(offset + 1), attr); 142 | } 143 | } 144 | 145 | pub fn draw_menu(x: usize, y: usize, items: &[&str]) { 146 | for (i, &item) in items.iter().enumerate() { 147 | for (j, byte) in item.bytes().enumerate() { 148 | 149 | unsafe { 150 | // Write selector arrow 151 | if i == SELECTED { 152 | write_char(x - 2, y + i * 2, b'\x1A', 0x0E); // arrow 153 | } else { 154 | write_char(x - 2, y + i * 2, b' ', 0x07); 155 | } 156 | } 157 | 158 | let offset = 2 * ((y + i * 2) * WIDTH as usize + x + j); 159 | unsafe { 160 | core::ptr::write_volatile( 161 | VGA_BUFFER.add(offset), 162 | byte, 163 | ); 164 | core::ptr::write_volatile( 165 | VGA_BUFFER.add(offset + 1), 166 | if i == SELECTED { 167 | 0xE0 // Yellow background, black text 168 | } else { 169 | 0x07 // Light grey on black 170 | }, 171 | ); 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/app/snake/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod engine; 2 | pub mod level; 3 | pub mod menu; 4 | pub mod score; 5 | -------------------------------------------------------------------------------- /src/app/snake/score.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::snake::menu::{draw_menu, draw_window}, fs::fat12::{ 3 | block::Floppy, 4 | fs::Filesystem}, init::config::PATH_CLUSTER, input::keyboard::keyboard_read_scancode 5 | }; 6 | 7 | const HIGH_SCORE_FILE: &[u8; 11] = b"SKSCORE DAT"; 8 | const SCORE_LEN: usize = 5; 9 | 10 | type Error = &'static str; 11 | 12 | pub fn render_scores_window(scores: &[u32; SCORE_LEN]) { 13 | let mut menu: [&str; SCORE_LEN] = [""; SCORE_LEN]; 14 | 15 | static mut BUF0: [u8; 32] = [0u8; 32]; 16 | static mut BUF1: [u8; 32] = [0u8; 32]; 17 | static mut BUF2: [u8; 32] = [0u8; 32]; 18 | static mut BUF3: [u8; 32] = [0u8; 32]; 19 | static mut BUF4: [u8; 32] = [0u8; 32]; 20 | 21 | #[expect(static_mut_refs)] 22 | unsafe { 23 | menu[0] = sprintf_score(b"1. ", &mut BUF0, scores[0]); 24 | menu[1] = sprintf_score(b"2. ", &mut BUF1, scores[1]); 25 | menu[2] = sprintf_score(b"3. ", &mut BUF2, scores[2]); 26 | menu[3] = sprintf_score(b"4. ", &mut BUF3, scores[3]); 27 | menu[4] = sprintf_score(b"5. ", &mut BUF4, scores[4]); 28 | } 29 | 30 | draw_window(25, 5, 30, 15, Some("High Scores")); 31 | draw_menu(31, 8, &menu); 32 | 33 | loop { 34 | let scancode = keyboard_read_scancode(); 35 | 36 | if scancode == 0x01 { 37 | break; 38 | } 39 | } 40 | } 41 | 42 | pub fn save_high_scores_fat12(scores: &[u32; SCORE_LEN]) -> Result<(), Error> { 43 | let floppy = Floppy::init(); 44 | 45 | // Serialize scores as little endian u32 46 | let mut buf = [0u8; 4 * SCORE_LEN]; 47 | 48 | for (i, &score) in scores.iter().enumerate() { 49 | if i >= buf.len() { 50 | break; 51 | } 52 | 53 | buf[i * 4..i * 4 + 4].copy_from_slice(&score.to_le_bytes()); 54 | } 55 | 56 | if let Ok(fs) = Filesystem::new(&floppy) { 57 | //let cluster: u16 = 0; 58 | 59 | unsafe { 60 | fs.write_file(PATH_CLUSTER, HIGH_SCORE_FILE, &buf); 61 | } 62 | } 63 | 64 | Ok(()) 65 | } 66 | 67 | pub fn update_high_scores(score: u32) { 68 | if let Some(mut scores) = load_high_scores_fat12() { 69 | scores.sort_unstable_by(|a, b| b.cmp(a)); 70 | 71 | let mut scores_new = [0u32; 6]; 72 | 73 | scores_new[..5].copy_from_slice(&scores); 74 | 75 | scores_new[SCORE_LEN] = score; 76 | 77 | scores_new.sort_unstable_by(|a, b| b.cmp(a)); 78 | 79 | scores.copy_from_slice(&scores_new[..5]); 80 | 81 | save_high_scores_fat12(&scores).expect("couldn't save highscores"); 82 | } else { 83 | let mut scores = [0u32; SCORE_LEN]; 84 | scores[0] = score; 85 | save_high_scores_fat12(&scores).expect("couldn't save highscores"); 86 | } 87 | } 88 | 89 | pub fn load_high_scores_fat12() -> Option<[u32; SCORE_LEN]> { 90 | let floppy = Floppy::init(); 91 | let mut sector_buf = [0u8; 512]; 92 | 93 | match Filesystem::new(&floppy) { 94 | Ok(fs) => { 95 | let mut cluster: u16 = 0; 96 | 97 | unsafe { 98 | fs.for_each_entry(PATH_CLUSTER, |entry| { 99 | if entry.name[0] == 0x00 || entry.name[0] == 0xE5 || entry.attr & 0x10 != 0 { 100 | return; 101 | } 102 | 103 | if entry.name.starts_with(b"SKSCORE") && entry.ext.starts_with(b"DAT") { 104 | cluster = entry.start_cluster; 105 | } 106 | }); 107 | } 108 | 109 | if cluster == 0 { 110 | return None; 111 | } 112 | 113 | fs.read_file(cluster, &mut sector_buf); 114 | } 115 | Err(_) => return None 116 | } 117 | 118 | parse_scores_from_sector(§or_buf) 119 | } 120 | 121 | pub fn parse_scores_from_sector(sector_buf: &[u8; 512]) -> Option<[u32; 5]> { 122 | let mut scores = [0u32; SCORE_LEN]; 123 | 124 | // Manually extract 5 * 4 = 20 bytes without causing bounds checks 125 | for i in 0..SCORE_LEN - 1 { 126 | let offset = i * 4; 127 | 128 | // Bounds check manually before copying 129 | if offset + 4 > sector_buf.len() { 130 | return None; 131 | } 132 | 133 | // Copy the 4 bytes manually without panic 134 | let mut b = [0u8; 4]; 135 | for j in 0..3 { 136 | if let Some(byte) = sector_buf.get(offset + j) { 137 | if let Some(sl) = b.get_mut(j) { 138 | *sl = *byte; 139 | } 140 | } 141 | } 142 | 143 | if i >= scores.len() { 144 | break; 145 | } 146 | 147 | if let Some(sc) = scores.get_mut(i) { 148 | *sc = u32::from_le_bytes(b); 149 | } 150 | } 151 | 152 | Some(scores) 153 | } 154 | 155 | pub fn sprintf_score<'a>(prefix: &'static [u8], buf: &'a mut [u8], score: u32) -> &'a str { 156 | let mut i = 0; 157 | 158 | for &b in prefix { 159 | if i < buf.len() { 160 | if let Some(bf) = buf.get_mut(i) { 161 | *bf = b; 162 | } 163 | i += 1; 164 | } 165 | } 166 | 167 | let mut num = score; 168 | if num == 0 { 169 | if i < buf.len() { 170 | if let Some(bf) = buf.get_mut(i) { 171 | *bf = b'0' 172 | } 173 | i += 1; 174 | } 175 | } else { 176 | let mut temp = [0u8; 10]; 177 | let mut j = 0; 178 | while num > 0 && j < temp.len() { 179 | if let Some(tmp) = temp.get_mut(j) { 180 | *tmp = b'0' + (num % 10) as u8; 181 | } 182 | num /= 10; 183 | j += 1; 184 | } 185 | for k in (0..j).rev() { 186 | if i < buf.len() { 187 | if let Some(bf) = buf.get_mut(i) { 188 | if let Some(tmp) = temp.get(k) { 189 | *bf = *tmp 190 | } 191 | } 192 | i += 1; 193 | } 194 | } 195 | } 196 | 197 | if i >= buf.len() { 198 | return ""; 199 | } 200 | 201 | // Safety: we only use ASCII bytes, so this is always valid UTF-8. 202 | unsafe { core::str::from_utf8_unchecked(&buf[..i]) } 203 | } 204 | 205 | -------------------------------------------------------------------------------- /src/audio/beep.rs: -------------------------------------------------------------------------------- 1 | use core; 2 | 3 | unsafe fn outb(port: u16, value: u8) { 4 | core::arch::asm!("out dx, al", in("dx") port, in("al") value); 5 | } 6 | 7 | unsafe fn inb(port: u16) -> u8 { 8 | let val: u8; 9 | core::arch::asm!("in al, dx", in("dx") port, out("al") val); 10 | val 11 | } 12 | 13 | pub fn beep(freq: u32) { 14 | let divisor = 1_193_180 / freq; 15 | 16 | unsafe { 17 | outb(0x43, 0b10110110); 18 | outb(0x42, (divisor & 0xFF) as u8); 19 | outb(0x42, (divisor >> 8) as u8); 20 | } 21 | 22 | unsafe { 23 | // Enable speaker (bits 0 and 1 on port 0x61) 24 | core::arch::asm!( 25 | "in al, dx", 26 | "or al, 3", 27 | "out dx, al", 28 | in("dx") 0x61, 29 | out("al") _, 30 | ); 31 | } 32 | } 33 | 34 | pub fn stop_beep() { 35 | unsafe { 36 | core::arch::asm!( 37 | "in al, dx", 38 | "and al, 0xFC", // clear bits 0 and 1 39 | "out dx, al", 40 | in("dx") 0x61, 41 | out("al") _, 42 | ); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/audio/midi.rs: -------------------------------------------------------------------------------- 1 | use super::beep::{beep, stop_beep}; 2 | use crate::fs::fat12; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct MidiEvent { 6 | pub note: u8, 7 | pub duration: u16, 8 | } 9 | 10 | pub struct MidiFile<'a> { 11 | pub division: u16, 12 | pub events: [MidiEvent; 256], 13 | pub event_count: usize, 14 | pub raw: &'a [u8], 15 | } 16 | 17 | pub fn wait_millis(ms: u16) { 18 | for _ in 0..(ms as u32 * 75_000) { 19 | unsafe { core::arch::asm!("nop") }; 20 | } 21 | } 22 | 23 | pub static MIDI_FREQ_TABLE: [u16; 128] = [ 24 | 8, 9, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, // 0–11 25 | 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, // 12–23 26 | 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, // 24–35 27 | 73, 78, 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, // 36–47 28 | 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, // 48–59 29 | 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, // 60–71 30 | 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, // 72–83 31 | 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, // 84–95 32 | 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, // 96–107 33 | 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7458, 7902, 8372, 8869, // 108–119 34 | 9397, 9956, 10548, 11175, 11840, 12544, 13290, 14080 // 120–127 35 | ]; 36 | 37 | pub fn midi_note_to_freq(note: u8) -> u16 { 38 | MIDI_FREQ_TABLE[note as usize] 39 | } 40 | 41 | pub static TEST_MELODY: &[(u8, u16)] = &[ 42 | (69, 200), // A4 - 440 Hz 43 | (0, 50), 44 | (71, 200), // B4 45 | (0, 50), 46 | (72, 200), // C5 47 | (0, 50), 48 | (74, 200), // D5 49 | (0, 50), 50 | (76, 400), // E5 51 | ]; 52 | 53 | pub fn play_melody() { 54 | for &(note, duration) in TEST_MELODY.iter() { 55 | stop_beep(); 56 | 57 | if note != 0 { 58 | let freq = midi_note_to_freq(note); 59 | beep(freq as u32); 60 | } 61 | 62 | wait_millis(duration); 63 | } 64 | 65 | stop_beep(); 66 | } 67 | 68 | fn read_varlen(data: &[u8]) -> (u32, usize) { 69 | let mut result = 0; 70 | let mut i = 0; 71 | loop { 72 | let byte = data[i]; 73 | result = (result << 7) | (byte & 0x7F) as u32; 74 | i += 1; 75 | if byte & 0x80 == 0 { 76 | break; 77 | } 78 | } 79 | (result, i) 80 | } 81 | 82 | pub fn parse_midi_format0(data: &'_ [u8]) -> Option> { 83 | if &data[0..4] != b"MThd" { return None; } 84 | 85 | let header_len = u32::from_be_bytes([data[4], data[5], data[6], data[7]]); 86 | if header_len != 6 { return None; } 87 | 88 | let format = u16::from_be_bytes([data[8], data[9]]); 89 | if format != 0 { return None; } 90 | 91 | let num_tracks = u16::from_be_bytes([data[10], data[11]]); 92 | if num_tracks != 1 { return None; } 93 | 94 | let division = u16::from_be_bytes([data[12], data[13]]); 95 | let mut pos = 14; 96 | 97 | if &data[pos..pos + 4] != b"MTrk" { return None; } 98 | let track_len = u32::from_be_bytes([data[pos+4], data[pos+5], data[pos+6], data[pos+7]]) as usize; 99 | pos += 8; 100 | 101 | let track_end = pos + track_len; 102 | let mut last_status = 0u8; 103 | let mut output = [MidiEvent { note: 0, duration: 0 }; 256]; 104 | let mut out_idx = 0; 105 | 106 | while pos < track_end && out_idx < output.len() { 107 | if pos >= data.len() { 108 | break; 109 | } 110 | 111 | // Delta time 112 | let (_delta, delta_len) = read_varlen(&data[pos..]); 113 | pos += delta_len; 114 | 115 | // Event type 116 | let mut status = data[pos]; 117 | if status < 0x80 { 118 | status = last_status; 119 | } else { 120 | pos += 1; 121 | last_status = status; 122 | } 123 | 124 | if status & 0xF0 == 0x90 { 125 | // Note On 126 | let note = data[pos]; 127 | let velocity = data[pos + 1]; 128 | pos += 2; 129 | 130 | if velocity > 0 { 131 | let duration_ticks = 240; 132 | let ms = ticks_to_ms(duration_ticks, division); 133 | output[out_idx] = MidiEvent { note, duration: ms }; 134 | out_idx += 1; 135 | } 136 | } else if status & 0xF0 == 0x80 { 137 | // Note Off — ignore 138 | pos += 2; 139 | } else if status == 0xFF { 140 | // Meta event 141 | // let meta_type = data[pos]; 142 | let (len, len_len) = read_varlen(&data[pos + 1..]); 143 | pos += 1 + len_len + len as usize; 144 | } else { 145 | pos += 2; 146 | } 147 | } 148 | 149 | Some(MidiFile { 150 | division, 151 | events: output, 152 | event_count: out_idx, 153 | raw: data, 154 | }) 155 | } 156 | 157 | fn ticks_to_ms(ticks: u32, division: u16) -> u16 { 158 | let micros = (ticks as u64 * 500_000) / division as u64; 159 | (micros / 1000) as u16 160 | } 161 | 162 | pub fn play_midi(midi: &MidiFile) { 163 | for i in 0..midi.event_count { 164 | let e = midi.events[i]; 165 | super::beep::stop_beep(); 166 | if e.note > 0 { 167 | let freq = super::midi::midi_note_to_freq(e.note); 168 | super::beep::beep(freq as u32); 169 | } 170 | super::midi::wait_millis(e.duration); 171 | } 172 | super::beep::stop_beep(); 173 | } 174 | 175 | pub fn play_midi_file() { 176 | if let Some(data) = read_file() { 177 | if let Some(midi) = parse_midi_format0(&data) { 178 | play_midi(&midi); 179 | } else { 180 | println!("Invalid MIDI file"); 181 | } 182 | } else { 183 | println!("Could not read file"); 184 | } 185 | } 186 | 187 | fn read_file() -> Option<[u8; 4096]> { 188 | let floppy = fat12::block::Floppy::init(); 189 | 190 | let mut buf = [0; 4096]; 191 | 192 | if let Ok(fs) = fat12::fs::Filesystem::new(&floppy) { 193 | fs.for_each_entry(0, | entry | { 194 | if entry.ext.starts_with(b"MID") { 195 | fs.read_file(entry.start_cluster, &mut buf); 196 | } 197 | }); 198 | } 199 | 200 | Some(buf) 201 | } 202 | 203 | -------------------------------------------------------------------------------- /src/audio/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod beep; 2 | pub mod midi; 3 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Write}; 2 | use spin::Mutex; 3 | use crate::{clear_screen, error, printb}; 4 | 5 | const DEBUG_LOG_SIZE: usize = 8192; 6 | 7 | pub struct DebugLog { 8 | buffer: [u8; DEBUG_LOG_SIZE], 9 | len: usize, 10 | } 11 | 12 | pub static DEBUG_LOG: Mutex = Mutex::new(DebugLog::new()); 13 | 14 | impl DebugLog { 15 | pub const fn new() -> Self { 16 | Self { 17 | buffer: [0; DEBUG_LOG_SIZE], 18 | len: 0, 19 | } 20 | } 21 | 22 | pub fn clear(&mut self) { 23 | self.len = 0; 24 | } 25 | 26 | pub fn data(&self) -> &[u8] { 27 | if let Some(d) = self.buffer.get(..self.len) { 28 | return d; 29 | } 30 | 31 | &[] 32 | } 33 | 34 | pub fn append(&mut self, data: &[u8]) { 35 | let remaining = DEBUG_LOG_SIZE - self.len; 36 | let to_copy = core::cmp::min(data.len(), remaining); 37 | 38 | 39 | if let Some(slice) = self.buffer.get_mut(self.len..self.len + to_copy) { 40 | if let Some(data) = data.get(..to_copy) { 41 | slice.copy_from_slice(data); 42 | } 43 | } 44 | 45 | self.len += to_copy; 46 | } 47 | } 48 | 49 | impl Write for DebugLog { 50 | fn write_str(&mut self, s: &str) -> fmt::Result { 51 | self.append(s.as_bytes()); 52 | Ok(()) 53 | } 54 | } 55 | 56 | pub fn u64_to_dec_str(mut n: u64, buf: &mut [u8; 20]) -> &[u8] { 57 | if n == 0 { 58 | buf[0] = b'0'; 59 | return &buf[..1]; 60 | } 61 | let mut i = 20; 62 | while n > 0 && i > 0 { 63 | i -= 1; 64 | buf[i] = b'0' + (n % 10) as u8; 65 | n /= 10; 66 | } 67 | &buf[i..] 68 | } 69 | 70 | #[macro_export] 71 | macro_rules! debugn { 72 | ($n:expr) => {{ 73 | if let Some(mut log) = $crate::debug::DEBUG_LOG.try_lock() { 74 | let mut buf = [0u8; 20]; 75 | let s = $crate::debug::u64_to_dec_str($n as u64, &mut buf); 76 | log.append(s); 77 | } 78 | }}; 79 | } 80 | 81 | #[macro_export] 82 | macro_rules! debug { 83 | ($s:expr) => {{ 84 | if let Some(mut log) = $crate::debug::DEBUG_LOG.try_lock() { 85 | // Only &[u8], *str and b"literal" 86 | let bytes = ($s).as_ref(); 87 | log.append(bytes); 88 | } 89 | }}; 90 | } 91 | 92 | #[macro_export] 93 | macro_rules! debugln { 94 | ($s:expr) => {{ 95 | $crate::debug!($s); 96 | $crate::debug!("\n"); 97 | }}; 98 | } 99 | 100 | #[macro_export] 101 | macro_rules! rprintn { 102 | ($n:expr) => {{ 103 | let mut buf = [0u8; 20]; 104 | let s = $crate::debug::u64_to_dec_str($n as u64, &mut buf); 105 | 106 | for b in s { 107 | $crate::net::serial::write(*b); 108 | } 109 | }}; 110 | } 111 | 112 | #[macro_export] 113 | macro_rules! rprintb { 114 | ($data:expr) => { 115 | for b in $data { 116 | $crate::net::serial::write(*b); 117 | } 118 | }; 119 | } 120 | 121 | #[macro_export] 122 | macro_rules! rprint { 123 | ($data:expr) => { 124 | //serial::init(); 125 | 126 | for b in $data.as_bytes() { 127 | $crate::net::serial::write(*b); 128 | } 129 | }; 130 | } 131 | 132 | #[macro_export] 133 | macro_rules! kprint { 134 | ($buf:expr, $off:expr, $str:expr) => { 135 | let len = $buf.len(); 136 | 137 | if *$off >= len || *$off + $str.len() >= len { 138 | return; 139 | } 140 | 141 | if let Some(slice) = $buf.get_mut(*$off..*$off + $str.len()) { 142 | slice.copy_from_slice($str); 143 | *$off += $str.len(); 144 | } 145 | }; 146 | } 147 | 148 | use crate::fs::fat12::{block::Floppy, fs::Filesystem}; 149 | 150 | pub fn dump_debug_log_to_file() { 151 | let dbg = DEBUG_LOG.lock(); 152 | 153 | let floppy = Floppy; 154 | 155 | // Dump log to display 156 | clear_screen!(); 157 | printb!(dbg.data()); 158 | 159 | match Filesystem::new(&floppy) { 160 | Ok(fs) => { 161 | // Dump debug data into the DEBUG.TXT file in root directory 162 | fs.write_file(0, b"DEBUG TXT", dbg.data()); 163 | } 164 | Err(e) => { 165 | debugln!(e); 166 | error!(e); 167 | } 168 | } 169 | 170 | use crate::net::serial; 171 | 172 | serial::init(); 173 | 174 | for b in dbg.data() { 175 | serial::write(*b); 176 | } 177 | } 178 | 179 | fn print_stack_info() { 180 | let sp: usize; 181 | unsafe { 182 | core::arch::asm!("mov {}, rsp", out(reg) sp); 183 | 184 | debug!("Stack pointer: "); 185 | debugn!(sp as u64); 186 | debugln!(""); 187 | } 188 | } 189 | 190 | -------------------------------------------------------------------------------- /src/fs/fat12/check.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::fat12::{fs::Filesystem, table::FatTable, block::Floppy}; 2 | 3 | pub struct CheckReport { 4 | pub errors: usize, 5 | pub orphan_clusters: usize, 6 | pub cross_linked: usize, 7 | pub invalid_entries: usize, 8 | } 9 | 10 | pub fn run_check() -> CheckReport { 11 | let fat = FatTable::load(); 12 | let mut report = CheckReport { 13 | errors: 0, 14 | orphan_clusters: 0, 15 | cross_linked: 0, 16 | invalid_entries: 0, 17 | }; 18 | 19 | let mut used_clusters = [false; 4096]; // FAT12 max: 0xFF4 (4084 entries) 20 | 21 | scan_directory(0, &fat, &mut used_clusters, &mut report, 0); 22 | 23 | // Check the FAT for unreferenced or multiply referenced clusters 24 | report.orphan_clusters += used_clusters.iter().zip((0..).map(|cluster| fat.get(cluster))).skip(2).filter_map(|x| if !x.0 { x.1 } else {None}).count(); 25 | 26 | /*print!("Done. Error count: "); 27 | printn!(report.errors as u64); 28 | print!(", Orphan clusters: "); 29 | printn!(report.orphan_clusters as u64); 30 | print!(", Cross-linked: "); 31 | printn!(report.cross_linked as u64); 32 | print!(", Invalid entries: "); 33 | printn!(report.invalid_entries as u64); 34 | println!(); 35 | println!();*/ 36 | 37 | report 38 | } 39 | 40 | fn scan_directory( 41 | start_cluster: u16, 42 | fat: &FatTable, 43 | used: &mut [bool; 4096], 44 | report: &mut CheckReport, 45 | depth: usize, 46 | ) { 47 | if depth > 64 { 48 | error!(" -> Recursion depth exceeded"); 49 | println!(); 50 | 51 | report.errors += 1; 52 | return; 53 | } 54 | 55 | let floppy = Floppy::init(); 56 | 57 | if let Ok(fs) = Filesystem::new(&floppy) { 58 | fs.for_each_entry(start_cluster, |entry| { 59 | if entry.name[0] == 0x00 || entry.name[0] == 0xE5 { 60 | //string(vga_index, b" -> Invalid entry: ", Color::Red); 61 | //number(vga_index, entry.start_cluster as u64); 62 | //newline(vga_index); 63 | report.invalid_entries += 1; 64 | return; 65 | } 66 | 67 | if entry.attr & 0x08 != 0 { 68 | // Volume label 69 | return; 70 | } 71 | 72 | if entry.attr & 0x10 != 0 && entry.file_size != 0 { 73 | error!(" -> Directory has non-zero value: "); 74 | printn!(entry.start_cluster as u64); 75 | println!(); 76 | 77 | report.errors += 1; 78 | } 79 | 80 | if entry.start_cluster < 2 { 81 | warn!(" -> Warning: entry with cluster < 2: "); 82 | printn!(entry.start_cluster as u64); 83 | println!(); 84 | return; 85 | } 86 | 87 | if entry.start_cluster == start_cluster { 88 | warn!(" -> Skipping self-recursive directory"); 89 | println!(); 90 | return; 91 | } 92 | 93 | let is_dotdot = entry.name.starts_with(b".."); 94 | if is_dotdot { 95 | return; 96 | } 97 | 98 | if entry.attr & 0x10 != 0 { 99 | scan_directory(entry.start_cluster, fat, used, report, depth + 1); 100 | } else { 101 | validate_chain(entry.start_cluster, fat, used, report); 102 | } 103 | }); 104 | } 105 | } 106 | 107 | fn is_valid_name(name: &[u8; 11]) -> bool { 108 | name.iter().all(|&c| { 109 | c.is_ascii_uppercase() || 110 | c.is_ascii_digit() || 111 | c == b' ' || b"!#$%&'()-@^_`{}~".contains(&c) 112 | }) 113 | } 114 | 115 | pub fn validate_chain( 116 | start: u16, 117 | fat: &FatTable, 118 | used: &mut [bool; 4096], 119 | report: &mut CheckReport, 120 | ) { 121 | let mut cluster = start; 122 | 123 | while fat.is_valid_cluster(cluster) && !fat.is_end_of_chain(cluster) { 124 | if cluster as usize >= used.len() { 125 | error!(" -> Cluster out of bounds: "); 126 | printn!(cluster as u64); 127 | println!(); 128 | 129 | report.errors += 1; 130 | return; 131 | } 132 | 133 | if used[cluster as usize] { 134 | error!(" -> Cross-linked cluster: "); 135 | printn!(cluster as u64); 136 | 137 | report.cross_linked += 1; 138 | return; 139 | } 140 | 141 | used[cluster as usize] = true; 142 | 143 | match fat.next_cluster(cluster) { 144 | Some(next) if next >= 0xFF8 => break, 145 | Some(next) => cluster = next, 146 | None => { 147 | error!(" -> Invalid chain entry"); 148 | println!(); 149 | 150 | report.errors += 1; 151 | break; 152 | } 153 | } 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /src/fs/fat12/entry.rs: -------------------------------------------------------------------------------- 1 | #[repr(C, packed)] 2 | #[derive(Clone)] 3 | pub struct BootSector { 4 | pub jmp: [u8; 3], 5 | pub oem: [u8; 8], 6 | pub bytes_per_sector: u16, 7 | pub sectors_per_cluster: u8, 8 | pub reserved_sectors: u16, 9 | pub fat_count: u8, 10 | pub root_entry_count: u16, 11 | pub total_sectors_16: u16, 12 | pub media: u8, 13 | pub fat_size_16: u16, 14 | pub sectors_per_track: u16, 15 | pub num_heads: u16, 16 | pub hidden_sectors: u32, 17 | pub total_sectors_32: u32, 18 | } 19 | 20 | #[repr(C, packed)] 21 | #[derive(Default,Copy,Clone)] 22 | pub struct Entry { 23 | pub name: [u8; 8], // "FILE " 24 | pub ext: [u8; 3], // "TXT" 25 | pub attr: u8, 26 | pub reserved: u8, 27 | pub create_time_tenths: u8, 28 | pub create_time: u16, 29 | pub create_date: u16, 30 | pub last_access_date: u16, 31 | pub high_cluster: u16, // ignored in FAT16 32 | pub write_time: u16, 33 | pub write_date: u16, 34 | pub start_cluster: u16, 35 | pub file_size: u32, 36 | } 37 | 38 | impl Entry { 39 | pub fn to_bytes(e: &Entry) -> [u8; 32] { 40 | let mut b = [0u8; 32]; 41 | 42 | b[0..8].copy_from_slice(&e.name); 43 | b[8..11].copy_from_slice(&e.ext); 44 | b[11] = e.attr; 45 | b[12] = e.reserved; 46 | b[13] = e.create_time_tenths; 47 | b[14..16].copy_from_slice(&e.create_time.to_le_bytes()); 48 | b[16..18].copy_from_slice(&e.create_date.to_le_bytes()); 49 | b[18..20].copy_from_slice(&e.last_access_date.to_le_bytes()); 50 | b[20..22].copy_from_slice(&e.high_cluster.to_le_bytes()); 51 | b[22..24].copy_from_slice(&e.write_time.to_le_bytes()); 52 | b[24..26].copy_from_slice(&e.write_date.to_le_bytes()); 53 | b[26..28].copy_from_slice(&e.start_cluster.to_le_bytes()); 54 | b[28..32].copy_from_slice(&e.file_size.to_le_bytes()); 55 | 56 | b 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/fs/fat12/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod block; 2 | pub mod check; 3 | pub mod entry; 4 | pub mod fs; 5 | pub mod table; 6 | -------------------------------------------------------------------------------- /src/fs/fat12/table.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::fat12::{block::{BlockDevice, Floppy}, fs::Filesystem}; 2 | 3 | /// The number of bytes in a sector 4 | const BYTES_PER_SECTOR: usize = 512; 5 | 6 | /// The size of the FAT table in sectors (for a 1.44MB floppy, usually 9) 7 | const FAT_SECTORS: usize = 9; 8 | 9 | /// The offset (in sectors) where the FAT starts (after boot sector) 10 | const FAT_START_SECTOR: u16 = 1; 11 | 12 | /// Total number of clusters in FAT12 (maximum 4084) 13 | const MAX_CLUSTERS: usize = 4085; 14 | 15 | /// End-of-chain marker for FAT12 (>= 0xFF8) 16 | const FAT12_EOC_MIN: u16 = 0x0FF8; 17 | 18 | pub struct FatTable { 19 | /// Raw FAT bytes (only the first copy for now) 20 | data: [u8; FAT_SECTORS * BYTES_PER_SECTOR], 21 | } 22 | 23 | impl FatTable { 24 | pub fn load() -> Self { 25 | let mut data = [0u8; FAT_SECTORS * BYTES_PER_SECTOR]; 26 | 27 | let floppy = Floppy; 28 | let mut buf: [u8; 512] = [0u8; BYTES_PER_SECTOR]; 29 | 30 | match Filesystem::new(&floppy) { 31 | Ok(fs) => { 32 | for i in 0..FAT_SECTORS { 33 | 34 | //fs.device.read_sector(FAT_START_SECTOR + i as u16, &mut data[i * BYTES_PER_SECTOR..][..BYTES_PER_SECTOR], vga_index); 35 | fs.device.read_sector((FAT_START_SECTOR + i as u16) as u64, &mut buf); 36 | data[i * BYTES_PER_SECTOR..(i + 1) * BYTES_PER_SECTOR].copy_from_slice(&buf); 37 | } 38 | Self { data } 39 | } 40 | Err(_) => { 41 | Self { data: [0u8; FAT_SECTORS * BYTES_PER_SECTOR] } 42 | } 43 | } 44 | 45 | } 46 | 47 | /// Get the next cluster in the chain 48 | /// Returns `None` for free cluster or end-of-chain 49 | pub fn get(&self, cluster: u16) -> Option { 50 | if cluster < 2 || cluster >= MAX_CLUSTERS as u16 { 51 | return None; 52 | } 53 | 54 | let index = (cluster as usize * 3) / 2; 55 | let b1 = self.data.get(index).copied().unwrap_or(0); 56 | let b2 = self.data.get(index + 1).copied().unwrap_or(0); 57 | let raw = if cluster & 1 == 0 { 58 | ((b2 as u16 & 0x0F) << 8) | b1 as u16 59 | } else { 60 | ((b2 as u16) << 4) | ((b1 as u16 & 0xF0) >> 4) 61 | }; 62 | 63 | if raw >= FAT12_EOC_MIN { 64 | None 65 | } else { 66 | Some(raw) 67 | } 68 | } 69 | 70 | /// Follow a cluster chain until end-of-chain or loop 71 | pub fn follow_chain_array(&self, start: u16) -> (usize, [u16; MAX_CLUSTERS]) { 72 | let mut result = [0u16; MAX_CLUSTERS]; 73 | let mut len = 0; 74 | let mut current = start; 75 | 76 | while let Some(next) = self.get(current) { 77 | // Skip cluster 0 and 1 which are reserved in FAT12 78 | if current < 2 || current >= MAX_CLUSTERS as u16 { 79 | break; 80 | } 81 | 82 | // Loop protection 83 | if result[..len].contains(¤t) { 84 | break; 85 | } 86 | 87 | result[len] = current; 88 | len += 1; 89 | 90 | if len >= MAX_CLUSTERS { 91 | break; 92 | } 93 | 94 | current = next; 95 | } 96 | 97 | // Include last if valid and not already included 98 | if current >= 2 && !result[..len].contains(¤t) && len < MAX_CLUSTERS { 99 | result[len] = current; 100 | len += 1; 101 | } 102 | 103 | (len, result) 104 | } 105 | 106 | pub fn total_clusters(&self) -> usize { 107 | MAX_CLUSTERS 108 | } 109 | 110 | pub fn next_cluster(&self, cluster: u16) -> Option { 111 | if !(2..0xFF8).contains(&cluster) { 112 | return None; 113 | } 114 | 115 | let offset = (cluster as usize * 3) / 2; 116 | if offset + 1 >= self.data.len() { 117 | return None; 118 | } 119 | 120 | let val = if cluster & 1 == 0 { 121 | // Even cluster 122 | ((self.data[offset] as u16) | ((self.data[offset + 1] as u16) << 8)) & 0x0FFF 123 | } else { 124 | // Odd cluster 125 | ((self.data[offset] as u16) >> 4 | ((self.data[offset + 1] as u16) << 4)) & 0x0FFF 126 | }; 127 | 128 | Some(val) 129 | } 130 | 131 | pub fn is_valid_cluster(&self, cluster: u16) -> bool { 132 | (2..=0xFEF).contains(&cluster) 133 | } 134 | 135 | pub fn is_end_of_chain(&self, cluster: u16) -> bool { 136 | (0xFF8..=0xFFF).contains(&cluster) 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /src/fs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod fat12; 2 | -------------------------------------------------------------------------------- /src/init/ascii.rs: -------------------------------------------------------------------------------- 1 | use crate::video::vga::Color; 2 | 3 | pub fn ascii_art() { 4 | print!(" ____ ___ _____ \n", Color::Green); 5 | print!(" _ __ ___ _ _|___ \\ _____ __/ _ \\/ ____| \n", Color::Green); 6 | print!("| '__/ _ \\| | | | __) / _ \\ \\/ / | | \\___ \\\n", Color::Green); 7 | print!("| | | (_) | |_| |/ __/ __/> <| |_| |___) |\n", Color::Green); 8 | print!("|_| \\___/ \\__,_|_____\\___/_/\\_\\____/|____/\n\n", Color::Green); 9 | 10 | // Set the fg color back to white on return 11 | print!("", Color::White); 12 | } 13 | -------------------------------------------------------------------------------- /src/init/color.rs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | use crate::video::vga::Color; 4 | 5 | pub fn color_demo() { 6 | let colors: [u8; 16] = [ 7 | 0x0, 0x1, 0x2, 0x3, 8 | 0x4, 0x5, 0x6, 0x7, 9 | 0x8, 0x9, 0xA, 0xB, 10 | 0xC, 0xD, 0xE, 0xF, 11 | ]; 12 | 13 | print!("Color test:\n"); 14 | 15 | let mut col = 0; 16 | for &color in colors.iter() { 17 | if col % 8 == 0 { 18 | // Render new row of colours 19 | print!("\n"); 20 | col = 0; 21 | } 22 | 23 | print!(" ", Color::Black, color); 24 | print!(" ", Color::Black, Color::Black); 25 | 26 | col += 1; 27 | } 28 | 29 | print!("\n\n"); 30 | } 31 | -------------------------------------------------------------------------------- /src/init/config.rs: -------------------------------------------------------------------------------- 1 | pub const USER: &[u8] = b"guest"; 2 | pub const HOST: &[u8] = b"rou2ex"; 3 | 4 | pub static mut PATH: &[u8] = b"/"; 5 | pub static mut PATH_CLUSTER: u16 = 0; 6 | 7 | pub static mut PATH_BUF: [u8; 256] = [0u8; 256]; 8 | pub static mut PATH_LEN: usize = 1; 9 | 10 | pub fn set_path(new_path: &[u8]) { 11 | unsafe { 12 | let len = new_path.len().min(256); 13 | PATH_BUF[..len].copy_from_slice(&new_path[..len]); 14 | PATH_LEN = len; 15 | } 16 | } 17 | 18 | pub fn get_path() -> &'static [u8] { 19 | unsafe { 20 | core::slice::from_raw_parts((&raw const PATH_BUF).cast(), PATH_LEN) 21 | } 22 | } 23 | 24 | // 25 | // 26 | // 27 | 28 | extern "C" { 29 | pub static mut p4_table: [u64; 512]; 30 | } 31 | 32 | extern "C" { 33 | pub static p3_table: u64; 34 | } 35 | 36 | extern "C" { 37 | pub static p2_table: u64; 38 | } 39 | 40 | extern "C" { 41 | pub static mut p3_fb_table: [u64; 512]; 42 | pub static mut p2_fb_table: [u64; 512]; 43 | pub static mut p1_fb_table: [u64; 512]; 44 | pub static mut p1_fb_table_2: [u64; 512]; 45 | } 46 | 47 | extern "C" { 48 | pub static multiboot_ptr: u32; 49 | } 50 | 51 | extern "C" { 52 | static debug_flag: u8; 53 | } 54 | 55 | pub fn debug_enabled() -> bool { 56 | unsafe { 57 | debug_flag != 0 58 | } 59 | } 60 | 61 | extern "C" { 62 | static __stack_start: u8; 63 | static __stack_end: u8; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/init/cpu.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use super::result; 3 | 4 | pub fn check_mode() -> crate::init::result::InitResult { 5 | let mode = check_cpu_mode(); 6 | 7 | enable_sse(); 8 | enable_syscalls(); 9 | 10 | if mode.len() > 5 && mode.as_bytes()[0..4] == *b"Long" { 11 | return result::InitResult::Passed; 12 | } 13 | 14 | result::InitResult::Failed 15 | } 16 | 17 | fn enable_sse() { 18 | unsafe { 19 | // Enable SSE + FXSR in CR4 20 | let mut cr4: u64; 21 | asm!("mov {}, cr4", out(reg) cr4); 22 | cr4 |= (1 << 9) | (1 << 10); // OSFXSR (bit 9), OSXMMEXCPT (bit 10) 23 | asm!("mov cr4, {}", in(reg) cr4); 24 | 25 | // Clear EM (bit 2), Set MP (bit 1) in CR0 26 | let mut cr0: u64; 27 | asm!("mov {}, cr0", out(reg) cr0); 28 | cr0 &= !(1 << 2); // Clear EM (disable emulation) 29 | cr0 |= 1 << 1; // Set MP (monitor co-processor) 30 | asm!("mov cr0, {}", in(reg) cr0); 31 | } 32 | } 33 | 34 | /// Function to check CPU mode using CPUID instruction 35 | fn check_cpu_mode() -> &'static str { 36 | let cpuid_supported = cpuid(0x1); 37 | 38 | if cpuid_supported == 0 { 39 | return "Real Mode (CPUID not supported)"; 40 | } 41 | 42 | let cpuid_value = cpuid(0x80000000); 43 | 44 | // Check for 64-bit long mode (if CPUID supports extended functions) 45 | if cpuid_value >= 0x80000001 { 46 | return "Long Mode (64-bit mode)"; 47 | } 48 | 49 | // Otherwise, it is protected mode 50 | "Protected Mode (32-bit)" 51 | } 52 | 53 | /// Inline assembly function to execute CPUID 54 | fn cpuid(eax: u32) -> u32 { 55 | let result: u32; 56 | unsafe { 57 | asm!( 58 | "cpuid", 59 | // Store eax into result 60 | inout("eax") eax => result, 61 | out("ecx") _, 62 | out("edx") _, 63 | ); 64 | } 65 | result 66 | } 67 | 68 | // 69 | // CPU SYSCALL 70 | // 71 | 72 | const IA32_EFER: u32 = 0xC0000080; 73 | const IA32_LSTAR: u32 = 0xC0000082; 74 | 75 | unsafe fn wrmsr(msr: u32, value: u64) { 76 | let low = value as u32; 77 | let high = (value >> 32) as u32; 78 | asm!( 79 | "wrmsr", 80 | in("ecx") msr, 81 | in("eax") low, 82 | in("edx") high, 83 | options(nostack, preserves_flags) 84 | ); 85 | } 86 | 87 | unsafe fn rdmsr(msr: u32) -> u64 { 88 | let low: u32; 89 | let high: u32; 90 | asm!( 91 | "rdmsr", 92 | in("ecx") msr, 93 | out("eax") low, 94 | out("edx") high, 95 | options(nostack, preserves_flags) 96 | ); 97 | ((high as u64) << 32) | (low as u64) 98 | } 99 | 100 | // Your syscall handler (just returns for now) 101 | #[unsafe(naked)] 102 | unsafe extern "C" fn syscall_handler() { 103 | core::arch::naked_asm!( 104 | "swapgs", // swap GS base to kernel GS base 105 | "push rcx", // save RCX (return RIP) 106 | "push r11", // save R11 (RFLAGS) 107 | // here you could call Rust code or handle syscall number in rax 108 | "pop r11", 109 | "pop rcx", 110 | "sysretq", 111 | ); 112 | } 113 | 114 | fn enable_syscalls() { 115 | unsafe { 116 | // Set IA32_LSTAR to syscall_handler address 117 | #[expect(clippy::fn_to_numeric_cast)] 118 | wrmsr(IA32_LSTAR, syscall_handler as u64); 119 | 120 | // Enable syscall/sysret in EFER (bit 0 = SCE) 121 | let mut efer = rdmsr(IA32_EFER); 122 | efer |= 1; // set SCE bit 123 | wrmsr(IA32_EFER, efer); 124 | } 125 | } 126 | 127 | -------------------------------------------------------------------------------- /src/init/font.rs: -------------------------------------------------------------------------------- 1 | pub fn draw_char(c: u8, x: usize, y: usize, fb: *mut u32, pitch: usize, fg: u32, font: &[u8]) { 2 | let char_size = font[3] as usize; 3 | //let glyph = &font[4 + (c as usize * char_size)..]; 4 | 5 | if let Some(glyph) = font.get(4 + (c as usize * char_size)..) { 6 | for (row, row_byte) in glyph.iter().enumerate().take(char_size) { 7 | for col in 0..8 { 8 | if (row_byte >> (7 - col)) & 1 != 0 { 9 | let px = x + col; 10 | let py = y + row; 11 | let offset = py * pitch + px * 4; 12 | unsafe { 13 | let pixel_ptr = fb.add(offset); 14 | *pixel_ptr = fg; 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | 22 | // 23 | // 24 | // 25 | 26 | pub fn print_result() -> super::result::InitResult { 27 | super::result::InitResult::Unknown 28 | } 29 | 30 | // 31 | // 32 | // 33 | 34 | pub static PSF_FONT: &[u8] = include_bytes!("../../terminus-font.psf"); 35 | 36 | pub struct PsfFont<'a> { 37 | glyphs: &'a [u8], 38 | bytes_per_glyph: usize, 39 | height: usize, 40 | width: usize, 41 | } 42 | 43 | pub fn parse_psf(psf: &'_ [u8]) -> Option> { 44 | if psf.starts_with(&[0x36, 0x04]) { // PSF1 45 | let glyph_size = psf[3] as usize; 46 | //let num_glyphs = if psf[2] & 0x01 != 0 { 512 } else { 256 }; 47 | 48 | Some(PsfFont { 49 | glyphs: &psf[4..], 50 | bytes_per_glyph: glyph_size, 51 | height: glyph_size, 52 | width: 8, 53 | }) 54 | } else if psf.starts_with(&[0x72, 0xb5, 0x4a, 0x86]) { // PSF2 55 | let header_len = u32::from_le_bytes(psf[8..12].try_into().unwrap()) as usize; 56 | let glyph_size = u32::from_le_bytes(psf[20..24].try_into().unwrap()) as usize; 57 | let height = u32::from_le_bytes(psf[24..28].try_into().unwrap()) as usize; 58 | let width = u32::from_le_bytes(psf[28..32].try_into().unwrap()) as usize; 59 | 60 | Some(PsfFont { 61 | glyphs: &psf[header_len..], 62 | bytes_per_glyph: glyph_size, 63 | height, 64 | width, 65 | }) 66 | } else { 67 | None 68 | } 69 | } 70 | 71 | #[expect(clippy::too_many_arguments)] 72 | fn draw_char_psf(font: &PsfFont, ch: u8, x: usize, y: usize, color: u32, framebuffer: *mut u32, _pitch: usize, _bpp: usize) { 73 | let glyph_start = ch as usize * font.bytes_per_glyph; 74 | //let glyph = &font.glyphs[glyph_start..glyph_start + font.bytes_per_glyph]; 75 | 76 | if let Some(glyph) = font.glyphs.get(glyph_start..glyph_start + font.bytes_per_glyph) { 77 | for (row, row_byte) in glyph.iter().enumerate().take(font.height) { 78 | for col in 0..font.width { 79 | if (row_byte >> (7 - col)) & 1 != 0 { 80 | unsafe { 81 | let offset = (y + row) * 4096 / 4 + (x + col); 82 | 83 | framebuffer.add(offset + 1).write_volatile(color); 84 | //framebuffer.add(offset as usize + 1).write_volatile(0xfefab0); 85 | //framebuffer.add(offset as usize + 2).write_volatile(0xdeadbeef); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | #[expect(clippy::too_many_arguments)] 94 | pub fn draw_text_psf(text: &str, font: &PsfFont, x: usize, y: usize, color: u32, framebuffer: *mut u32, pitch: usize, bpp: usize) { 95 | let mut cx = x; 96 | 97 | for ch in text.bytes() { 98 | draw_char_psf(font, ch, cx, y, color, framebuffer, pitch, bpp); 99 | cx += font.width; 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/init/fs.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::fat12::{block::Floppy, fs::Filesystem}; 2 | use super::{ 3 | config::{PATH_CLUSTER, set_path}, 4 | result, 5 | }; 6 | 7 | pub fn check_floppy() -> result::InitResult { 8 | let floppy = Floppy::init(); 9 | 10 | let res = match Filesystem::new(&floppy) { 11 | Ok(_) => { 12 | result::InitResult::Passed 13 | } 14 | Err(e) => { 15 | debug!("Filesystem init (floppy) fail: "); 16 | debugln!(e); 17 | result::InitResult::Skipped 18 | } 19 | }; 20 | 21 | set_path(b"/"); 22 | 23 | unsafe { 24 | PATH_CLUSTER = 0; 25 | } 26 | 27 | res 28 | } 29 | 30 | /*pub fn print_info(vga_index: &mut isize) { 31 | let floppy = Floppy; 32 | Floppy::init(); 33 | 34 | string(vga_index, b"Reading floppy...", Color::White); 35 | newline(vga_index); 36 | 37 | match Fs::new(&floppy, vga_index) { 38 | Ok(fs) => { 39 | unsafe { 40 | fs.list_dir(PATH_CLUSTER, &[b' '; 11], vga_index); 41 | } 42 | } 43 | Err(e) => { 44 | string(vga_index, e.as_bytes(), Color::Red); 45 | newline(vga_index); 46 | } 47 | } 48 | }*/ 49 | -------------------------------------------------------------------------------- /src/init/heap.rs: -------------------------------------------------------------------------------- 1 | use crate::mem::bump::{ALLOCATOR}; 2 | use core::ptr; 3 | 4 | use super::result::InitResult; 5 | 6 | pub fn print_result() -> InitResult { 7 | /*if !init_heap_allocator() { 8 | return InitResult::Failed; 9 | }*/ 10 | 11 | crate::mem::heap::init(); 12 | 13 | unsafe { 14 | for _ in 0..3 { 15 | let test_addr0 = crate::mem::heap::alloc(5) as usize; 16 | let test_addr1 = crate::mem::heap::alloc(50) as usize; 17 | let test_addr2 = crate::mem::heap::alloc(500) as usize; 18 | 19 | extern "C" { 20 | static __heap_start: u64; 21 | static __heap_end: u64; 22 | } 23 | 24 | let heap_start = &__heap_start as *const u64 as usize; 25 | let heap_end = &__heap_end as *const u64 as usize; 26 | 27 | if test_addr0 > heap_end || test_addr0 < heap_start { 28 | return InitResult::Failed; 29 | } 30 | 31 | if test_addr1 > heap_end || test_addr1 < heap_start { 32 | return InitResult::Failed; 33 | } 34 | 35 | if test_addr2 > heap_end || test_addr2 < heap_start { 36 | return InitResult::Failed; 37 | } 38 | 39 | let test_node0 = test_addr0 as *mut crate::mem::heap::HeapNode; 40 | let test_node1 = test_addr1 as *mut crate::mem::heap::HeapNode; 41 | let test_node2 = test_addr2 as *mut crate::mem::heap::HeapNode; 42 | 43 | rprint!("Test heap allocation: node size: "); 44 | rprintn!((*test_node0).size as u64); 45 | rprint!(" bytes\n"); 46 | 47 | rprint!("Test heap allocation: node size: "); 48 | rprintn!((*test_node1).size as u64); 49 | rprint!(" bytes\n"); 50 | 51 | rprint!("Test heap allocation: node size: "); 52 | rprintn!((*test_node2).size as u64); 53 | rprint!(" bytes\n"); 54 | 55 | crate::mem::heap::free(test_addr0 as u64); 56 | crate::mem::heap::free(test_addr1 as u64); 57 | crate::mem::heap::free(test_addr2 as u64); 58 | } 59 | } 60 | 61 | InitResult::Passed 62 | } 63 | 64 | fn init_heap_allocator() -> bool { 65 | debugln!("Heap allocator init start"); 66 | 67 | unsafe { 68 | unsafe extern "C" { 69 | static __heap_start: u8; 70 | static __heap_end: u8; 71 | } 72 | 73 | let heap_start = &__heap_start as *const u8 as usize; 74 | let heap_end = &__heap_end as *const u8 as usize; 75 | let heap_size = heap_end - heap_start; 76 | 77 | //#![allow(static_mut_refs)] 78 | let allocator_ptr = ptr::addr_of_mut!(ALLOCATOR); 79 | (*allocator_ptr).init(heap_start, heap_size); 80 | } 81 | 82 | true 83 | } 84 | 85 | -------------------------------------------------------------------------------- /src/init/idt.rs: -------------------------------------------------------------------------------- 1 | use crate::abi::idt::{install_isrs, load_idt}; 2 | 3 | pub fn get_result() -> super::result::InitResult { 4 | debugln!("Installing Exception handlers and ISRs"); 5 | install_isrs(); 6 | 7 | debugln!("Reloading IDT"); 8 | load_idt(); 9 | 10 | debugln!("Initializing TSS"); 11 | init_tss(); 12 | 13 | debugln!("Resetting TSS"); 14 | let base_addr = &raw const tss64 as u64; 15 | setup_tss_descriptor(base_addr, 0x67); 16 | 17 | debugln!("Reloading GDT"); 18 | reload_gdt(); 19 | 20 | debugln!("Loading TSS"); 21 | load_tss(0x28); 22 | 23 | super::result::InitResult::Passed 24 | } 25 | 26 | extern "C" { 27 | static mut tss64: Tss64; 28 | static mut gdt_start: u8; 29 | static mut gdt_end: u8; 30 | static mut gdt_tss_descriptor: [u8; 16]; 31 | } 32 | 33 | #[repr(C, packed)] 34 | struct DescriptorTablePointer { 35 | limit: u16, 36 | base: u64, 37 | } 38 | 39 | fn reload_gdt() { 40 | unsafe { 41 | // Prepare GDTR 42 | let gdtr = DescriptorTablePointer { 43 | limit: (&raw const gdt_end as usize - &raw const gdt_start as usize - 1) as u16, 44 | base: &raw const gdt_start as u64, 45 | }; 46 | 47 | // Load new GDT 48 | core::arch::asm!( 49 | "lgdt [{}]", 50 | in(reg) &gdtr, 51 | options(nostack, preserves_flags), 52 | ); 53 | } 54 | } 55 | 56 | fn load_tss(tss_selector: u16) { 57 | unsafe { 58 | core::arch::asm!( 59 | "ltr {0:x}", 60 | in(reg) tss_selector, 61 | options(nostack, preserves_flags), 62 | ); 63 | } 64 | } 65 | 66 | fn setup_tss_descriptor(base: u64, limit: u32) { 67 | let desc = make_tss_descriptor(base, limit); 68 | #[expect(static_mut_refs)] 69 | unsafe { 70 | gdt_tss_descriptor.copy_from_slice(&desc); 71 | } 72 | } 73 | 74 | fn make_tss_descriptor(base: u64, limit: u32) -> [u8; 16] { 75 | let mut desc = [0u8; 16]; 76 | 77 | // Limit low 16 bits 78 | desc[0] = (limit & 0xFF) as u8; 79 | desc[1] = ((limit >> 8) & 0xFF) as u8; 80 | 81 | // Base low 16 bits 82 | desc[2] = (base & 0xFF) as u8; 83 | desc[3] = ((base >> 8) & 0xFF) as u8; 84 | 85 | // Base middle 8 bits 86 | desc[4] = ((base >> 16) & 0xFF) as u8; 87 | 88 | // Access byte: present, privilege level 0, system segment, type 0x9 (available 64-bit TSS) 89 | desc[5] = 0b10001001; // P=1, DPL=00, S=0, Type=1001b = 0x9 90 | 91 | // Flags: limit high 4 bits + granularity + 64-bit flag + others 92 | desc[6] = ((limit >> 16) & 0xF) as u8; 93 | 94 | // Base high 8 bits 95 | desc[7] = ((base >> 24) & 0xFF) as u8; 96 | 97 | // Base upper 32 bits (for 64-bit base address) 98 | desc[8] = ((base >> 32) & 0xFF) as u8; 99 | desc[9] = ((base >> 40) & 0xFF) as u8; 100 | desc[10] = ((base >> 48) & 0xFF) as u8; 101 | desc[11] = ((base >> 56) & 0xFF) as u8; 102 | 103 | // The rest (12-15) must be zero per spec 104 | desc[12] = 0; 105 | desc[13] = 0; 106 | desc[14] = 0; 107 | desc[15] = 0; 108 | 109 | desc 110 | } 111 | 112 | #[repr(C, packed)] 113 | struct Tss64 { 114 | reserved0: u32, 115 | rsp0: u64, 116 | rsp1: u64, 117 | rsp2: u64, 118 | reserved1: u64, 119 | ist1: u64, 120 | ist2: u64, 121 | ist3: u64, 122 | ist4: u64, 123 | ist5: u64, 124 | ist6: u64, 125 | ist7: u64, 126 | reserved2: u64, 127 | reserved3: u16, 128 | io_map_base: u16, 129 | } 130 | 131 | fn init_tss() { 132 | unsafe { 133 | // Zero out the whole TSS first (probably redundant if in .bss) 134 | core::ptr::write_bytes(&raw mut tss64 as *mut u8, 0, core::mem::size_of::()); 135 | 136 | // Set kernel stack (top) pointer for ring 0 (rsp0) 137 | tss64.rsp0 = 0x190000; 138 | 139 | // IST pointers (interrupt stacks) 140 | // tss64.ist1 = some_stack_address; 141 | 142 | // IO Map base: set to size of TSS to disable IO bitmap 143 | tss64.io_map_base = core::mem::size_of::() as u16; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/init/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ascii; 2 | pub mod boot; 3 | pub mod color; 4 | pub mod config; 5 | pub mod cpu; 6 | pub mod font; 7 | pub mod fs; 8 | pub mod idt; 9 | pub mod heap; 10 | pub mod pit; 11 | pub mod result; 12 | pub mod video; 13 | 14 | use spin::Mutex; 15 | 16 | const BUFFER_SIZE: usize = 1024; 17 | 18 | static INIT_BUFFER: Mutex = Mutex::new(Buffer::new()); 19 | static mut FRAMEBUFFER: Option = None; 20 | 21 | pub fn init(multiboot_ptr: u64) { 22 | debugln!("Kernel init start"); 23 | 24 | let framebuffer_tag: boot::FramebufferTag = boot::FramebufferTag{ 25 | ..Default::default() 26 | }; 27 | 28 | result::print_result( 29 | "Load kernel", 30 | result::InitResult::Passed, 31 | ); 32 | 33 | result::print_result( 34 | "Check 64-bit Long Mode", 35 | cpu::check_mode(), 36 | ); 37 | 38 | result::print_result( 39 | "Reload IDT, GDT and TSS", 40 | idt::get_result() 41 | ); 42 | 43 | result::print_result( 44 | "Initialize heap allocator", 45 | heap::print_result(), 46 | ); 47 | 48 | result::print_result( 49 | "Read Multiboot2 tags", 50 | boot::print_info(multiboot_ptr, &framebuffer_tag), 51 | ); 52 | 53 | let video_result = video::print_result(&framebuffer_tag); 54 | 55 | result::print_result( 56 | "Initialize video", 57 | video_result, 58 | ); 59 | 60 | result::print_result( 61 | "Start PIC timer", 62 | pit::get_result(), 63 | ); 64 | 65 | result::print_result( 66 | "Check floppy drive", 67 | fs::check_floppy(), 68 | ); 69 | 70 | // TODO: Fallback to floppy to dump debug logs + init buffer 71 | if video_result == result::InitResult::Passed { 72 | INIT_BUFFER.lock().flush(); 73 | } 74 | 75 | color::color_demo(); 76 | ascii::ascii_art(); 77 | 78 | // Play startup melody 79 | //crate::audio::midi::play_melody(); 80 | //crate::audio::fs::play_midi_file(); 81 | let freqs = [440, 880, 660, 550]; 82 | for f in freqs { 83 | crate::audio::beep::stop_beep(); 84 | crate::audio::beep::beep(f); 85 | crate::audio::midi::wait_millis(300); 86 | } 87 | crate::audio::beep::stop_beep(); 88 | } 89 | 90 | struct Buffer { 91 | buf: [u8; 1024], 92 | pos: usize, 93 | } 94 | 95 | impl Buffer { 96 | /// Creates and returns a new instance of Buffer. 97 | const fn new() -> Self { 98 | Self { 99 | buf: [0u8; BUFFER_SIZE], 100 | pos: 0, 101 | } 102 | } 103 | 104 | /// Adds given byte slice to the buffer at offset of self.pos. 105 | fn append(&mut self, s: &[u8]) { 106 | // Take the input length, or the offset 107 | let len = s.len().min(self.buf.len() - self.pos); 108 | 109 | if let Some(buf) = self.buf.get_mut(self.pos..self.pos + len) { 110 | if let Some(slice) = s.get(..len) { 111 | // Copy the slice into buffer at offset of self.pos 112 | buf.copy_from_slice(slice); 113 | self.pos += len; 114 | } 115 | } 116 | } 117 | 118 | /// Puts the contents of buf into the printb! macro. 119 | fn flush(&self) { 120 | if let Some(buf) = self.buf.get(..self.pos) { 121 | printb!(buf); 122 | } 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/init/pit.rs: -------------------------------------------------------------------------------- 1 | use crate::input::port::{read, write}; 2 | 3 | pub fn init_pit(frequency_hz: u32) { 4 | if frequency_hz == 0 { 5 | return; 6 | } 7 | 8 | //let divisor = 1_193_180 / frequency_hz; 9 | let divisor = 1_193_000_000 / frequency_hz; 10 | 11 | // PIT control port 12 | write(0x43, 0x36); 13 | write(0x40, (divisor & 0xFF) as u8); // low byte 14 | write(0x40, ((divisor >> 8) & 0xFF) as u8); // high byte 15 | 16 | // Enable interrupts 17 | unsafe { 18 | core::arch::asm!("sti"); 19 | } 20 | 21 | } 22 | 23 | pub unsafe fn remap_pic() { 24 | const PIC1: u16 = 0x20; 25 | const PIC2: u16 = 0xA0; 26 | const PIC1_COMMAND: u16 = PIC1; 27 | const PIC1_DATA: u16 = PIC1 + 1; 28 | const PIC2_COMMAND: u16 = PIC2; 29 | const PIC2_DATA: u16 = PIC2 + 1; 30 | 31 | const ICW1_INIT: u8 = 0x11; 32 | const ICW4_8086: u8 = 0x01; 33 | 34 | let offset1: u8 = 0x20; // remap IRQs 0-7 to IDT 32–39 35 | let offset2: u8 = 0x28; // remap IRQs 8-15 to IDT 40–47 36 | 37 | // Save current masks 38 | let a1 = read(PIC1_DATA); 39 | let a2 = read(PIC2_DATA); 40 | 41 | // Start initialization 42 | write(PIC1_COMMAND, ICW1_INIT); 43 | io_wait(); 44 | write(PIC2_COMMAND, ICW1_INIT); 45 | io_wait(); 46 | 47 | // Set vector offsets 48 | write(PIC1_DATA, offset1); 49 | io_wait(); 50 | write(PIC2_DATA, offset2); 51 | io_wait(); 52 | 53 | // Tell PIC1 there is a PIC2 at IRQ2 (0000 0100) 54 | write(PIC1_DATA, 4); 55 | io_wait(); 56 | 57 | // Tell PIC2 its cascade identity (0000 0010) 58 | write(PIC2_DATA, 2); 59 | io_wait(); 60 | 61 | // Set to 8086/88 mode 62 | write(PIC1_DATA, ICW4_8086); 63 | io_wait(); 64 | write(PIC2_DATA, ICW4_8086); 65 | io_wait(); 66 | 67 | // Restore saved masks 68 | write(PIC1_DATA, a1); 69 | write(PIC2_DATA, a2); 70 | } 71 | 72 | 73 | pub unsafe fn io_wait() { 74 | write(0x80, 0); 75 | } 76 | 77 | pub fn get_result() -> super::result::InitResult { 78 | debugln!("Remapping PIC"); 79 | unsafe { remap_pic(); } 80 | 81 | debugln!("Starting 100Hz timer"); 82 | init_pit(1); // 100Hz -> 10ms per tick??? 83 | 84 | super::result::InitResult::Passed 85 | } 86 | -------------------------------------------------------------------------------- /src/init/result.rs: -------------------------------------------------------------------------------- 1 | use crate::{init::Buffer, video::vga::Color}; 2 | 3 | use super::INIT_BUFFER; 4 | 5 | #[derive(PartialEq, Copy, Clone)] 6 | pub enum InitResult { 7 | Unknown, 8 | Passed, 9 | Failed, 10 | Skipped, 11 | } 12 | 13 | impl InitResult { 14 | pub fn format(&self) -> (&[u8; 6], Color) { 15 | match self { 16 | InitResult::Unknown => 17 | (b"UNKNWN", Color::Cyan), 18 | InitResult::Passed => 19 | (b" OK ", Color::Green), 20 | InitResult::Failed => 21 | (b" FAIL ", Color::Red), 22 | InitResult::Skipped => 23 | (b" SKIP ", Color::Yellow), 24 | } 25 | } 26 | } 27 | 28 | const MAX_MSG_LEN: usize = 60; 29 | 30 | pub fn print_result(message: &'static str, result: InitResult) { 31 | let mut buf = Buffer::new(); 32 | 33 | buf.append(message.as_bytes()); 34 | 35 | for _ in 0..MAX_MSG_LEN - message.len() { 36 | buf.append(b"."); 37 | } 38 | 39 | buf.append(b" ["); 40 | buf.append(result.format().0); 41 | buf.append(b"]\n"); 42 | 43 | if let Some(slice) = buf.buf.get(..buf.pos) { 44 | // 45 | INIT_BUFFER.lock().append(slice); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/init/video.rs: -------------------------------------------------------------------------------- 1 | const PAGE_SIZE: u64 = 4096; 2 | const PAGE_PRESENT: u64 = 1 << 0; 3 | const PAGE_WRITE: u64 = 1 << 1; 4 | const PAGE_FLAGS: u64 = PAGE_PRESENT | PAGE_WRITE; 5 | 6 | pub fn map_framebuffer( 7 | fb_phys_addr: u64, 8 | fb_virt_base: u64, 9 | fb_size: u64, 10 | p4_table: &mut [u64; 512], 11 | p3_fb_table: &mut [u64; 512], 12 | p2_fb_tables: &mut [&mut [u64; 512]], 13 | p1_fb_tables: &mut [&mut [u64; 512]], 14 | ) { 15 | let page_count = fb_size.div_ceil(PAGE_SIZE); 16 | 17 | for i in 0..page_count { 18 | let virt = fb_virt_base + i * PAGE_SIZE; 19 | let phys = fb_phys_addr + i * PAGE_SIZE; 20 | 21 | let p4i = ((virt >> 39) & 0x1FF) as usize; 22 | let p3i = ((virt >> 30) & 0x1FF) as usize; 23 | let p2i = ((virt >> 21) & 0x1FF) as usize; 24 | let p1i = ((virt >> 12) & 0x1FF) as usize; 25 | 26 | // Link P4 → P3 27 | if p4_table[p4i] & PAGE_PRESENT == 0 { 28 | p4_table[p4i] = (p3_fb_table.as_ptr() as u64) | PAGE_FLAGS; 29 | } 30 | 31 | // Link P3 → P2 32 | if p3_fb_table[p3i] & PAGE_PRESENT == 0 { 33 | let p2_table = &mut p2_fb_tables[p3i]; 34 | p3_fb_table[p3i] = (p2_table.as_ptr() as u64) | PAGE_FLAGS; 35 | } 36 | 37 | // Link P2 → P1 38 | let p2_table = &mut p2_fb_tables[p3i]; 39 | if p2_table[p2i] & PAGE_PRESENT == 0 { 40 | let p1_index = (p3i << 9) | p2i; 41 | let p1_table = &p1_fb_tables[p1_index]; 42 | p2_table[p2i] = (p1_table.as_ptr() as u64) | PAGE_FLAGS; 43 | } 44 | 45 | // Map the physical framebuffer page into P1 46 | let p1_index = (p3i << 9) | p2i; 47 | let p1_table = &mut p1_fb_tables[p1_index]; 48 | p1_table[p1i] = phys | PAGE_FLAGS; 49 | } 50 | } 51 | 52 | pub fn print_result(fb: &super::boot::FramebufferTag) -> super::result::InitResult { 53 | use crate::video; 54 | 55 | video::mode::init_video(fb); 56 | 57 | if video::mode::get_video_mode().is_some() { 58 | return super::result::InitResult::Passed; 59 | } 60 | 61 | super::result::InitResult::Failed 62 | } 63 | -------------------------------------------------------------------------------- /src/input/elf.rs: -------------------------------------------------------------------------------- 1 | use core::ptr::{copy_nonoverlapping, write_bytes}; 2 | 3 | use crate::input::keyboard::keyboard_loop; 4 | 5 | #[repr(C)] 6 | #[derive(Debug)] 7 | struct Elf64Ehdr { 8 | e_ident: [u8; 16], 9 | e_type: u16, 10 | e_machine: u16, 11 | e_version: u32, 12 | e_entry: u64, 13 | e_phoff: u64, 14 | e_shoff: u64, 15 | e_flags: u32, 16 | e_ehsize: u16, 17 | e_phentsize: u16, 18 | e_phnum: u16, 19 | e_shentsize: u16, 20 | e_shnum: u16, 21 | e_shstrndx: u16, 22 | } 23 | 24 | #[repr(C)] 25 | #[derive(Debug)] 26 | struct Elf64Phdr { 27 | p_type: u32, 28 | p_flags: u32, 29 | p_offset: u64, 30 | p_vaddr: u64, 31 | p_paddr: u64, 32 | p_filesz: u64, 33 | p_memsz: u64, 34 | p_align: u64, 35 | } 36 | 37 | const PT_LOAD: u32 = 1; 38 | 39 | pub unsafe fn load_elf64(elf_addr: usize) -> usize { 40 | let ehdr = &*(elf_addr as *const Elf64Ehdr); 41 | 42 | rprint!("First 16 bytes (elf_addr + 0x18): "); 43 | for i in 0..16 { 44 | rprintn!(*((elf_addr + 0x18)as *const u8).add(i)); 45 | rprint!(" "); 46 | } 47 | rprint!("\n"); 48 | 49 | // Validate ELF magic 50 | assert_eq!(&ehdr.e_ident[0..4], b"\x7FELF"); 51 | assert_eq!(ehdr.e_ident[4], 2); // ELF64 52 | 53 | let phdrs = (elf_addr + ehdr.e_phoff as usize) as *const Elf64Phdr; 54 | 55 | for i in 0..ehdr.e_phnum { 56 | let ph = &*phdrs.add(i as usize); 57 | 58 | if ph.p_type == PT_LOAD { 59 | let src = (elf_addr + ph.p_offset as usize) as *const u8; 60 | let dst = ph.p_vaddr as *mut u8; 61 | 62 | // Sane debug info 63 | rprint!("Loading segment "); 64 | rprintn!(i); 65 | rprint!(" to "); 66 | rprintn!(ph.p_vaddr); 67 | rprint!(", filesz = "); 68 | rprintn!(ph.p_filesz); 69 | rprint!(", memsz = "); 70 | rprintn!(ph.p_memsz); 71 | rprint!("\n"); 72 | 73 | copy_nonoverlapping(src, dst, ph.p_filesz as usize); 74 | if ph.p_memsz > ph.p_filesz { 75 | write_bytes(dst.add(ph.p_filesz as usize), 0, (ph.p_memsz - ph.p_filesz) as usize); 76 | } 77 | } 78 | } 79 | 80 | //rprint!("ELF entry point: "); 81 | //rprintn!(ehdr.e_entry); 82 | //rprint!("\n"); 83 | 84 | ehdr.e_entry as usize 85 | } 86 | 87 | pub type ElfEntry = extern "C" fn() -> u64; 88 | 89 | #[no_mangle] 90 | #[link_section = ".data"] 91 | pub static mut SAVED_KERNEL_RSP: u64 = 0; 92 | 93 | #[expect(clippy::fn_to_numeric_cast)] 94 | #[no_mangle] 95 | pub unsafe extern "C" fn jump_to_elf(entry: ElfEntry, stack_top: u64, arg: u64) { 96 | extern "C" { 97 | fn kernel_return(); 98 | } 99 | 100 | let kernel_rsp: u64; 101 | core::arch::asm!("mov {}, rsp", out(reg) kernel_rsp); 102 | SAVED_KERNEL_RSP = kernel_rsp; 103 | 104 | // Trampoline 105 | let user_stack = (stack_top - 8) as *mut u64; 106 | *user_stack = kernel_return as u64; 107 | 108 | println!("Switching to user mode:"); 109 | 110 | core::arch::asm!( 111 | //"cli", 112 | "mov rsp, {0}", 113 | "mov rdi, {1}", 114 | 115 | "push 0x23", 116 | "push {0}", 117 | "pushfq", 118 | "push 0x1B", 119 | "push {2}", 120 | "iretq", 121 | in(reg) user_stack, 122 | in(reg) arg, 123 | in(reg) entry, 124 | options(noreturn) 125 | ); 126 | } 127 | 128 | #[no_mangle] 129 | pub unsafe extern "C" fn kernel_return(result: u64) -> ! { 130 | /*core::arch::asm!( 131 | // Restore original kernel stack 132 | "mov rsp, qword ptr [rip + saved_kernel_rsp]", 133 | "mov rbp, {saved_kernel_rsp}", 134 | saved_kernel_rsp = in(reg) saved_kernel_rsp, 135 | options(noreturn), 136 | );*/ 137 | 138 | print!("Program return code: "); 139 | printn!(result); 140 | print!("\n"); 141 | 142 | // Restore shell 143 | keyboard_loop(); 144 | } 145 | 146 | #[no_mangle] 147 | pub unsafe extern "C" fn call_elf(entry: ElfEntry, stack_top: u64, arg: u64) -> u64 { 148 | let _kernel_stack: u64; 149 | let _kernel_stack_pointer: u64; 150 | let result: u64; 151 | 152 | core::arch::asm!( 153 | "mov {kernel_stack_pointer}, rbp", 154 | "mov {kernel_stack}, rsp", 155 | "mov rsp, {stack}", 156 | "xor rbp, rbp", 157 | "call {entry}", 158 | "mov rbp, {kernel_stack_pointer}", 159 | "mov rsp, {kernel_stack}", 160 | kernel_stack = lateout(reg) _kernel_stack, 161 | kernel_stack_pointer = lateout(reg) _kernel_stack_pointer, 162 | stack = in(reg) stack_top, 163 | entry = in(reg) entry, 164 | in("rdi") arg, 165 | lateout("rax") result, 166 | options(nostack), 167 | ); 168 | 169 | //core::arch::asm!("mov {}, rax", out(reg) result); 170 | result 171 | } 172 | -------------------------------------------------------------------------------- /src/input/elf.rs.bak: -------------------------------------------------------------------------------- 1 | use core::ptr::{copy_nonoverlapping, write_bytes}; 2 | 3 | /// ELF64 types 4 | type Elf64Addr = u64; 5 | type Elf64Off = u64; 6 | type Elf64Half = u16; 7 | type Elf64Word = u32; 8 | type Elf64Xword = u64; 9 | 10 | /// ELF header (first 64 bytes) 11 | #[repr(C)] 12 | struct Elf64Ehdr { 13 | e_ident: [u8; 16], 14 | e_type: Elf64Half, 15 | e_machine: Elf64Half, 16 | e_version: Elf64Word, 17 | e_entry: Elf64Addr, 18 | e_phoff: Elf64Off, 19 | e_shoff: Elf64Off, 20 | e_flags: Elf64Word, 21 | e_ehsize: Elf64Half, 22 | e_phentsize:Elf64Half, 23 | e_phnum: Elf64Half, 24 | e_shentsize:Elf64Half, 25 | e_shnum: Elf64Half, 26 | e_shstrndx: Elf64Half, 27 | } 28 | 29 | /// Program header 30 | #[repr(C)] 31 | struct Elf64Phdr { 32 | p_type: Elf64Word, 33 | p_flags: Elf64Word, 34 | p_offset: Elf64Off, 35 | p_vaddr: Elf64Addr, 36 | p_paddr: Elf64Addr, 37 | p_filesz: Elf64Xword, 38 | p_memsz: Elf64Xword, 39 | p_align: Elf64Xword, 40 | } 41 | 42 | const PT_LOAD: Elf64Word = 1; 43 | 44 | /// Parse and load an ELF64 image from `elf_addr` (in-memory file), 45 | /// then return its entry point address. 46 | /// 47 | /// # Safety 48 | /// - Memory at `elf_addr..elf_addr+file_size` must contain the complete ELF file. 49 | /// - The destination virtual addresses (`p_vaddr`) must be identity-mapped and writable. 50 | pub unsafe fn load_elf64(elf_addr: usize, file_size: usize) -> usize { 51 | let ehdr = &*(elf_addr as *const Elf64Ehdr); 52 | 53 | // Validate ELF magic 54 | let id = &ehdr.e_ident; 55 | assert!(id[0] == 0x7F && id[1] == b'E' && id[2] == b'L' && id[3] == b'F'); 56 | assert!(id[4] == 2); // 64-bit 57 | 58 | let phdr_base = elf_addr + ehdr.e_phoff as usize; 59 | let phdrs = phdr_base as *const Elf64Phdr; 60 | 61 | for i in 0..ehdr.e_phnum { 62 | let ph = &*phdrs.add(i as usize); 63 | 64 | if ph.p_type != PT_LOAD { 65 | continue; 66 | } 67 | 68 | // Validate segment is inside file 69 | assert!((ph.p_offset + ph.p_filesz) as usize <= file_size); 70 | 71 | let src = (elf_addr + ph.p_offset as usize) as *const u8; 72 | let dst = ph.p_vaddr as *mut u8; 73 | 74 | // Copy to physical memory 75 | copy_nonoverlapping(src, dst, ph.p_filesz as usize); 76 | 77 | // Zero .bss if needed 78 | if ph.p_memsz > ph.p_filesz { 79 | let bss_start = dst.add(ph.p_filesz as usize); 80 | let bss_len = (ph.p_memsz - ph.p_filesz) as usize; 81 | write_bytes(bss_start, 0, bss_len); 82 | } 83 | 84 | rprint!("Loaded segment "); 85 | rprintn!(i); 86 | rprint!(" to "); 87 | rprintn!(ph.p_vaddr); 88 | rprint!(", filesz = "); 89 | rprintn!(ph.p_filesz); 90 | rprint!(", memsz = "); 91 | rprintn!(ph.p_memsz); 92 | rprint!("\n"); 93 | } 94 | 95 | rprint!("ELF entry point: "); 96 | rprintn!(ehdr.e_entry); 97 | rprint!("\n"); 98 | 99 | ehdr.e_entry as usize 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/input/irq.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::{AtomicUsize, Ordering}; 2 | 3 | const MAX_RECEPTORS: usize = 5; 4 | const USER_KBUF_SIZE: usize = 256; 5 | 6 | pub struct Subscriber { 7 | pub buf_ptr: u64, 8 | pub pid: usize, 9 | kbuf: [u8; USER_KBUF_SIZE], 10 | head: AtomicUsize, 11 | tail: AtomicUsize, 12 | } 13 | 14 | impl Subscriber { 15 | pub const fn new() -> Self { 16 | Self { 17 | buf_ptr: 0, 18 | pid: 0, 19 | kbuf: [0; USER_KBUF_SIZE], 20 | head: AtomicUsize::new(0), 21 | tail: AtomicUsize::new(0), 22 | } 23 | } 24 | 25 | /// Called from IRQ context 26 | pub fn push_irq(&self, b: u8) { 27 | let head = self.head.load(Ordering::Relaxed); 28 | let next = (head + 1) % USER_KBUF_SIZE; 29 | let tail = self.tail.load(Ordering::Acquire); 30 | if next == tail { 31 | return; 32 | } 33 | unsafe { 34 | //core::ptr::write_volatile(self.kbuf.as_ptr().add(head) as *mut u8, b); 35 | core::ptr::write_volatile(self.buf_ptr as *mut u8, b); 36 | } 37 | self.head.store(next, Ordering::Release); 38 | } 39 | 40 | pub fn copy_to_user(&self, dst: *mut u8, len: usize) -> usize { 41 | let mut copied = 0usize; 42 | while copied < len { 43 | let tail = self.tail.load(Ordering::Relaxed); 44 | let head = self.head.load(Ordering::Acquire); 45 | if tail == head { break; } // empty 46 | // 47 | let byte = unsafe { core::ptr::read_volatile(self.kbuf.as_ptr().add(tail)) }; 48 | unsafe { core::ptr::write_volatile(dst.add(copied), byte); } 49 | self.tail.store((tail + 1) % USER_KBUF_SIZE, Ordering::Release); 50 | copied += 1; 51 | } 52 | copied 53 | } 54 | 55 | pub fn available(&self) -> usize { 56 | let head = self.head.load(Ordering::Acquire); 57 | let tail = self.tail.load(Ordering::Relaxed); 58 | if head >= tail { head - tail } else { USER_KBUF_SIZE - tail + head } 59 | } 60 | 61 | pub fn clear(&self) { 62 | self.head.store(0, Ordering::Relaxed); 63 | self.tail.store(0, Ordering::Relaxed); 64 | } 65 | } 66 | 67 | pub static mut RECEPTORS: [Subscriber; MAX_RECEPTORS] = [ 68 | Subscriber::new(), 69 | Subscriber::new(), 70 | Subscriber::new(), 71 | Subscriber::new(), 72 | Subscriber::new(), 73 | ]; 74 | 75 | const TEMP_PID: usize = 123; 76 | 77 | pub fn pipe_subscribe(addr: u64) -> isize { 78 | unsafe { 79 | #[expect(static_mut_refs)] 80 | for s in RECEPTORS.iter_mut() { 81 | if s.pid == 0 { 82 | s.pid = TEMP_PID; 83 | s.buf_ptr = addr; 84 | s.clear(); 85 | 86 | rprint!("New subscriber registered\n"); 87 | return 0; 88 | } 89 | } 90 | } 91 | -1 // busy 92 | } 93 | 94 | pub fn pipe_unsubscribe(_addr: u64) -> isize { 95 | unsafe { 96 | #[expect(static_mut_refs)] 97 | for s in RECEPTORS.iter_mut() { 98 | if s.pid == TEMP_PID { 99 | s.pid = 0; 100 | s.buf_ptr = 0; 101 | s.clear(); 102 | return 0; 103 | } 104 | } 105 | } 106 | -1 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/input/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cmd; 2 | pub mod elf; 3 | pub mod irq; 4 | pub mod keyboard; 5 | pub mod port; 6 | -------------------------------------------------------------------------------- /src/input/port.rs: -------------------------------------------------------------------------------- 1 | use core; 2 | 3 | // 4 | // PORT HANDLING 5 | // 6 | 7 | pub fn read(port: u16) -> u8 { 8 | let data: u8; 9 | unsafe { 10 | core::arch::asm!( 11 | "in al, dx", 12 | in("dx") port, 13 | out("al") data 14 | ); 15 | } 16 | data 17 | } 18 | 19 | /// Writes a byte to a port (needs inline assembly) 20 | pub fn write(port: u16, value: u8) { 21 | unsafe { 22 | core::arch::asm!( 23 | "out dx, al", 24 | in("dx") port, 25 | in("al") value, 26 | ); 27 | } 28 | } 29 | 30 | /// Read a byte (u8) from port 31 | pub fn read_u8(port: u16) -> u8 { 32 | let value: u8; 33 | unsafe { 34 | core::arch::asm!( 35 | "in al, dx", 36 | in("dx") port, 37 | out("al") value, 38 | ); 39 | } 40 | value 41 | } 42 | 43 | /// Write a byte (u8) to port 44 | pub fn write_u8(port: u16, value: u8) { 45 | unsafe { 46 | core::arch::asm!( 47 | "out dx, al", 48 | in("dx") port, 49 | in("al") value, 50 | ); 51 | } 52 | } 53 | 54 | /// Read a word (u16) from port 55 | pub fn read_u16(port: u16) -> u16 { 56 | let value: u16; 57 | unsafe { 58 | core::arch::asm!( 59 | "in ax, dx", 60 | in("dx") port, 61 | out("ax") value, 62 | ); 63 | } 64 | value 65 | } 66 | 67 | /// Write a word (u16) to port 68 | pub fn write_u16(port: u16, value: u16) { 69 | unsafe { 70 | core::arch::asm!( 71 | "out dx, ax", 72 | in("dx") port, 73 | in("ax") value, 74 | ); 75 | } 76 | } 77 | 78 | /// Read a double word (u32) from port 79 | pub fn read_u32(port: u16) -> u32 { 80 | let value: u32; 81 | unsafe { 82 | core::arch::asm!( 83 | "in eax, dx", 84 | in("dx") port, 85 | out("eax") value, 86 | ); 87 | } 88 | value 89 | } 90 | 91 | /// Write a double word (u32) to port 92 | pub fn write_u32(port: u16, value: u32) { 93 | unsafe { 94 | core::arch::asm!( 95 | "out dx, eax", 96 | in("dx") port, 97 | in("eax") value, 98 | ); 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(abi_x86_interrupt)] 4 | #![feature(alloc_error_handler)] 5 | 6 | #[macro_use] 7 | mod debug; 8 | mod multiboot2; 9 | #[macro_use] 10 | mod video; 11 | 12 | // Core kernel modules 13 | mod abi; 14 | mod acpi; 15 | mod app; 16 | mod audio; 17 | mod fs; 18 | mod init; 19 | mod input; 20 | mod mem; 21 | mod net; 22 | mod task; 23 | mod time; 24 | mod tui; 25 | mod vga; 26 | 27 | /// Kernel entrypoint 28 | #[unsafe(no_mangle)] 29 | pub extern "C" fn kernel_main(_multiboot2_magic: u32, multiboot_ptr: u32) { 30 | debugln!("Kernel loaded"); 31 | 32 | // VGA buffer position (LEGACY) 33 | clear_screen!(); 34 | 35 | // TODO: REmove: Instantiate new VGA Writer 36 | video::vga::init_writer(); 37 | 38 | // Run init checks 39 | init::init(multiboot_ptr as u64); 40 | 41 | // Run the shell loop 42 | debugln!("Starting shell..."); 43 | println!("Starting shell...\n"); 44 | input::keyboard::keyboard_loop(); 45 | } 46 | 47 | // 48 | // 49 | // 50 | 51 | // #[lang = "eh_personality"] extern fn eh_personality() {} 52 | 53 | use core::panic::PanicInfo; 54 | 55 | /// Panic handler for panic fucntion invocations 56 | #[panic_handler] 57 | fn panic(info: &PanicInfo) -> ! { 58 | use vga::write::{string, number, newline}; 59 | use vga::buffer::Color; 60 | 61 | let vga_index: &mut isize = &mut 0; 62 | 63 | vga::screen::clear(vga_index); 64 | 65 | if let Some(location) = info.location() { 66 | string(vga_index, location.file().as_bytes(), Color::Red); 67 | string(vga_index, b":", Color::Red); 68 | number(vga_index, location.line() as u64); 69 | newline(vga_index); 70 | } else { 71 | string(vga_index, b"No location", Color::Red); 72 | newline(vga_index); 73 | } 74 | 75 | loop {} 76 | } 77 | 78 | #[unsafe(no_mangle)] 79 | pub extern "C" fn rust_begin_unwind(_: &core::panic::PanicInfo) { 80 | //loop {} 81 | } 82 | 83 | #[no_mangle] 84 | pub extern "C" fn panic_bounds_check() -> ! { 85 | //panic("bounds check failed"); 86 | loop { 87 | x86_64::instructions::hlt(); 88 | } 89 | } 90 | 91 | #[no_mangle] 92 | pub extern "C" fn slice_end_index_len_fail() -> ! { 93 | loop { 94 | x86_64::instructions::hlt(); 95 | } 96 | } 97 | 98 | #[no_mangle] 99 | pub extern "C" fn core_fmt_write() { 100 | loop { 101 | x86_64::instructions::hlt(); 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/mem/bump.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::{GlobalAlloc, Layout}; 2 | use core::ptr::null_mut; 3 | use core::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | #[global_allocator] 6 | pub static mut ALLOCATOR: BumpAllocator = BumpAllocator::new(); 7 | 8 | pub struct BumpAllocator { 9 | heap_start: usize, 10 | heap_end: usize, 11 | next: AtomicUsize, 12 | } 13 | 14 | impl BumpAllocator { 15 | pub const fn new() -> Self { 16 | Self { 17 | heap_start: 0, 18 | heap_end: 0, 19 | next: AtomicUsize::new(0), 20 | } 21 | } 22 | 23 | /// Called once during kernel init 24 | pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) { 25 | self.heap_start = heap_start; 26 | self.heap_end = heap_start + heap_size; 27 | self.next.store(heap_start, Ordering::SeqCst); 28 | } 29 | } 30 | 31 | unsafe impl GlobalAlloc for BumpAllocator { 32 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 33 | let align = layout.align(); 34 | let size = layout.size(); 35 | 36 | let current = self.next.load(Ordering::SeqCst); 37 | 38 | // Align current pointer 39 | let aligned = (current + align - 1) & !(align - 1); 40 | let new_next = aligned.checked_add(size).unwrap_or(aligned); 41 | 42 | if new_next > self.heap_end { 43 | return null_mut(); // Out of memory 44 | } 45 | 46 | self.next.store(new_next, Ordering::SeqCst); 47 | aligned as *mut u8 48 | } 49 | 50 | unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { 51 | // Bump allocator does not deallocate 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/mem/c.rs: -------------------------------------------------------------------------------- 1 | #[unsafe(no_mangle)] 2 | pub extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 { 3 | unsafe { 4 | let mut i = 0; 5 | while i < n { 6 | *dst.add(i) = *src.add(i); 7 | i += 1; 8 | } 9 | dst 10 | } 11 | } 12 | 13 | #[no_mangle] 14 | pub extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { 15 | for i in 0..n { 16 | let a = unsafe { *s1.add(i) }; 17 | let b = unsafe { *s2.add(i) }; 18 | if a != b { 19 | return a as i32 - b as i32; 20 | } 21 | } 22 | 0 23 | } 24 | 25 | #[unsafe(no_mangle)] 26 | pub extern "C" fn memset(dst: *mut u8, val: i32, n: usize) -> *mut u8 { 27 | unsafe { 28 | let mut i = 0; 29 | while i < n { 30 | *dst.add(i) = val as u8; 31 | i += 1; 32 | } 33 | dst 34 | } 35 | } 36 | 37 | #[unsafe(no_mangle)] 38 | pub extern "C" fn memmove(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 { 39 | unsafe { 40 | if src < dst as *const u8 { 41 | let mut i = n; 42 | while i != 0 { 43 | i -= 1; 44 | *dst.add(i) = *src.add(i); 45 | } 46 | } else { 47 | let mut i = 0; 48 | while i < n { 49 | *dst.add(i) = *src.add(i); 50 | i += 1; 51 | } 52 | } 53 | dst 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/mem/heap.rs: -------------------------------------------------------------------------------- 1 | pub const MIN_HEAP_NODE_SIZE: usize = 0x10; 2 | 3 | pub struct HeapNode { 4 | pub size: usize, 5 | pub status: HeapNodeStatus, 6 | pub previous: *mut HeapNode, 7 | pub next: *mut HeapNode, 8 | } 9 | 10 | #[derive(PartialEq)] 11 | pub enum HeapNodeStatus { 12 | Unknown = 0x00, 13 | Free, 14 | Used, 15 | } 16 | 17 | unsafe extern "C" { 18 | static __heap_start: u64; 19 | static __heap_end: u64; 20 | } 21 | 22 | static mut HEAP_PTR: usize = 0; 23 | 24 | pub fn init() { 25 | unsafe { 26 | let heap_start = &__heap_start as *const u64 as usize; 27 | let heap_end = &__heap_end as *const u64 as usize; 28 | 29 | HEAP_PTR = heap_start; 30 | 31 | let init_node = HeapNode { 32 | size: heap_end - heap_start - core::mem::size_of::(), 33 | status: HeapNodeStatus::Free, 34 | previous: core::ptr::null_mut::(), 35 | next: core::ptr::null_mut::(), 36 | }; 37 | 38 | core::ptr::copy(&init_node, HEAP_PTR as *mut HeapNode, 1); 39 | 40 | rprint!("Kernel heap initialized: size: "); 41 | rprintn!(init_node.size); 42 | rprint!(" bytes.\n"); 43 | } 44 | } 45 | 46 | unsafe fn merge(mut node: *mut HeapNode) { 47 | while (*node).status != HeapNodeStatus::Free { 48 | if (*node).previous > core::ptr::null_mut::() { 49 | node = (*node).previous; 50 | continue; 51 | } 52 | 53 | if (*node).next > core::ptr::null_mut::() { 54 | node = (*node).next; 55 | continue; 56 | } 57 | 58 | rprint!("OOM: merge()\n"); 59 | } 60 | 61 | if (*(*node).previous).status == HeapNodeStatus::Free { 62 | node = (*node).previous; 63 | 64 | let merged_node = HeapNode { 65 | size: (*node).size + (*(*node).next).size + core::mem::size_of::(), 66 | status: HeapNodeStatus::Free, 67 | previous: (*node).previous, 68 | next: (*(*node).next).next, 69 | }; 70 | 71 | rprint!("Merging nodes: total size: "); 72 | rprintn!(merged_node.size); 73 | rprint!(" bytes\n"); 74 | 75 | core::ptr::copy(&merged_node, node, 1); 76 | 77 | let right_node = merged_node.next; 78 | (*right_node).previous = merged_node.previous; 79 | 80 | core::ptr::copy(right_node, merged_node.next, 1); 81 | 82 | HEAP_PTR = node as usize; 83 | } 84 | } 85 | 86 | unsafe fn split(node: *mut HeapNode, alloc_size: usize) { 87 | if (*node).size - MIN_HEAP_NODE_SIZE < alloc_size { 88 | // Do not split relatively small nodes. 89 | return; 90 | } 91 | 92 | let node_size = { 93 | if alloc_size < MIN_HEAP_NODE_SIZE { 94 | MIN_HEAP_NODE_SIZE 95 | } else { 96 | alloc_size 97 | } 98 | }; 99 | 100 | let left_node = HeapNode { 101 | size: node_size, 102 | status: HeapNodeStatus::Free, 103 | previous: (*node).previous, 104 | next: node.add(core::mem::size_of::() + node_size).addr() as *mut HeapNode, 105 | }; 106 | 107 | let right_node = HeapNode { 108 | size: (*node).size - node_size - core::mem::size_of::(), 109 | status: HeapNodeStatus::Free, 110 | previous: node, 111 | next: (*node).next, 112 | }; 113 | 114 | rprint!("Splitting: left_node.size: "); 115 | rprintn!(left_node.size); 116 | rprint!(" bytes, right_node.size: "); 117 | rprintn!(right_node.size); 118 | rprint!(" bytes\n"); 119 | 120 | core::ptr::copy(&right_node, left_node.next, 1); 121 | core::ptr::copy(&left_node, node, 1); 122 | } 123 | 124 | pub unsafe fn alloc(alloc_size: usize) -> u64 { 125 | let mut cur_node = HEAP_PTR as *mut HeapNode; 126 | 127 | let mut limit = 0; 128 | 129 | while (*cur_node).size < alloc_size { 130 | if (*cur_node).previous > core::ptr::null_mut::() && (*(*cur_node).previous).status == HeapNodeStatus::Free { 131 | cur_node = (*cur_node).previous; 132 | continue; 133 | } 134 | 135 | if (*cur_node).next > core::ptr::null_mut::() && (*(*cur_node).next).status == HeapNodeStatus::Free { 136 | cur_node = (*cur_node).next; 137 | continue; 138 | } 139 | 140 | rprint!("OOM: alloc()\n"); 141 | limit += 1; 142 | 143 | if limit > 50 { 144 | return 0; 145 | } 146 | } 147 | 148 | if (*cur_node).size > alloc_size { 149 | split(cur_node, alloc_size); 150 | } 151 | 152 | // Reload the current node's metadata 153 | cur_node = HEAP_PTR as *mut HeapNode; 154 | 155 | // Zero the heap allocation 156 | cur_node.add(1).write_bytes(0, alloc_size); 157 | 158 | (*cur_node).status = HeapNodeStatus::Used; 159 | HEAP_PTR = (*cur_node).next as usize; 160 | 161 | // Return VAddr to allocated area 162 | //(cur_node as *mut HeapNode as u64) + (core::mem::size_of::() as u64) 163 | cur_node as u64 164 | } 165 | 166 | pub unsafe fn free(vaddr: u64) { 167 | let cur_node = vaddr as *mut HeapNode; 168 | 169 | (*cur_node).status = HeapNodeStatus::Free; 170 | 171 | core::ptr::copy(cur_node, vaddr as *mut HeapNode, 1); 172 | 173 | merge(cur_node); 174 | } 175 | -------------------------------------------------------------------------------- /src/mem/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bump; 2 | pub mod c; 3 | pub mod heap; 4 | pub mod pages; 5 | -------------------------------------------------------------------------------- /src/mem/pages.rs: -------------------------------------------------------------------------------- 1 | // Pre-allocated memory space for page tables (e.g., 64 KiB) 2 | const PAGE_TABLE_MEMORY_SIZE: usize = 128 * 1024; 3 | static mut PAGE_TABLE_MEMORY: [u8; PAGE_TABLE_MEMORY_SIZE] = [0; 128 * 1024]; 4 | static mut NEXT_FREE_PAGE: usize = 0x1000; 5 | 6 | unsafe fn alloc_page() -> *mut u8 { 7 | if NEXT_FREE_PAGE + 0x1000 > PAGE_TABLE_MEMORY_SIZE { 8 | panic!("Out of preallocated page table memory!"); 9 | } 10 | let addr = &mut PAGE_TABLE_MEMORY[NEXT_FREE_PAGE] as *mut u8; 11 | NEXT_FREE_PAGE += 0x1000; 12 | core::ptr::write_bytes(addr, 0, 0x1000); 13 | addr 14 | } 15 | 16 | pub unsafe fn map_32mb(p4: *mut u64, phys_start: usize, virt_start: usize) { 17 | let p4_index = (virt_start >> 39) & 0x1FF; 18 | let p3_index = (virt_start >> 30) & 0x1FF; 19 | // let p2_index = (virt_start >> 21) & 0x1FF; 20 | // let p1_index = (virt_start >> 12) & 0x1FF; 21 | 22 | let p3 = get_or_alloc_table(p4.add(p4_index).as_mut().unwrap()); 23 | //let p3 = alloc_page(); 24 | p4.add(p4_index).write(p3 as u64 | 0x3); // present + writable 25 | 26 | let p2 = get_or_alloc_table(p3.add(p3_index).as_mut().unwrap()); 27 | //let p2 = alloc_page(); 28 | p3.add(p3_index).write(p2 as u64 | 0x3); 29 | 30 | for i in 0..16 { 31 | let p1 = alloc_page(); 32 | p2.add(i).write(p1 as u64 | 0x3); 33 | 34 | for j in 0..512 { 35 | let frame = phys_start + ((i * 512 + j) * 0x1000); 36 | p1.add(j).write(frame as u8 | 0x3); 37 | } 38 | } 39 | } 40 | 41 | pub unsafe fn identity_map(p4: *mut u64, size: usize) { 42 | let page_count = size / 0x1000; 43 | let p1_tables = page_count.div_ceil(512); 44 | 45 | let p3 = get_or_alloc_table(p4); 46 | p4.write(p3 as u64 | 0x3); 47 | 48 | let p2 = get_or_alloc_table(p3); 49 | p3.write(p2 as u64 | 0x3); 50 | 51 | for i in 0..p1_tables { 52 | let p1 = alloc_page(); 53 | debug!("identity_map: allocating P1["); 54 | debugn!(i); 55 | debug!("] = "); 56 | debugn!(p1); 57 | debugln!(""); 58 | p2.add(i).write(p1 as u64 | 0x3); 59 | for j in 0..512 { 60 | let page_idx = i * 512 + j; 61 | if page_idx >= page_count { 62 | break; 63 | } 64 | let phys = (page_idx * 0x1000) as u64; 65 | p1.add(j).write(phys as u8 | 0x3); 66 | } 67 | } 68 | } 69 | 70 | unsafe fn get_or_alloc_table(entry: *mut u64) -> *mut u64 { 71 | let val = entry.read(); 72 | if val & 1 == 0 { 73 | let new_page = alloc_page() as u64 | 0x3; 74 | entry.write(new_page); 75 | (new_page & 0x000f_ffff_ffff_f000) as *mut u64 76 | } else { 77 | (val & 0x000f_ffff_ffff_f000) as *mut u64 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/multiboot2.rs: -------------------------------------------------------------------------------- 1 | #[repr(C, packed)] 2 | #[cfg(feature = "kernel_text")] 3 | pub struct Multiboot2HeaderText { 4 | magic: u32, 5 | architecture: u32, 6 | header_length: u32, 7 | checksum: u32, 8 | 9 | // End tag 10 | tag_end_type: u16, // = 0 11 | tag_end_flags: u16, // = 0 12 | tag_end_size: u32, // = 8 13 | } 14 | 15 | #[unsafe(no_mangle)] 16 | #[unsafe(link_section = ".multiboot2_header")] 17 | #[used] 18 | #[cfg(feature = "kernel_text")] 19 | pub static MULTIBOOT2_HEADER_TEXT: Multiboot2HeaderText = { 20 | const MAGIC: u32 = 0xE85250D6; 21 | const ARCH: u32 = 0; 22 | const HEADER_LEN: u32 = core::mem::size_of::() as u32; 23 | const CHECKSUM: u32 = 0u32.wrapping_sub(MAGIC + ARCH + HEADER_LEN); 24 | 25 | Multiboot2HeaderText { 26 | magic: MAGIC, 27 | architecture: ARCH, 28 | header_length: HEADER_LEN, 29 | checksum: CHECKSUM, 30 | 31 | tag_end_type: 0, 32 | tag_end_flags: 0, 33 | tag_end_size: 8, 34 | } 35 | }; 36 | 37 | // 38 | // Graphics kernel with a framebuffer 39 | // 40 | 41 | #[repr(C, packed)] 42 | #[cfg(feature = "kernel_graphics")] 43 | pub struct Multiboot2HeaderGraphics { 44 | magic: u32, 45 | architecture: u32, 46 | header_length: u32, 47 | checksum: u32, 48 | 49 | // Framebuffer tag 50 | tag_fb_type: u16, // = 5 51 | tag_fb_flags: u16, // = 0 52 | tag_fb_size: u32, // = 20 53 | fb_width: u32, // e.g., 1024 54 | fb_height: u32, // e.g., 768 55 | fb_depth: u32, // e.g., 32 56 | fb_pad: u32, 57 | 58 | // End tag 59 | tag_end_type: u16, // = 0 60 | tag_end_flags: u16, // = 0 61 | tag_end_size: u32, // = 8 62 | } 63 | 64 | #[unsafe(no_mangle)] 65 | #[unsafe(link_section = ".multiboot2_header")] 66 | #[used] 67 | #[cfg(feature = "kernel_graphics")] 68 | pub static MULTIBOOT2_HEADER_GRAPHICS: Multiboot2HeaderGraphics = { 69 | const MAGIC: u32 = 0xE85250D6; 70 | const ARCH: u32 = 0; 71 | const HEADER_LEN: u32 = core::mem::size_of::() as u32; 72 | const CHECKSUM: u32 = 0u32.wrapping_sub(MAGIC + ARCH + HEADER_LEN); 73 | 74 | Multiboot2HeaderGraphics { 75 | magic: MAGIC, 76 | architecture: ARCH, 77 | header_length: HEADER_LEN, 78 | checksum: CHECKSUM, 79 | 80 | tag_fb_type: 5, 81 | tag_fb_flags: 0, 82 | tag_fb_size: 24, 83 | fb_width: 1024, 84 | fb_height: 768, 85 | fb_depth: 32, 86 | fb_pad: 0, 87 | 88 | tag_end_type: 0, 89 | tag_end_flags: 0, 90 | tag_end_size: 8, 91 | } 92 | }; 93 | 94 | -------------------------------------------------------------------------------- /src/net/arp.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 2 | pub struct MacAddress(pub [u8; 6]); 3 | 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 5 | pub struct Ipv4Address(pub [u8; 4]); 6 | 7 | #[repr(u16)] 8 | pub enum ArpOp { 9 | Request = 1, 10 | Reply = 2, 11 | } 12 | 13 | pub struct ArpPacket<'a> { 14 | pub hw_type: u16, // Usually 1 for Ethernet 15 | pub proto_type: u16, // Usually 0x0800 for IPv4 16 | pub hw_len: u8, // 6 17 | pub proto_len: u8, // 4 18 | pub op: ArpOp, 19 | pub sender_mac: MacAddress, 20 | pub sender_ip: Ipv4Address, 21 | pub target_mac: MacAddress, 22 | pub target_ip: Ipv4Address, 23 | pub raw: &'a [u8], // Whole packet slice 24 | } 25 | 26 | impl<'a> ArpPacket<'a> { 27 | pub fn parse(packet: &'a [u8]) -> Option { 28 | if packet.len() < 28 { 29 | return None; 30 | } 31 | 32 | let hw_type = u16::from_be_bytes([packet[0], packet[1]]); 33 | let proto_type = u16::from_be_bytes([packet[2], packet[3]]); 34 | let hw_len = packet[4]; 35 | let proto_len = packet[5]; 36 | let op_code = u16::from_be_bytes([packet[6], packet[7]]); 37 | let op = match op_code { 38 | 1 => ArpOp::Request, 39 | 2 => ArpOp::Reply, 40 | _ => return None, 41 | }; 42 | 43 | let sender_mac = MacAddress([ 44 | packet[8], packet[9], packet[10], 45 | packet[11], packet[12], packet[13], 46 | ]); 47 | let sender_ip = Ipv4Address([packet[14], packet[15], packet[16], packet[17]]); 48 | let target_mac = MacAddress([ 49 | packet[18], packet[19], packet[20], 50 | packet[21], packet[22], packet[23], 51 | ]); 52 | let target_ip = Ipv4Address([packet[24], packet[25], packet[26], packet[27]]); 53 | 54 | Some(Self { 55 | hw_type, 56 | proto_type, 57 | hw_len, 58 | proto_len, 59 | op, 60 | sender_mac, 61 | sender_ip, 62 | target_mac, 63 | target_ip, 64 | raw: packet, 65 | }) 66 | } 67 | 68 | pub fn build( 69 | buf: &mut [u8], 70 | op: ArpOp, 71 | sender_mac: MacAddress, 72 | sender_ip: Ipv4Address, 73 | target_mac: MacAddress, 74 | target_ip: Ipv4Address, 75 | ) -> Option { 76 | if buf.len() < 28 { 77 | return None; 78 | } 79 | 80 | buf[0..2].copy_from_slice(&1u16.to_be_bytes()); // Hw type: Ethernet 81 | buf[2..4].copy_from_slice(&0x0800u16.to_be_bytes()); // Proto type: IPv4 82 | buf[4] = 6; // MAC length 83 | buf[5] = 4; // IP length 84 | buf[6..8].copy_from_slice(&(op as u16).to_be_bytes()); 85 | 86 | buf[8..14].copy_from_slice(&sender_mac.0); 87 | buf[14..18].copy_from_slice(&sender_ip.0); 88 | buf[18..24].copy_from_slice(&target_mac.0); 89 | buf[24..28].copy_from_slice(&target_ip.0); 90 | 91 | Some(28) 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/net/ethernet.rs: -------------------------------------------------------------------------------- 1 | use crate::input::port; 2 | 3 | use super::rtl8139; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct MacAddress(pub [u8; 6]); 7 | 8 | #[derive(Debug)] 9 | pub enum EtherType { 10 | Ipv4, 11 | Arp, 12 | Unknown(u16), 13 | } 14 | 15 | impl EtherType { 16 | pub fn from_u16(value: u16) -> Self { 17 | match value { 18 | 0x0800 => EtherType::Ipv4, 19 | 0x0806 => EtherType::Arp, 20 | other => EtherType::Unknown(other), 21 | } 22 | } 23 | 24 | pub fn to_u16(&self) -> u16 { 25 | match *self { 26 | EtherType::Ipv4 => 0x0800, 27 | EtherType::Arp => 0x0806, 28 | EtherType::Unknown(val) => val, 29 | } 30 | } 31 | } 32 | 33 | pub struct EthernetFrame<'a> { 34 | pub dest_mac: MacAddress, 35 | pub src_mac: MacAddress, 36 | pub ethertype: EtherType, 37 | pub payload: &'a [u8], 38 | } 39 | 40 | impl<'a> EthernetFrame<'a> { 41 | pub fn parse(frame: &'a [u8]) -> Option { 42 | if frame.len() < 14 { 43 | return None; 44 | } 45 | 46 | let dest_mac = MacAddress([frame[0], frame[1], frame[2], frame[3], frame[4], frame[5]]); 47 | let src_mac = MacAddress([frame[6], frame[7], frame[8], frame[9], frame[10], frame[11]]); 48 | let ethertype = EtherType::from_u16(u16::from_be_bytes([frame[12], frame[13]])); 49 | let payload = &frame[14..]; 50 | 51 | Some(Self { 52 | dest_mac, 53 | src_mac, 54 | ethertype, 55 | payload, 56 | }) 57 | } 58 | 59 | pub fn write( 60 | buffer: &mut [u8], 61 | dest_mac: MacAddress, 62 | src_mac: MacAddress, 63 | ethertype: EtherType, 64 | payload: &[u8], 65 | ) -> Option { 66 | if buffer.len() < 14 + payload.len() { 67 | return None; 68 | } 69 | 70 | buffer[0..6].copy_from_slice(&dest_mac.0); 71 | buffer[6..12].copy_from_slice(&src_mac.0); 72 | let ethertype_bytes = ethertype.to_u16().to_be_bytes(); 73 | buffer[12..14].copy_from_slice(ðertype_bytes); 74 | buffer[14..14 + payload.len()].copy_from_slice(payload); 75 | 76 | Some(14 + payload.len()) 77 | } 78 | } 79 | 80 | pub fn receive_frame(_buf: &mut [u8]) -> Option { 81 | // Hardware-specific receive logic (e.g., RTL8139, E1000) 82 | // Fill `buf[..len]` with received frame 83 | // Return Some(len) if a frame is received 84 | 85 | None 86 | } 87 | 88 | pub fn build_ethernet_frame( 89 | dst_mac: [u8; 6], 90 | src_mac: [u8; 6], 91 | ethertype: u16, 92 | payload: &[u8], 93 | ) -> [u8; 1514] { 94 | let mut buf = [0u8; 1514]; 95 | buf[..6].copy_from_slice(&dst_mac); 96 | buf[6..12].copy_from_slice(&src_mac); 97 | buf[12..14].copy_from_slice(ðertype.to_be_bytes()); 98 | buf[14..14 + payload.len()].copy_from_slice(payload); 99 | buf 100 | } 101 | 102 | fn send_arp_reply(payload: &[u8]) { 103 | let frame = build_ethernet_frame( 104 | [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01], 105 | [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC], 106 | 0x0806, 107 | payload, 108 | ); 109 | 110 | rtl8139::send_frame(&frame).unwrap(); 111 | } 112 | 113 | 114 | pub fn receive_loop(callback: fn(packet: &[u8]) -> u8) -> u8 { 115 | let mut frame_buf: [u8; 2048] = [0; 2048]; 116 | 117 | loop { 118 | // While the keyboard is idle... 119 | while port::read(0x64) & 1 == 0 { 120 | 121 | rtl8139::rtl8139_init(); 122 | 123 | if let Some(frame_len) = rtl8139::receive_frame(&mut frame_buf) { 124 | // Minimal length check 125 | if frame_len < 14 { 126 | continue; 127 | } 128 | 129 | if let Some(slice) = frame_buf.get(..frame_len) { 130 | return callback(slice); 131 | // if let Some(frame) = EthernetFrame::parse(slice) { 132 | 133 | // match frame.ethertype { 134 | // //0x0800 => { 135 | // EtherType::Ipv4 => { 136 | // // IPv4 packet → pass to callback 137 | // return callback(frame.payload); 138 | // } 139 | // //0x0806 => { 140 | // EtherType::Arp => { 141 | // // ARP → handle separately 142 | // if let Some(arp) = ArpPacket::parse(frame.payload) { 143 | // // handle_arp(arp); 144 | // } 145 | // } 146 | // _ => { 147 | // return callback(frame.payload); 148 | // } 149 | // } 150 | // } 151 | } 152 | } 153 | } 154 | 155 | // If any key is pressed, break the loop and return. 156 | if port::read(0x60) & 0x80 == 0 { 157 | break; 158 | } 159 | } 160 | 161 | 3 162 | } 163 | 164 | -------------------------------------------------------------------------------- /src/net/icmp.rs: -------------------------------------------------------------------------------- 1 | #[repr(C, packed)] 2 | pub struct IcmpHeader { 3 | pub icmp_type: u8, 4 | pub icmp_code: u8, 5 | pub checksum: u16, 6 | pub identifier: u16, 7 | pub sequence_number: u16, 8 | } 9 | 10 | pub fn create_packet( 11 | packet_type: u8, // 8 for Echo Request, 0 for Echo Reply 12 | identifier: u16, 13 | sequence_number: u16, 14 | payload: &[u8], 15 | out_buffer: &mut [u8], 16 | ) -> usize { 17 | let header_len = 8; // ICMP header is 8 bytes 18 | 19 | let header = IcmpHeader { 20 | icmp_type: packet_type, 21 | icmp_code: 0, // Usually 0 22 | checksum: 0, // To be filled later 23 | identifier, 24 | sequence_number, 25 | }; 26 | 27 | // Copy header 28 | unsafe { 29 | let header_bytes = core::slice::from_raw_parts( 30 | &header as *const _ as *const u8, 31 | core::mem::size_of::(), 32 | ); 33 | if let Some(slice) = out_buffer.get_mut(..header_bytes.len()) { 34 | slice.copy_from_slice(header_bytes); 35 | } 36 | } 37 | 38 | // Copy payload 39 | if let Some(slice) = out_buffer.get_mut(header_len..header_len + payload.len()) { 40 | slice.copy_from_slice(payload); 41 | } 42 | 43 | // Calculate checksum (over full packet: header + payload) 44 | let out_slice = out_buffer.get(..header_len + payload.len()).unwrap_or(&[]); 45 | let checksum = get_checksum(out_slice); 46 | 47 | // Store checksum (Big Endian / Network order!) 48 | if let Some(slice) = out_buffer.get_mut(2..4) { 49 | slice.copy_from_slice(&checksum.to_be_bytes()); 50 | } 51 | 52 | header_len + payload.len() 53 | } 54 | 55 | fn get_checksum(packet: &[u8]) -> u16 { 56 | let mut sum = 0u32; 57 | let mut i = 0; 58 | 59 | while i + 1 < packet.len() { 60 | let word = if i == 2 { 61 | 0u16 // Skip checksum field (2..=3) 62 | } else { 63 | let mut ret: u16 = 0; 64 | 65 | if let Some(w1) = packet.get(i) { 66 | if let Some(w2) = packet.get(i + 1) { 67 | ret = u16::from_be_bytes([*w1, *w2]); 68 | } 69 | } 70 | 71 | ret 72 | }; 73 | sum = sum.wrapping_add(word as u32); 74 | i += 2; 75 | } 76 | 77 | if i < packet.len() { 78 | // Odd length: last byte padded with 0 79 | if let Some(w) = packet.get(i) { 80 | sum = sum.wrapping_add(((*w as u16) << 8) as u32); 81 | } 82 | } 83 | 84 | // Fold overflows 85 | while (sum >> 16) != 0 { 86 | sum = (sum & 0xffff) + (sum >> 16); 87 | } 88 | 89 | !(sum as u16) 90 | } 91 | 92 | pub fn parse_packet(packet: &[u8]) -> Option<(IcmpHeader, &[u8])> { 93 | if packet.len() < 8 { 94 | return None; // ICMP header is at least 8 bytes 95 | } 96 | 97 | let header = unsafe { 98 | let ptr = packet.as_ptr() as *const IcmpHeader; 99 | ptr.read_unaligned() 100 | }; 101 | 102 | let header_len = 8; // ICMP header length is always 8 bytes 103 | let payload_slice = packet.get(header_len..).unwrap_or(&[]); 104 | 105 | Some((header, payload_slice)) 106 | } 107 | 108 | -------------------------------------------------------------------------------- /src/net/ipv4.rs: -------------------------------------------------------------------------------- 1 | use crate::input::port; 2 | use crate::net::serial; 3 | use crate::net::slip; 4 | use crate::net::tcp; 5 | 6 | pub const MAX_CONNS: usize = 10; 7 | 8 | #[repr(C, packed)] 9 | pub struct Ipv4Header { 10 | pub version_ihl: u8, 11 | dscp_ecn: u8, 12 | pub total_length: u16, 13 | identification: u16, 14 | flags_fragment_offset: u16, 15 | ttl: u8, 16 | pub protocol: u8, 17 | header_checksum: u16, 18 | pub source_ip: [u8; 4], 19 | pub dest_ip: [u8; 4], 20 | } 21 | 22 | // 23 | // CREATE/HANDLE PACKET 24 | // 25 | 26 | pub fn create_packet( 27 | source: [u8; 4], 28 | dest: [u8; 4], 29 | protocol: u8, 30 | payload: &[u8], 31 | out_buffer: &mut [u8], 32 | ) -> usize { 33 | let header_len = 20; 34 | let total_len = (header_len + payload.len()) as u16; 35 | 36 | let header = Ipv4Header { 37 | version_ihl: (4 << 4) | 5, // Version 4, IHL=5 (20 bytes) 38 | dscp_ecn: 0, 39 | total_length: total_len.to_be(), 40 | identification: 0x1337u16.to_be(), 41 | flags_fragment_offset: (0x4000u16).to_be(), // Don't Fragment flag 42 | ttl: 64, 43 | protocol, 44 | header_checksum: 0, // To be calculated 45 | source_ip: source, 46 | dest_ip: dest, 47 | }; 48 | 49 | // Copy header into buffer 50 | unsafe { 51 | let header_bytes = core::slice::from_raw_parts( 52 | &header as *const _ as *const u8, 53 | core::mem::size_of::(), 54 | ); 55 | 56 | if let Some(slice) = out_buffer.get_mut(..header_bytes.len()) { 57 | slice.copy_from_slice(header_bytes); 58 | } 59 | } 60 | 61 | // Calculate checksum 62 | 63 | let out_slice = out_buffer.get(..header_len).unwrap_or(&[]); 64 | let checksum = ipv4_checksum(out_slice); 65 | 66 | if let Some(slice) = out_buffer.get_mut(10..12) { 67 | slice.copy_from_slice(&checksum.to_be_bytes()); 68 | } 69 | 70 | // Copy payload 71 | 72 | if let Some(slice) = out_buffer.get_mut(header_len..header_len + payload.len()) { 73 | slice.copy_from_slice(payload); 74 | } 75 | 76 | header_len + payload.len() 77 | } 78 | 79 | pub fn parse_packet(packet: &[u8]) -> Option<(Ipv4Header, &[u8])> { 80 | if packet.len() < 20 { 81 | return None; 82 | } 83 | 84 | let header = unsafe { 85 | let ptr = packet.as_ptr() as *const Ipv4Header; 86 | ptr.read_unaligned() 87 | }; 88 | 89 | let header_len = (header.version_ihl & 0x0F) * 4; 90 | if packet.len() < header_len as usize { 91 | return None; 92 | } 93 | 94 | let payload_slice = packet.get(header_len as usize..).unwrap_or(&[]); 95 | 96 | Some((header, payload_slice)) 97 | } 98 | 99 | 100 | /// Compute IPv4 header checksum 101 | fn ipv4_checksum(data: &[u8]) -> u16 { 102 | let mut sum = 0u32; 103 | let mut chunks = data.chunks_exact(2); 104 | 105 | for chunk in &mut chunks { 106 | if let Some(w1) = chunk.first() { 107 | if let Some(w2) = chunk.get(1) { 108 | sum = sum.wrapping_add( u16::from_be_bytes([*w1, *w2]) as u32 ); 109 | } 110 | } 111 | } 112 | if let Some(&byte) = chunks.remainder().first() { 113 | let word = (byte as u16) << 8; 114 | sum = sum.wrapping_add(word as u32); 115 | } 116 | 117 | while (sum >> 16) != 0 { 118 | sum = (sum & 0xFFFF) + (sum >> 16); 119 | } 120 | 121 | !(sum as u16) 122 | } 123 | 124 | // 125 | // SEND/RECEIVE PACKET 126 | // 127 | 128 | /// Called to send a packet 129 | pub fn send_packet(packet: &[u8]) { 130 | let mut encoded_buf = [0u8; 4096]; 131 | 132 | if let Some(encoded_len) = slip::encode(packet, &mut encoded_buf) { 133 | serial::init(); 134 | 135 | let encoded_slice = encoded_buf.get(..encoded_len).unwrap_or(&[]); 136 | for &b in encoded_slice { 137 | serial::write(b); 138 | } 139 | } 140 | } 141 | 142 | /// Called when you receive a new serial byte 143 | pub fn receive_loop(callback: fn(packet: &[u8]) -> u8) -> u8 { 144 | let mut temp_buf: [u8; 2048] = [0; 2048]; 145 | let mut packet_buf: [u8; 2048] = [0; 2048]; 146 | let mut temp_len: usize = 0; 147 | 148 | serial::init(); 149 | 150 | loop { 151 | // While the keyboard is idle... 152 | while port::read(0x64) & 1 == 0 { 153 | if serial::ready() && temp_len <= temp_buf.len() { 154 | 155 | if let Some(p) = temp_buf.get_mut(temp_len) { 156 | *p = serial::read(); 157 | } 158 | temp_len += 1; 159 | 160 | let temp_slice = temp_buf.get(..temp_len).unwrap_or(&[]); 161 | 162 | if let Some(packet_len) = slip::decode(temp_slice, &mut packet_buf) { 163 | // Full packet decoded 164 | let packet_slice = packet_buf.get(..packet_len).unwrap_or(&[]); 165 | return callback(packet_slice); 166 | } 167 | } 168 | } 169 | 170 | // If any key is pressed, break the loop and return 171 | if port::read(0x60) & 0x80 == 0 { 172 | break; 173 | } 174 | } 175 | 3 176 | } 177 | 178 | /// Called when you receive a new serial byte 179 | pub fn receive_loop_tcp(conns: &mut [Option; MAX_CONNS], callback: fn(conns: &mut [Option; MAX_CONNS], packet: &[u8]) -> u8) -> u8 { 180 | let mut temp_buf: [u8; 2048] = [0; 2048]; 181 | let mut packet_buf: [u8; 2048] = [0; 2048]; 182 | let mut temp_len: usize = 0; 183 | 184 | serial::init(); 185 | 186 | loop { 187 | // While the keyboard is idle... 188 | while port::read(0x64) & 1 == 0 { 189 | if serial::ready() && temp_len <= temp_buf.len() { 190 | if let Some(p) = temp_buf.get_mut(temp_len) { 191 | *p = serial::read(); 192 | } 193 | temp_len += 1; 194 | 195 | let temp_slice = temp_buf.get(..temp_len).unwrap_or(&[]); 196 | 197 | if let Some(packet_len) = slip::decode(temp_slice, &mut packet_buf) { 198 | // Full packet decoded 199 | let packet_slice = packet_buf.get(..packet_len).unwrap_or(&[]); 200 | return callback(conns, packet_slice); 201 | } 202 | } 203 | } 204 | 205 | // If any key is pressed, break the loop and return 206 | if port::read(0x60) & 0x80 == 0 { 207 | break; 208 | } 209 | } 210 | 3 211 | } 212 | 213 | -------------------------------------------------------------------------------- /src/net/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod arp; 2 | pub mod ethernet; 3 | pub mod icmp; 4 | pub mod ipv4; 5 | pub mod pci; 6 | pub mod rtl8139; 7 | pub mod serial; 8 | pub mod slip; 9 | pub mod tcp; 10 | pub mod udp; 11 | -------------------------------------------------------------------------------- /src/net/pci.rs: -------------------------------------------------------------------------------- 1 | use crate::input::port; 2 | 3 | fn pci_config_address(bus: u8, device: u8, function: u8, offset: u8) -> u32 { 4 | 0x8000_0000 5 | | ((bus as u32) << 16) 6 | | ((device as u32) << 11) 7 | | ((function as u32) << 8) 8 | | ((offset as u32) & 0xFC) 9 | } 10 | 11 | fn pci_config_read_u32(bus: u8, device: u8, function: u8, offset: u8) -> u32 { 12 | let address = pci_config_address(bus, device, function, offset); 13 | port::write_u32(0xCF8, address); 14 | port::read_u32(0xCFC) 15 | } 16 | 17 | fn pci_config_read_u16(bus: u8, device: u8, function: u8, offset: u8) -> u16 { 18 | let address = pci_config_address(bus, device, function, offset); 19 | port::write_u32(0xCF8, address); 20 | (port::read_u32(0xCFC) >> ((offset & 2) * 8)) as u16 21 | } 22 | 23 | fn pci_config_write_u16(bus: u8, device: u8, function: u8, offset: u8, value: u16) { 24 | let address = pci_config_address(bus, device, function, offset); 25 | port::write_u32(0xCF8, address); 26 | let old = port::read_u32(0xCFC); 27 | let shift = (offset & 2) * 8; 28 | let mask = !(0xFFFF << shift); 29 | let new = (old & mask) | ((value as u32) << shift); 30 | port::write_u32(0xCFC, new); 31 | } 32 | 33 | pub fn enable_bus_mastering(vendor_id: u16, device_id: u16) { 34 | for bus in 0..=255u8 { 35 | for device in 0..32u8 { 36 | for function in 0..8u8 { 37 | let id = pci_config_read_u32(bus, device, function, 0x00); 38 | if id == 0xFFFFFFFF { 39 | continue; 40 | } 41 | 42 | let found_vendor = (id & 0xFFFF) as u16; 43 | let found_device = ((id >> 16) & 0xFFFF) as u16; 44 | 45 | if found_vendor == vendor_id && found_device == device_id { 46 | let command = pci_config_read_u16(bus, device, function, 0x04); 47 | pci_config_write_u16(bus, device, function, 0x04, command | 0x0004); // Set bus master bit 48 | return; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/net/rtl8139.rs: -------------------------------------------------------------------------------- 1 | use crate::input::port; 2 | use crate::net::pci; 3 | 4 | pub const PCI_VENDOR_ID_REALTEK: u16 = 0x10EC; 5 | pub const PCI_DEVICE_ID_RTL8139: u16 = 0x8139; 6 | 7 | const RTL8139_IO_BASE: u16 = 0xC000; 8 | const NUM_TX_BUFFERS: usize = 4; 9 | 10 | static mut RX_BUFFER: [u8; 8192 + 16 + 1500] = [0; 8192 + 16 + 1500]; 11 | static mut RX_OFFSET: usize = 0; 12 | 13 | //#[repr(align(4))] 14 | static mut TX_BUFFERS: [[u8; 2048]; NUM_TX_BUFFERS] = [[0; 2048]; NUM_TX_BUFFERS]; 15 | static mut TX_INDEX: usize = 0; 16 | 17 | pub fn receive_frame(buf: &mut [u8]) -> Option { 18 | unsafe { 19 | let isr = port::read(RTL8139_IO_BASE + 0x3E); // ISR (Interrupt Status Register) 20 | if isr & 0x01 == 0 { 21 | return None; // No packet received 22 | } 23 | 24 | port::write_u8(RTL8139_IO_BASE + 0x3E, 0x01); // Acknowledge RX interrupt 25 | 26 | let offset = RX_OFFSET & 0x1FFF; 27 | //let rx_buf = &RX_BUFFER[offset..]; 28 | 29 | #[expect(static_mut_refs)] 30 | if let Some(rx_buf) = RX_BUFFER.get(offset..) { 31 | if rx_buf.len() < 4 { 32 | return None; 33 | } 34 | 35 | let _rx_status = u16::from_le_bytes([rx_buf[0], rx_buf[1]]); 36 | let len = u16::from_le_bytes([rx_buf[2], rx_buf[3]]) as usize; 37 | 38 | if len == 0 || len > buf.len() { 39 | return None; 40 | } 41 | 42 | if let Some(bf) = buf.get_mut(..len) { 43 | if let Some(rx) = rx_buf.get(4..4 + len) { 44 | bf.copy_from_slice(rx); 45 | } 46 | } 47 | //buf[..len].copy_from_slice(&rx_buf[4..4 + len]); 48 | 49 | RX_OFFSET = (RX_OFFSET + len + 4 + 3) & !3; // Align to 4 bytes 50 | 51 | // Tell the card the packet has been read 52 | port::write_u16(RTL8139_IO_BASE + 0x38, RX_OFFSET as u16); 53 | 54 | Some(len) 55 | } else { 56 | None 57 | } 58 | } 59 | } 60 | 61 | pub fn send_frame(data: &[u8]) -> Result<(), &'static str> { 62 | if data.len() > 2048 { 63 | return Err("Frame too large"); 64 | } 65 | 66 | unsafe { 67 | let tx_idx = TX_INDEX; 68 | let buf = &mut TX_BUFFERS[tx_idx]; 69 | buf[..data.len()].copy_from_slice(data); 70 | 71 | // Write buffer address 72 | let buf_phys = buf.as_ptr() as u32; 73 | let tx_addr_port = RTL8139_IO_BASE + 0x20 + (tx_idx * 4) as u16; 74 | port::write_u32(tx_addr_port, buf_phys); 75 | 76 | // Write length 77 | let tx_status_port = RTL8139_IO_BASE + 0x10 + (tx_idx * 4) as u16; 78 | port::write_u32(tx_status_port, data.len() as u32); 79 | 80 | // Advance TX index 81 | TX_INDEX = (TX_INDEX + 1) % NUM_TX_BUFFERS; 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | 88 | pub fn rtl8139_init() { 89 | // Enable bus mastering 90 | pci::enable_bus_mastering(PCI_VENDOR_ID_REALTEK, PCI_DEVICE_ID_RTL8139); 91 | 92 | let io_base = RTL8139_IO_BASE; 93 | 94 | // Reset 95 | port::write_u8(io_base + 0x37, 0x10); 96 | while port::read_u8(io_base + 0x37) & 0x10 != 0 {} 97 | 98 | // Set receive buffer address 99 | let rx_buf_addr = &raw const RX_BUFFER as u32; 100 | port::write_u32(io_base + 0x30, rx_buf_addr); 101 | 102 | // Enable RX and TX 103 | port::write_u8(io_base + 0x37, 0x0C); 104 | 105 | // Set receive config 106 | port::write_u32(io_base + 0x44, 0xf | (1 << 7)); // Accept broadcast | multicast | runt 107 | 108 | // Enable RX OK interrupts 109 | port::write_u16(io_base + 0x3C, 0x0005); 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/net/serial.rs: -------------------------------------------------------------------------------- 1 | use crate::input::port; 2 | //use x86_64; 3 | 4 | pub const COM1: u16 = 0x3F8; 5 | 6 | pub fn init() { 7 | port::write(COM1 + 1, 0x00); // Disable interrupts 8 | port::write(COM1 + 3, 0x80); // Enable DLAB 9 | port::write(COM1, 0x03); // Set divisor to 3 (38400 baud) 10 | port::write(COM1 + 1, 0x00); // High byte divisor 11 | port::write(COM1 + 3, 0x03); // 8 bits, no parity, one stop bit 12 | port::write(COM1 + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold 13 | port::write(COM1 + 4, 0x0B); // IRQs enabled, RTS/DSR set 14 | } 15 | 16 | /// Check if a byte is available from UART 17 | pub fn ready() -> bool { 18 | (port::read(COM1 + 5) & 1) != 0 19 | } 20 | 21 | /// Read a byte from UART 22 | pub fn read() -> u8 { 23 | port::read(COM1) 24 | } 25 | 26 | /// Write a byte to UART 27 | pub fn write(b: u8) { 28 | while (port::read(COM1 + 5) & 0x20) == 0 {} 29 | port::write(COM1, b); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/net/slip.rs: -------------------------------------------------------------------------------- 1 | const SLIP_END: u8 = 0xC0; 2 | const SLIP_ESC: u8 = 0xDB; 3 | const SLIP_ESC_END: u8 = 0xDC; 4 | const SLIP_ESC_ESC: u8 = 0xDD; 5 | 6 | /// Encodes a raw packet into a SLIP frame 7 | /// Returns how many bytes were written to `output` 8 | pub fn encode(input: &[u8], output: &mut [u8]) -> Option { 9 | let mut out_pos = 0; 10 | 11 | // Start with SLIP_END 12 | if out_pos >= output.len() { 13 | return None; 14 | } 15 | 16 | if let Some(p) = output.get_mut(out_pos) { 17 | *p = SLIP_END; 18 | } 19 | 20 | out_pos += 1; 21 | 22 | for &b in input { 23 | match b { 24 | SLIP_END => { 25 | if out_pos + 2 > output.len() { 26 | return None; 27 | } 28 | 29 | if let Some(p) = output.get_mut(out_pos) { 30 | *p = SLIP_ESC; 31 | } 32 | if let Some(p) = output.get_mut(out_pos + 1) { 33 | *p = SLIP_ESC_END; 34 | } 35 | 36 | out_pos += 2; 37 | } 38 | SLIP_ESC => { 39 | if out_pos + 2 > output.len() { 40 | return None; 41 | } 42 | 43 | if let Some(p) = output.get_mut(out_pos) { 44 | *p = SLIP_ESC; 45 | } 46 | if let Some(p) = output.get_mut(out_pos + 1) { 47 | *p = SLIP_ESC_ESC; 48 | } 49 | 50 | out_pos += 2; 51 | } 52 | _ => { 53 | if out_pos >= output.len() { 54 | return None; 55 | } 56 | 57 | if let Some(p) = output.get_mut(out_pos) { 58 | *p = b 59 | } 60 | 61 | out_pos += 1; 62 | } 63 | } 64 | } 65 | 66 | // End with SLIP_END 67 | if out_pos >= output.len() { 68 | return None; 69 | } 70 | 71 | if let Some(p) = output.get_mut(out_pos) { 72 | *p = SLIP_END; 73 | } 74 | 75 | out_pos += 1; 76 | 77 | Some(out_pos) 78 | } 79 | 80 | /// Decodes a SLIP frame into a raw packet 81 | /// Returns how many bytes were written to `output` 82 | pub fn decode(input: &[u8], output: &mut [u8]) -> Option { 83 | let mut out_pos = 0; 84 | let mut escape = false; 85 | 86 | for &b in input { 87 | match b { 88 | SLIP_END => { 89 | if out_pos > 0 { 90 | return Some(out_pos); 91 | } 92 | // Ignore empty ENDs at start 93 | } 94 | SLIP_ESC => { 95 | escape = true; 96 | } 97 | _ => { 98 | if escape { 99 | match b { 100 | SLIP_ESC_END => output.get_mut(out_pos)?.clone_from(&SLIP_END), 101 | SLIP_ESC_ESC => output.get_mut(out_pos)?.clone_from(&SLIP_ESC), 102 | _ => return None, // Protocol error 103 | } 104 | escape = false; 105 | } else { 106 | *output.get_mut(out_pos)? = b; 107 | } 108 | out_pos += 1; 109 | } 110 | } 111 | } 112 | 113 | None // Not finished yet 114 | } 115 | 116 | -------------------------------------------------------------------------------- /src/net/tcp.rs: -------------------------------------------------------------------------------- 1 | pub const FIN: u16 = 0x01; 2 | pub const SYN: u16 = 0x02; 3 | //pub const RST: u16 = 0x04; 4 | pub const PSH: u16 = 0x08; 5 | pub const ACK: u16 = 0x10; 6 | 7 | #[repr(C, packed)] 8 | pub struct TcpHeader { 9 | pub source_port: u16, 10 | pub dest_port: u16, 11 | pub seq_num: u32, 12 | pub ack_num: u32, 13 | pub data_offset_reserved_flags: u16, 14 | pub window_size: u16, 15 | pub checksum: u16, 16 | pub urgent_pointer: u16, 17 | } 18 | 19 | #[derive(PartialEq, Eq)] 20 | pub enum TcpState { 21 | Closed, 22 | Listen, 23 | SynReceived, 24 | Established, 25 | FinWait1, 26 | FinWait2, 27 | Closing, 28 | TimeWait, 29 | CloseWait, 30 | LastAck, 31 | } 32 | 33 | pub struct TcpConnection { 34 | pub state: TcpState, 35 | pub src_ip: [u8; 4], 36 | pub dst_ip: [u8; 4], 37 | pub src_port: u16, 38 | pub dst_port: u16, 39 | pub seq_num: u32, 40 | pub ack_num: u32, 41 | } 42 | 43 | #[allow(clippy::too_many_arguments)] 44 | pub fn create_packet( 45 | src_port: u16, 46 | dst_port: u16, 47 | seq_num: u32, 48 | ack_num: u32, 49 | flags: u16, 50 | window_size: u16, 51 | payload: &[u8], 52 | src_ip: [u8; 4], 53 | dst_ip: [u8; 4], 54 | out: &mut [u8], 55 | ) -> usize { 56 | let data_offset = 5u16 << 12; // 5 * 4 = 20 bytes, no options 57 | // 58 | let tcp_header = TcpHeader { 59 | source_port: src_port.to_be(), 60 | dest_port: dst_port.to_be(), 61 | seq_num: seq_num.to_be(), 62 | ack_num: ack_num.to_be(), 63 | data_offset_reserved_flags: (data_offset | flags).to_be(), 64 | window_size: window_size.to_be(), 65 | checksum: 0, 66 | urgent_pointer: 0, 67 | }; 68 | 69 | let header_bytes = unsafe { 70 | core::slice::from_raw_parts( 71 | &tcp_header as *const _ as *const u8, 72 | core::mem::size_of::(), 73 | ) 74 | }; 75 | 76 | if let Some(slice) = out.get_mut(..header_bytes.len()) { 77 | slice.copy_from_slice(header_bytes); 78 | } 79 | if let Some(slice) = out.get_mut(20..20 + payload.len()) { 80 | slice.copy_from_slice(payload); 81 | } 82 | 83 | let mut checksum: u16 = 0; 84 | 85 | if let Some(out_slice) = out.get_mut(..20 + payload.len()) { 86 | checksum = get_checksum(src_ip, dst_ip, out_slice); 87 | } 88 | 89 | if let Some(w) = out.get_mut(16) { 90 | *w = (checksum >> 8) as u8; 91 | } 92 | if let Some(w) = out.get_mut(17) { 93 | *w = (checksum & 0xff) as u8; 94 | } 95 | 96 | 20 + payload.len() 97 | } 98 | 99 | 100 | pub fn parse_packet(packet: &[u8]) -> Option<(TcpHeader, &[u8])> { 101 | if packet.len() < 20 { 102 | return None; 103 | } 104 | 105 | let header = unsafe { 106 | core::ptr::read_unaligned(packet.as_ptr() as *const TcpHeader) 107 | }; 108 | 109 | let data_offset = (u16::from_be(header.data_offset_reserved_flags) >> 12) * 4; 110 | if packet.len() < data_offset as usize { 111 | return None; 112 | } 113 | 114 | //let payload = &packet[data_offset as usize..]; 115 | let payload_slice = packet.get(data_offset as usize..).unwrap_or(&[]); 116 | Some((header, payload_slice)) 117 | } 118 | 119 | pub fn parse_flags(header: &TcpHeader) -> (bool, bool, bool, bool) { 120 | //let flags = header.data_offset_reserved_flags & 0x01FF; 121 | let flags = u16::from_be(header.data_offset_reserved_flags) & 0x01FF; 122 | 123 | let fin = flags & 0x001 != 0; 124 | let syn = flags & 0x002 != 0; 125 | let rst = flags & 0x004 != 0; 126 | let ack = flags & 0x010 != 0; 127 | (syn, ack, fin, rst) 128 | } 129 | 130 | pub fn get_checksum( 131 | src_ip: [u8; 4], 132 | dst_ip: [u8; 4], 133 | tcp_packet: &[u8], 134 | ) -> u16 { 135 | let mut sum: u32 = 0; 136 | 137 | // Pseudo-header: Source IP (4), Dest IP (4), zero (1), protocol (1), TCP length (2) 138 | 139 | if let Some(w1) = src_ip.first() { 140 | if let Some(w2) = src_ip.get(1) { 141 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 142 | } 143 | } 144 | if let Some(w1) = src_ip.get(2) { 145 | if let Some(w2) = src_ip.get(3) { 146 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 147 | } 148 | } 149 | 150 | if let Some(w1) = dst_ip.first() { 151 | if let Some(w2) = dst_ip.get(1) { 152 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 153 | } 154 | } 155 | if let Some(w1) = dst_ip.get(2) { 156 | if let Some(w2) = dst_ip.get(3) { 157 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 158 | } 159 | } 160 | 161 | sum += 0x0006u16 as u32; // Protocol: TCP = 6 162 | sum += tcp_packet.len() as u32; 163 | 164 | // Now include the TCP header + payload 165 | let mut i = 0; 166 | while i + 1 < tcp_packet.len() { 167 | // Skip checksum field at offset 16..18 168 | if i == 16 { 169 | i += 2; 170 | continue; 171 | } 172 | 173 | if let Some(w1) = tcp_packet.get(i) { 174 | if let Some(w2) = tcp_packet.get(i+1) { 175 | sum = sum.wrapping_add(u16::from_be_bytes([*w1, *w2]) as u32); 176 | } 177 | } 178 | i += 2; 179 | } 180 | 181 | if i < tcp_packet.len() { 182 | // Odd byte at the end 183 | if let Some(w) = tcp_packet.get(i) { 184 | sum = sum.wrapping_add(((*w as u16) << 8) as u32); 185 | } 186 | //let word = (tcp_packet[i] as u16) << 8; 187 | //sum = sum.wrapping_add(word as u32); 188 | } 189 | 190 | // Fold 32-bit sum to 16 bits 191 | while (sum >> 16) != 0 { 192 | sum = (sum & 0xffff) + (sum >> 16); 193 | } 194 | 195 | !(sum as u16) 196 | } 197 | 198 | -------------------------------------------------------------------------------- /src/net/udp.rs: -------------------------------------------------------------------------------- 1 | #[repr(C, packed)] 2 | pub struct UdpHeader { 3 | pub source_port: u16, 4 | pub dest_port: u16, 5 | pub length: u16, 6 | pub checksum: u16, 7 | } 8 | 9 | pub fn create_packet( 10 | _source_ip: [u8; 4], 11 | _dest_ip: [u8; 4], 12 | source_port: u16, 13 | dest_port: u16, 14 | payload: &[u8], 15 | out_buffer: &mut [u8], 16 | ) -> usize { 17 | let udp_len = 8 + payload.len(); // 8 bytes header + payload 18 | 19 | let header = UdpHeader { 20 | source_port: source_port.to_be(), 21 | dest_port: dest_port.to_be(), 22 | length: (udp_len as u16).to_be(), 23 | checksum: 0, // Temporary 0, we'll compute later 24 | }; 25 | 26 | // Copy header 27 | unsafe { 28 | let header_bytes = core::slice::from_raw_parts( 29 | &header as *const _ as *const u8, 30 | core::mem::size_of::(), 31 | ); 32 | if let Some(slice) = out_buffer.get_mut(..header_bytes.len()) { 33 | slice.copy_from_slice(header_bytes); 34 | } 35 | } 36 | 37 | // Copy payload 38 | if let Some(slice) = out_buffer.get_mut(8..8 + payload.len()) { 39 | slice.copy_from_slice(payload); 40 | } 41 | 42 | // Calculate checksum 43 | // TODO 44 | 45 | udp_len 46 | } 47 | 48 | pub fn parse_packet(packet: &[u8]) -> Option<(u16, u16, &[u8])> { 49 | if packet.len() < 8 { 50 | return None; 51 | } 52 | 53 | let (mut source_port, mut dest_port, mut length): (u16, u16, u16) = (0, 0, 0); 54 | 55 | if let Some(w1) = packet.first() { 56 | if let Some(w2) = packet.get(1) { 57 | source_port = u16::from_be_bytes([*w1, *w2]); 58 | } 59 | } 60 | if let Some(w1) = packet.get(2) { 61 | if let Some(w2) = packet.get(3) { 62 | dest_port = u16::from_be_bytes([*w1, *w2]); 63 | } 64 | } 65 | if let Some(w1) = packet.get(4) { 66 | if let Some(w2) = packet.get(5) { 67 | length = u16::from_be_bytes([*w1, *w2]); 68 | } 69 | } 70 | /*if let Some(w1) = packet.get(6) { 71 | if let Some(w2) = packet.get(7) { 72 | _checksum = u16::from_be_bytes([*w1, *w2]); 73 | } 74 | }*/ 75 | 76 | //let source_port = u16::from_be_bytes([packet[0], packet[1]]); 77 | //let dest_port = u16::from_be_bytes([packet[2], packet[3]]); 78 | //let length = u16::from_be_bytes([packet[4], packet[5]]); 79 | //let _checksum = u16::from_be_bytes([packet[6], packet[7]]); 80 | 81 | if packet.len() < length as usize { 82 | return None; 83 | } 84 | 85 | let payload_slice = packet.get(8..length as usize).unwrap_or(&[]); 86 | //let payload = &packet[8..length as usize]; 87 | Some((source_port, dest_port, payload_slice)) 88 | } 89 | 90 | /// Calculate UDP checksum including IPv4 pseudo-header 91 | pub fn get_checksum( 92 | src_ip: [u8; 4], 93 | dst_ip: [u8; 4], 94 | udp_packet: &[u8], // Whole UDP header + data 95 | ) -> u16 { 96 | let mut sum = 0u32; 97 | 98 | // Pseudo-header 99 | 100 | if let Some(w1) = src_ip.first() { 101 | if let Some(w2) = src_ip.get(1) { 102 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 103 | } 104 | } 105 | if let Some(w1) = src_ip.get(2) { 106 | if let Some(w2) = src_ip.get(3) { 107 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 108 | } 109 | } 110 | 111 | if let Some(w1) = dst_ip.first() { 112 | if let Some(w2) = dst_ip.get(1) { 113 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 114 | } 115 | } 116 | if let Some(w1) = dst_ip.get(2) { 117 | if let Some(w2) = dst_ip.get(3) { 118 | sum += u16::from_be_bytes([*w1, *w2]) as u32; 119 | } 120 | } 121 | 122 | sum += 0x11u8 as u32; // Protocol (UDP = 17 decimal) 123 | sum += udp_packet.len() as u32; // UDP length 124 | 125 | // UDP header + payload 126 | let mut i = 0; 127 | while i + 1 < udp_packet.len() { 128 | if let Some(w1) = udp_packet.get(i) { 129 | if let Some(w2) = udp_packet.get(i+1) { 130 | sum = sum.wrapping_add( u16::from_be_bytes([*w1, *w2]) as u32 ); 131 | } 132 | } 133 | i += 2; 134 | } 135 | 136 | if i < udp_packet.len() { 137 | if let Some(w) = udp_packet.get(i) { 138 | sum = sum.wrapping_add(((*w as u16) << 8) as u32); 139 | } 140 | } 141 | 142 | // Fold carries 143 | while (sum >> 16) != 0 { 144 | sum = (sum & 0xFFFF) + (sum >> 16); 145 | } 146 | 147 | !(sum as u16) 148 | } 149 | 150 | -------------------------------------------------------------------------------- /src/task/context.asm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | section .text 4 | global context_switch 5 | 6 | ; rdi = old_regs_ptr, rsi = new_regs_ptr 7 | context_switch: 8 | test rsi, rsi 9 | jz .fail 10 | mov rax, [rsi + 0x78] 11 | test rax, rax 12 | jz .fail 13 | 14 | ; Save all GPRs into *old 15 | mov [rdi + 0x00], r15 16 | mov [rdi + 0x08], r14 17 | mov [rdi + 0x10], r13 18 | mov [rdi + 0x18], r12 19 | mov [rdi + 0x20], r11 20 | mov [rdi + 0x28], r10 21 | mov [rdi + 0x30], r9 22 | mov [rdi + 0x38], r8 23 | ;mov [rdi + 0x40], rdi 24 | ;mov [rdi + 0x48], rsi 25 | mov [rdi + 0x50], rbp 26 | mov [rdi + 0x58], rdx 27 | mov [rdi + 0x60], rcx 28 | mov [rdi + 0x68], rbx 29 | mov [rdi + 0x70], rax 30 | 31 | ; Catch the RIP! 32 | call .save_rip 33 | 34 | .save_rip: 35 | pop rax 36 | mov [r8 + 0x78], rax 37 | 38 | mov rax, rsp 39 | mov [r8 + 0x90], rax 40 | 41 | pushfq 42 | pop rax 43 | mov [r8 + 0x88], rax 44 | 45 | ; Load all GPRs from *new 46 | mov r15, [rsi + 0x00] 47 | mov r14, [rsi + 0x08] 48 | mov r13, [rsi + 0x10] 49 | mov r12, [rsi + 0x18] 50 | mov r11, [rsi + 0x20] 51 | mov r10, [rsi + 0x28] 52 | mov r9 , [rsi + 0x30] 53 | mov r8 , [rsi + 0x38] 54 | mov rdi, [rsi + 0x40] 55 | mov rbp, [rsi + 0x50] 56 | mov rdx, [rsi + 0x58] 57 | mov rcx, [rsi + 0x60] 58 | mov rbx, [rsi + 0x68] 59 | mov rax, [rsi + 0x70] 60 | 61 | mov r10, [rsi + 0x78] ; new.rip 62 | mov r11, [rsi + 0x80] ; new.cs 63 | mov r12, [rsi + 0x88] ; new.rflags 64 | mov r13, [rsi + 0x90] ; new.rsp 65 | mov r14, [rsi + 0x98] ; new.ss 66 | 67 | ;mov rsi, [rsi + 0x48] 68 | 69 | ; Build the return frame on the stack for iretq 70 | push r14 ; SS 71 | push r13 ; RSP 72 | push r12 ; RFLAGS 73 | push r11 ; CS 74 | push r10 ; RIP 75 | 76 | ; Do the far return 77 | iretq 78 | 79 | .fail: 80 | hlt 81 | 82 | -------------------------------------------------------------------------------- /src/task/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pipe; 2 | pub mod process; 3 | 4 | pub struct Task { 5 | pub regs: process::Registers, 6 | //pub stack: &'static mut [u8], 7 | pub stack: u64, 8 | pub is_done: bool, 9 | } 10 | 11 | const MAX_TASKS: usize = 4; 12 | 13 | #[unsafe(no_mangle)] 14 | static mut TASKS: [Option; MAX_TASKS] = [None, None, None, None]; 15 | static mut CURRENT_TASK: usize = 0; 16 | 17 | #[unsafe(no_mangle)] 18 | static STACKS: [u64; 4] = [0x790000, 0x780000, 0x770000, 0x760000]; 19 | 20 | #[unsafe(no_mangle)] 21 | extern "C" fn new_stack() -> u64 { 22 | //extern "C" fn new_stack() -> &'static mut [u8] { 23 | //static mut STACKS: [[u8; 4096]; MAX_TASKS] = [[0; 4096]; MAX_TASKS]; 24 | static mut NEXT: usize = 0; 25 | 26 | unsafe { 27 | let s = STACKS[NEXT]; 28 | 29 | let ptr = s as *mut u8; 30 | for i in 0..4096 { 31 | ptr.add(i).write_volatile(0); 32 | } 33 | 34 | NEXT += 1; 35 | s 36 | } 37 | } 38 | 39 | #[expect(clippy::fn_to_numeric_cast)] 40 | fn add_task(entry: extern "C" fn()) { 41 | let stack = new_stack(); 42 | let rsp = stack + 0x90000 - 8; // top of stack 43 | 44 | let regs = process::Registers { 45 | r15: 0, 46 | r14: 0, 47 | r13: 0, 48 | r12: 0, 49 | r11: 0, 50 | r10: 0, 51 | r9: 0, 52 | r8: 0, 53 | rdi: 0, 54 | rsi: 0, 55 | rbp: 0, 56 | rdx: 0, 57 | rcx: 0, 58 | rbx: 0, 59 | rax: 0, 60 | rip: entry as u64, 61 | cs: 0x08, 62 | rflags: 0x202, 63 | rsp, 64 | ss: 0x10, 65 | }; 66 | 67 | unsafe { 68 | #[expect(static_mut_refs)] 69 | for slot in TASKS.iter_mut() { 70 | if slot.is_none() { 71 | *slot = Some(Task { regs, stack, is_done: false }); 72 | break; 73 | } 74 | } 75 | } 76 | } 77 | 78 | extern "C" { 79 | fn context_switch(old: *mut process::Registers, new: *const process::Registers); 80 | //fn context_switch_kern(old: *mut Registers, new: *const Registers); 81 | } 82 | 83 | #[no_mangle] 84 | pub fn status() { 85 | unsafe { 86 | print!("RUNNING TASKS\n"); 87 | #[expect(static_mut_refs)] 88 | for (i, task) in TASKS.iter().enumerate().take(MAX_TASKS) { 89 | if let Some(task) = task { 90 | if task.is_done { 91 | continue; 92 | } 93 | 94 | print!("Task "); 95 | printn!(i); 96 | print!("\n"); 97 | } 98 | } 99 | } 100 | } 101 | 102 | #[no_mangle] 103 | pub extern "C" fn end_task(task_id: usize) { 104 | unsafe { 105 | if task_id != 0xff { 106 | if let Some(task) = &mut TASKS[task_id] { 107 | if task.is_done { 108 | task.is_done = true; 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | #[no_mangle] 116 | pub extern "C" fn schedule() { 117 | unsafe { 118 | // Get current task slot mutable reference 119 | let old_task_opt = &mut TASKS[CURRENT_TASK]; 120 | 121 | // Start searching for the next runnable task 122 | let mut next = CURRENT_TASK; 123 | 124 | //for _ in 0..MAX_TASKS { 125 | next = (next + 1) % 2; 126 | 127 | // Check if next task exists and is not done 128 | if let Some(next_task) = &TASKS[next] { 129 | if !next_task.is_done { 130 | // Check that old task exists as well 131 | if let Some(old_task) = old_task_opt.as_mut() { 132 | rprint!("[SCHEDULE] Switching from task "); 133 | rprintn!(CURRENT_TASK); 134 | rprint!(" to task "); 135 | rprintn!(next); 136 | rprint!("\n"); 137 | 138 | rprint!("Task "); 139 | rprintn!(CURRENT_TASK); 140 | rprint!(" registers:\nRIP: "); 141 | rprintn!(old_task.regs.rip); 142 | rprint!("\nRSP: "); 143 | rprintn!(old_task.regs.rsp); 144 | rprint!("\nSS: "); 145 | rprintn!(old_task.regs.ss); 146 | rprint!("\n\n"); 147 | 148 | rprint!("Task "); 149 | rprintn!(next); 150 | rprint!(" registers:\nRIP: "); 151 | rprintn!(next_task.regs.rip); 152 | rprint!("\nRSP: "); 153 | rprintn!(next_task.regs.rsp); 154 | rprint!("\nSS: "); 155 | rprintn!(next_task.regs.ss); 156 | rprint!("\n\n"); 157 | 158 | // Update current task index 159 | CURRENT_TASK = next; 160 | 161 | // Perform the context switch with valid pointers 162 | context_switch( 163 | &mut old_task.regs as *mut process::Registers, 164 | &next_task.regs as *const process::Registers, 165 | ); 166 | 167 | //break; 168 | } else { 169 | //println!("[SCHEDULE] Current task "); 170 | //printn!(CURRENT_TASK); 171 | //println!(" is None") 172 | } 173 | } 174 | } else { 175 | //print!("[SCHEDULE] Task slot "); 176 | //printn!(next); 177 | //println!(" is None") 178 | //} 179 | } 180 | } 181 | } 182 | 183 | #[unsafe(no_mangle)] 184 | static mut PIPE: Option = None; 185 | 186 | #[unsafe(no_mangle)] 187 | extern "C" fn kern_task1() { 188 | let mut ch: u8 = 0; 189 | 190 | //println!("[TASK 1]: Start"); 191 | 192 | loop { 193 | unsafe { 194 | #[expect(static_mut_refs)] 195 | if let Some(pipe) = PIPE.as_mut() { 196 | ch += 1; 197 | pipe.write(ch); 198 | 199 | if ch.is_multiple_of(26) { 200 | ch = 0; 201 | } 202 | 203 | for _ in 0..10_000 { 204 | core::arch::asm!("nop"); 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | #[unsafe(no_mangle)] 212 | extern "C" fn kern_task2() { 213 | //println!("[TASK 2]: Start"); 214 | 215 | loop { 216 | unsafe { 217 | #[expect(static_mut_refs)] 218 | if let Some(pipe) = PIPE.as_mut() { 219 | let ch = pipe.read(); 220 | 221 | if ch == 0x00 { 222 | continue; 223 | } 224 | 225 | rprintb!( &[ch % 26 + 65] ); 226 | } 227 | } 228 | } 229 | } 230 | 231 | #[no_mangle] 232 | #[unsafe(link_section = ".user_task.task1")] 233 | extern "C" fn user_task1() { 234 | #[unsafe(link_section = ".user_task.data1")] 235 | static MSG1: [u8; 18]= *b"[TASK 1]: bonjour\n"; 236 | 237 | loop { 238 | //print!("[TASK 1]: bonjour\n"); 239 | 240 | unsafe { 241 | core::arch::asm!( 242 | "mov rdi, {0}", 243 | "mov rsi, {1:r}", 244 | "mov rax, 0x10", 245 | "int $0x7f", 246 | in(reg) MSG1.as_ptr(), 247 | in(reg) MSG1.len(), 248 | ); 249 | } 250 | 251 | for _ in 0..50_000_000 { 252 | unsafe { core::arch::asm!("nop"); } 253 | } 254 | } 255 | } 256 | 257 | #[no_mangle] 258 | #[unsafe(link_section = ".user_task.task2")] 259 | extern "C" fn user_task2() { 260 | #[unsafe(link_section = ".user_task.data2")] 261 | static MSG2: [u8; 17] = *b"[TASK 2]: wowerz\n"; 262 | 263 | loop { 264 | //print!("[TASK 1]: bonjour\n"); 265 | 266 | unsafe { 267 | core::arch::asm!( 268 | "mov rdi, {0}", 269 | "mov rsi, {1:r}", 270 | "mov rax, 0x10", 271 | "int $0x7f", 272 | in(reg) MSG2.as_ptr(), 273 | in(reg) MSG2.len(), 274 | ); 275 | } 276 | 277 | for _ in 0..50_000_000 { 278 | unsafe { core::arch::asm!("nop"); } 279 | } 280 | } 281 | } 282 | 283 | pub fn run_scheduler() { 284 | unsafe { 285 | PIPE = Some(pipe::Pipe::new(0)); 286 | } 287 | 288 | add_task(kern_task1); 289 | add_task(kern_task2); 290 | //schedule(); 291 | } 292 | 293 | -------------------------------------------------------------------------------- /src/task/pipe.rs: -------------------------------------------------------------------------------- 1 | const MAX_BUFFER_SIZE: usize = 14096; 2 | 3 | #[derive(Copy,Clone)] 4 | #[repr(C, packed)] 5 | pub struct Pipe { 6 | buffer: [u8; MAX_BUFFER_SIZE], 7 | id: u64, 8 | pub read_pos: usize, 9 | pub write_pos: usize, 10 | } 11 | 12 | impl Pipe { 13 | pub fn new(id: u64) -> Self { 14 | Pipe{ 15 | buffer: [0u8; MAX_BUFFER_SIZE], 16 | id, 17 | read_pos: 0, 18 | write_pos: 0, 19 | } 20 | } 21 | 22 | pub fn read(&mut self) -> u8 { 23 | if self.read_pos == self.write_pos { 24 | return 0x00; 25 | } 26 | 27 | let output = self.buffer[self.read_pos]; 28 | 29 | self.read_pos += 1; 30 | self.read_pos %= MAX_BUFFER_SIZE; 31 | 32 | /*if self.read_pos < self.write_pos { 33 | if let Some(buf) = self.buffer.get(self.read_pos..self.write_pos) { 34 | output.copy_from_slice(buf); 35 | } 36 | }*/ 37 | 38 | output 39 | } 40 | 41 | pub fn write(&mut self, ch: u8) { 42 | self.buffer[self.write_pos] = ch; 43 | 44 | self.write_pos += 1; 45 | self.write_pos %= MAX_BUFFER_SIZE; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/task/process.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | pub struct Registers { 3 | pub(super) r15: u64, 4 | pub(super) r14: u64, 5 | pub(super) r13: u64, 6 | pub(super) r12: u64, 7 | pub(super) r11: u64, 8 | pub(super) r10: u64, 9 | pub(super) r9: u64, 10 | pub(super) r8: u64, 11 | pub(super) rdi: u64, 12 | pub(super) rsi: u64, 13 | pub(super) rbp: u64, 14 | pub(super) rdx: u64, 15 | pub(super) rcx: u64, 16 | pub(super) rbx: u64, 17 | pub(super) rax: u64, 18 | 19 | // iretq stack frame 20 | pub(super) rip: u64, 21 | pub(super) cs: u64, 22 | pub(super) rflags: u64, 23 | pub(super) rsp: u64, 24 | pub(super) ss: u64, 25 | } 26 | 27 | pub struct Process { 28 | regs: Registers, 29 | kernel_stack: [u8; 4096], 30 | //pid: u16, 31 | //page_tables: ... 32 | } 33 | 34 | static mut PROCESSES: [Option; 2] = [None, None]; 35 | static mut CURRENT_PID: usize = 0; 36 | 37 | pub fn schedule() { 38 | unsafe { 39 | #[expect(static_mut_refs)] 40 | let next_pid = (CURRENT_PID + 1) % PROCESSES.len(); 41 | 42 | if let Some(next_proc) = &mut PROCESSES[next_pid] { 43 | context_switch(&mut PROCESSES[CURRENT_PID].as_mut().unwrap().regs, &next_proc.regs); 44 | CURRENT_PID = next_pid; 45 | } 46 | } 47 | } 48 | 49 | extern "C" { 50 | fn context_switch(old_regs: *mut Registers, new_regs: *const Registers); 51 | } 52 | 53 | fn create_process(entry_point: u64, process_stack_top: u64) -> Process { 54 | Process { 55 | regs: Registers { 56 | r15: 0, r14: 0, r13: 0, r12: 0, 57 | r11: 0, r10: 0, r9: 0, r8: 0, 58 | rdi: 0, rsi: 0, rbp: 0, rdx: 0, 59 | rcx: 0, rbx: 0, rax: 0, 60 | rip: entry_point, 61 | cs: 0x1B, // user code segment selector (RPL=3) 62 | rflags: 0x202, // IF=1 (interrupt flag) 63 | rsp: process_stack_top, 64 | ss: 0x23, // user stack segment selector 65 | }, 66 | kernel_stack: [0; 4096], 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/time/acpi.rs: -------------------------------------------------------------------------------- 1 | const ACPI_PM_TIMER_PORT: u16 = 0x408; // TODO: Hardcoded 2 | const PM_TIMER_FREQUENCY_HZ: u64 = 3_579_545; // Hz 3 | 4 | static mut LAST_TICKS: u32 = 0; 5 | static mut UPTIME_TICKS: u64 = 0; 6 | 7 | pub fn read_pm_timer() -> u32 { 8 | let value: u32; 9 | unsafe { 10 | core::arch::asm!( 11 | "in eax, dx", 12 | in("dx") ACPI_PM_TIMER_PORT, 13 | out("eax") value, 14 | ); 15 | } 16 | value & 0xFFFFFF // 24 bits 17 | } 18 | 19 | pub fn update_uptime() { 20 | let current = read_pm_timer(); 21 | unsafe { 22 | if current < LAST_TICKS { 23 | // Wrapped 24 | UPTIME_TICKS += (0xFFFFFF - LAST_TICKS + current) as u64; 25 | } else { 26 | UPTIME_TICKS += (current - LAST_TICKS) as u64; 27 | } 28 | LAST_TICKS = current; 29 | } 30 | } 31 | 32 | pub fn get_uptime_seconds() -> u64 { 33 | update_uptime(); 34 | unsafe { UPTIME_TICKS / PM_TIMER_FREQUENCY_HZ } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/time/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod acpi; 2 | pub mod rtc; 3 | -------------------------------------------------------------------------------- /src/time/rtc.rs: -------------------------------------------------------------------------------- 1 | pub fn read_rtc_register(reg: u8) -> u8 { 2 | unsafe { 3 | // Tell CMOS what address is wanted 4 | core::arch::asm!( 5 | "out dx, al", 6 | in("dx") 0x70, 7 | in("al") reg, 8 | ); 9 | 10 | // Read the data 11 | let value: u8; 12 | core::arch::asm!( 13 | "in al, dx", 14 | in("dx") 0x71, 15 | out("al") value, 16 | ); 17 | value 18 | } 19 | } 20 | 21 | pub fn read_rtc_full() -> (u16, u8, u8, u8, u8, u8) { 22 | let mut seconds; 23 | let mut minutes; 24 | let mut hours; 25 | let mut day; 26 | let mut month; 27 | let mut year; 28 | let mut century = 20; // Fallback if no CMOS reg 0x32 29 | 30 | loop { 31 | if (read_rtc_register(0x0A) & 0x80) == 0 { 32 | seconds = read_rtc_register(0x00); 33 | minutes = read_rtc_register(0x02); 34 | hours = read_rtc_register(0x04); 35 | day = read_rtc_register(0x07); 36 | month = read_rtc_register(0x08); 37 | year = read_rtc_register(0x09); 38 | let maybe_century = read_rtc_register(0x32); 39 | 40 | if maybe_century != 0 { 41 | century = bcd_to_bin(maybe_century) as u16; 42 | } 43 | 44 | break; 45 | } 46 | } 47 | 48 | if (read_rtc_register(0x0B) & 0x04) == 0 { 49 | seconds = bcd_to_bin(seconds); 50 | minutes = bcd_to_bin(minutes); 51 | hours = bcd_to_bin(hours); 52 | day = bcd_to_bin(day); 53 | month = bcd_to_bin(month); 54 | year = bcd_to_bin(year); 55 | } 56 | 57 | let full_year = century * 100 + year as u16; 58 | 59 | (full_year, month, day, hours, minutes, seconds) 60 | } 61 | 62 | fn bcd_to_bin(value: u8) -> u8 { 63 | (value & 0x0F) + ((value >> 4) * 10) 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/tui/app.rs: -------------------------------------------------------------------------------- 1 | use crate::input::keyboard::{keyboard_read_scancode}; 2 | use crate::tui::{screen::Screen, widget::Widget}; 3 | 4 | #[derive(Clone,Copy)] 5 | pub enum TuiEvent { 6 | Key(u8), 7 | Quit, 8 | } 9 | 10 | pub struct TuiApp<'a> { 11 | pub root: Option<&'a mut dyn Widget>, 12 | } 13 | 14 | impl<'a> TuiApp<'a> { 15 | pub fn new() -> Self { 16 | Self { root: None } 17 | } 18 | 19 | pub fn set_root(&mut self, root: &'a mut dyn Widget) { 20 | self.root = Some(root); 21 | } 22 | 23 | pub fn run(&mut self) { 24 | Screen::clear(0x07); 25 | if let Some(root) = &mut self.root { 26 | root.render(&Screen, 0, 0); 27 | } 28 | 29 | loop { 30 | let scancode = keyboard_read_scancode(); 31 | 32 | let event = match scancode { 33 | 0x01 => TuiEvent::Quit, // ESC 34 | _ => TuiEvent::Key(scancode), 35 | }; 36 | 37 | if let Some(root) = &mut self.root { 38 | root.handle_event(event); 39 | } 40 | 41 | if let TuiEvent::Quit = event { 42 | break; 43 | } 44 | 45 | Screen::clear(0x07); 46 | if let Some(root) = &mut self.root { 47 | root.render(&Screen, 0, 0); 48 | } 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/tui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | pub mod screen; 3 | pub mod widget; 4 | -------------------------------------------------------------------------------- /src/tui/screen.rs: -------------------------------------------------------------------------------- 1 | pub struct Screen; 2 | 3 | impl Screen { 4 | const VGA_BUFFER: *mut u8 = 0xB8000 as *mut u8; 5 | const WIDTH: usize = 80; 6 | const HEIGHT: usize = 25; 7 | 8 | pub fn write_char(x: usize, y: usize, chr: u8, attr: u8) { 9 | let offset = 2 * (y * Self::WIDTH + x); 10 | unsafe { 11 | core::ptr::write_volatile(Self::VGA_BUFFER.add(offset), chr); 12 | core::ptr::write_volatile(Self::VGA_BUFFER.add(offset + 1), attr); 13 | } 14 | } 15 | 16 | pub fn clear(attr: u8) { 17 | for y in 0..Self::HEIGHT { 18 | for x in 0..Self::WIDTH { 19 | Self::write_char(x, y, b' ', attr); 20 | } 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/tui/widget.rs: -------------------------------------------------------------------------------- 1 | use crate::tui::{screen::Screen, app::TuiEvent}; 2 | 3 | pub trait Widget { 4 | fn render(&mut self, screen: &Screen, offset_x: usize, offset_y: usize); 5 | fn handle_event(&mut self, event: TuiEvent); 6 | } 7 | 8 | // 9 | // LABEL 10 | // 11 | 12 | pub struct Label { 13 | pub x: usize, 14 | pub y: usize, 15 | pub text: &'static str, 16 | pub attr: u8, 17 | } 18 | 19 | impl Widget for Label { 20 | fn render(&mut self, _screen: &Screen, offset_x: usize, offset_y: usize) { 21 | let _x = offset_x + self.x; 22 | let _y = offset_y + self.y; 23 | for (i, byte) in self.text.bytes().enumerate() { 24 | Screen::write_char(offset_x + i, offset_y, byte, self.attr); 25 | } 26 | } 27 | 28 | fn handle_event(&mut self, _event: TuiEvent) {} 29 | } 30 | 31 | // 32 | // WINDOW 33 | // 34 | 35 | pub struct Window<'a> { 36 | pub x: usize, 37 | pub y: usize, 38 | pub w: usize, 39 | pub h: usize, 40 | pub title: Option<&'static str>, 41 | pub child: Option<&'a mut dyn Widget>, 42 | } 43 | 44 | impl<'a> Widget for Window<'a> { 45 | fn render(&mut self, screen: &Screen, offset_x: usize, offset_y: usize) { 46 | let attr = 0x4F; 47 | 48 | // Corners 49 | Screen::write_char(self.x, self.y, 0xC9, attr); 50 | Screen::write_char(self.x + self.w - 1, self.y, 0xBB, attr); 51 | Screen::write_char(self.x, self.y + self.h - 1, 0xC8, attr); 52 | Screen::write_char(self.x + self.w - 1, self.y + self.h - 1, 0xBC, attr); 53 | 54 | // Edges 55 | for i in 1..(self.w - 1) { 56 | Screen::write_char(self.x + i, self.y, 0xCD, attr); 57 | Screen::write_char(self.x + i, self.y + self.h - 1, 0xCD, attr); 58 | } 59 | 60 | for i in 1..(self.h - 1) { 61 | Screen::write_char(self.x, self.y + i, 0xBA, attr); 62 | Screen::write_char(self.x + self.w - 1, self.y + i, 0xBA, attr); 63 | } 64 | 65 | // Title 66 | if let Some(t) = self.title { 67 | let start = self.x + (self.w - t.len()) / 2; 68 | for (i, byte) in t.bytes().enumerate() { 69 | Screen::write_char(start + i, self.y, byte, 0x1E); 70 | } 71 | } 72 | 73 | //let base_x = offset_x + self.x; 74 | //let base_y = offset_y + self.y; 75 | 76 | // Render child widget 77 | if let Some(child) = &mut self.child { 78 | child.render(screen, offset_x, offset_y); 79 | } 80 | } 81 | 82 | fn handle_event(&mut self, event: TuiEvent) { 83 | if let Some(child) = &mut self.child { 84 | child.handle_event(event); 85 | } 86 | } 87 | } 88 | 89 | // 90 | // CONTAINER 91 | // 92 | 93 | pub struct Container<'a> { 94 | pub x: usize, 95 | pub y: usize, 96 | pub children: [&'a mut dyn Widget; 3], 97 | } 98 | 99 | impl<'a> Widget for Container<'a> { 100 | fn render(&mut self, screen: &Screen, offset_x: usize, offset_y: usize) { 101 | let base_x = offset_x + self.x; 102 | let base_y = offset_y + self.y; 103 | 104 | for (i, child) in self.children.iter_mut().enumerate() { 105 | let dy = i * 2; 106 | let mut offset_child = OffsetWidget { 107 | widget: *child, 108 | dx: self.x, 109 | dy: self.y + dy, 110 | }; 111 | offset_child.render(screen, base_x, base_y); 112 | } 113 | } 114 | 115 | fn handle_event(&mut self, event: TuiEvent) { 116 | for child in self.children.iter_mut() { 117 | child.handle_event(event); 118 | } 119 | } 120 | } 121 | 122 | pub struct OffsetWidget<'a> { 123 | pub widget: &'a mut dyn Widget, 124 | pub dx: usize, 125 | pub dy: usize, 126 | } 127 | 128 | impl<'a> Widget for OffsetWidget<'a> { 129 | fn render(&mut self, screen: &Screen, _offset_x: usize, _offset_y: usize) { 130 | self.widget.render(screen, self.dx, self.dy); 131 | // TODO: Offset math amd render 132 | } 133 | 134 | fn handle_event(&mut self, event: TuiEvent) { 135 | self.widget.handle_event(event); 136 | } 137 | } 138 | 139 | -------------------------------------------------------------------------------- /src/vga/buffer.rs: -------------------------------------------------------------------------------- 1 | pub const VGA_BUFFER: *mut u8 = 0xb8000 as *mut u8; 2 | 3 | pub const WIDTH: usize = 80; 4 | pub const HEIGHT: usize = 25; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub enum Color { 8 | Black = 0x00, 9 | Blue = 0x09, 10 | White = 0x0f, 11 | Green = 0xa, 12 | Cyan = 0xb, 13 | Red = 0xc, 14 | Pink = 0xd, 15 | Yellow = 0xe, 16 | } 17 | -------------------------------------------------------------------------------- /src/vga/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod buffer; 2 | pub mod screen; 3 | pub mod write; 4 | 5 | -------------------------------------------------------------------------------- /src/vga/screen.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | use crate::vga::buffer; 3 | 4 | pub fn clear(vga_index: &mut isize) { 5 | unsafe { 6 | for row in 0..buffer::HEIGHT { 7 | for col in 0..buffer::WIDTH { 8 | let idx = (row * buffer::WIDTH + col) * 2; 9 | let mut offset: isize = idx as isize; 10 | 11 | *buffer::VGA_BUFFER.offset(offset) = b' '; // Character byte 12 | offset += 1; 13 | *buffer::VGA_BUFFER.offset(offset) = 0x07; // Attribute byte 14 | } 15 | } 16 | 17 | *vga_index = 0; 18 | } 19 | } 20 | 21 | pub fn scroll_at(vga_index: &mut isize, height: &mut isize) { 22 | if *height == 0 { 23 | *height = buffer::HEIGHT as isize; 24 | } 25 | 26 | if (*vga_index / 2) / (buffer::WIDTH as isize) < *height { 27 | return; 28 | } 29 | 30 | unsafe { 31 | let row_size = buffer::WIDTH * 2; // Bytes per row 32 | let _screen_size = row_size * buffer::HEIGHT; 33 | 34 | // Copy all rows up one line: from row 1 to row 0 35 | ptr::copy( 36 | buffer::VGA_BUFFER.add(row_size), // Start of row 1 37 | buffer::VGA_BUFFER, // Start of row 0 38 | row_size * (buffer::HEIGHT - 1), // total bytes of 24 rows 39 | ); 40 | 41 | let last_line_offset = row_size * (buffer::HEIGHT - 1); 42 | let last_line_ptr = buffer::VGA_BUFFER.add(last_line_offset); 43 | 44 | for i in 0..buffer::WIDTH { 45 | *last_line_ptr.add(i * 2) = b' '; 46 | *last_line_ptr.add(i * 2 + 1) = 0x07; 47 | } 48 | } 49 | 50 | *vga_index = (*height - 1) * buffer::WIDTH as isize * 2; 51 | } 52 | 53 | pub fn scroll(vga_index: &mut isize) { 54 | if (*vga_index / 2) / (buffer::WIDTH as isize) < (buffer::HEIGHT as isize) { 55 | return; 56 | } 57 | 58 | unsafe { 59 | let row_size = buffer::WIDTH * 2; // Bytes per row 60 | let _screen_size = row_size * buffer::HEIGHT; 61 | 62 | // Copy all rows up one line: from row 1 to row 0 63 | ptr::copy( 64 | buffer::VGA_BUFFER.add(row_size), // Start of row 1 65 | buffer::VGA_BUFFER, // Start of row 0 66 | row_size * (buffer::HEIGHT - 1), // Total bytes of 24 rows 67 | ); 68 | 69 | let last_line_offset = row_size * (buffer::HEIGHT - 1); 70 | let last_line_ptr = buffer::VGA_BUFFER.add(last_line_offset); 71 | 72 | for i in 0..buffer::WIDTH { 73 | *last_line_ptr.add(i * 2) = b' '; 74 | *last_line_ptr.add(i * 2 + 1) = 0x07; // Light gray on black 75 | } 76 | } 77 | 78 | *vga_index = (buffer::HEIGHT as isize - 1) * buffer::WIDTH as isize * 2; 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/vga/write.rs: -------------------------------------------------------------------------------- 1 | use crate::vga::buffer; 2 | use crate::vga::screen; 3 | 4 | pub fn number(vga_index: &mut isize, num: u64) { 5 | let mut buf = [0u8; 20]; 6 | let mut i = buf.len(); 7 | 8 | if num == 0 { 9 | string(vga_index, b"0", buffer::Color::White); 10 | return; 11 | } 12 | 13 | let mut n = num; 14 | 15 | while n > 0 { 16 | i -= 1; 17 | if let Some(b) = buf.get_mut(i) { 18 | *b = b'0' + (n % 10) as u8; 19 | } 20 | n /= 10; 21 | } 22 | 23 | string(vga_index, buf.get(i..).unwrap_or(&[]) as &[u8], buffer::Color::White); 24 | } 25 | 26 | /// Write a whole string to screen 27 | pub fn string(vga_index: &mut isize, string: &[u8], color: buffer::Color) { 28 | screen::scroll(vga_index); 29 | 30 | for &b in string { 31 | if b == b'\n' { 32 | newline(vga_index); 33 | continue; 34 | } 35 | 36 | byte(vga_index, b, color); 37 | } 38 | } 39 | 40 | pub fn byte(vga_index: &mut isize, b: u8, color: buffer::Color) { 41 | unsafe { 42 | *buffer::VGA_BUFFER.offset(*vga_index) = b; 43 | *buffer::VGA_BUFFER.offset(*vga_index + 1) = color as u8; 44 | *vga_index += 2; 45 | } 46 | } 47 | 48 | /// Move to a new line 49 | pub fn byte_raw(vga_index: &mut isize, b: u8, color: u8) { 50 | unsafe { 51 | *buffer::VGA_BUFFER.offset(*vga_index) = b; 52 | *buffer::VGA_BUFFER.offset(*vga_index + 1) = color; 53 | *vga_index += 2; 54 | } 55 | } 56 | 57 | pub fn newline(vga_index: &mut isize) { 58 | // VGA 80x25: each line is 80 chars * 2 bytes per char 59 | *vga_index += (80 * 2) - (*vga_index % (80 * 2)); 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/video/macros.rs: -------------------------------------------------------------------------------- 1 | // 2 | // PRINT MACROS 3 | // 4 | 5 | /// Macro to render all rows with the currently set ColorCode. 6 | #[macro_export] 7 | macro_rules! clear_screen { 8 | () => { 9 | if let Some(mut writer) = $crate::video::vga::get_writer() { 10 | writer.clear_screen(); 11 | } 12 | }; 13 | } 14 | 15 | /// Prints the error string to screen in red. 16 | #[macro_export] 17 | macro_rules! error { 18 | () => { 19 | $crate::print!("\n"); 20 | }; 21 | ($arg:expr $(,)?) => { 22 | // Set yellow chars on black 23 | $crate::print!($arg, $crate::video::vga::Color::Red, $crate::video::vga::Color::Black); 24 | }; 25 | } 26 | 27 | /// Prints the warning string to screen in yellow. 28 | #[macro_export] 29 | macro_rules! warn { 30 | () => { 31 | $crate::print!("\n"); 32 | }; 33 | ($arg:expr $(,)?) => { 34 | // Set yellow chars on black 35 | $crate::print!($arg, $crate::video::vga::Color::Yellow, $crate::video::vga::Color::Black); 36 | }; 37 | } 38 | 39 | /// This macro takes in a reference to byte slice (&[u8]) and prints all its contents to display. 40 | #[macro_export] 41 | macro_rules! printb { 42 | ($arg:expr) => { 43 | if let Some(mut writer) = $crate::video::vga::get_writer() { 44 | //writer.set_color($crate::vga::writer::Color::White, $crate::vga::writer::Color::Black); 45 | for b in $arg { 46 | writer.write_byte(*b); 47 | } 48 | } 49 | }; 50 | } 51 | 52 | /// Special macro to print u64 numbers as a slice of u8 bytes. 53 | #[macro_export] 54 | macro_rules! printn { 55 | ($arg:expr) => { 56 | // 57 | let mut buf = [0u8; 20]; 58 | let mut len = buf.len(); 59 | 60 | if $arg == 0 { 61 | print!("0"); 62 | //return 63 | } 64 | 65 | let mut num = $arg; 66 | 67 | while num > 0 { 68 | len -= 1; 69 | if let Some(b) = buf.get_mut(len) { 70 | *b = b'0' + (num % 10) as u8; 71 | } 72 | num /= 10; 73 | } 74 | 75 | printb!(buf.get(len..).unwrap_or(&[])); 76 | }; 77 | } 78 | 79 | /// Meta macro to include the newline implicitly at the end of provided string. 80 | #[macro_export] 81 | macro_rules! println { 82 | () => ($crate::print!("\n")); 83 | ($arg:expr) => ({ 84 | $crate::print!($arg); 85 | $crate::print!("\n"); 86 | }); 87 | } 88 | 89 | /// The main string printing macro. Takes in 1-3 arguments. The first one is always the string to 90 | /// print, and the latter ones are to change the foreground color (and background respectively) of 91 | /// the characters printed to screen. 92 | #[macro_export] 93 | macro_rules! print { 94 | ($arg:expr) => { 95 | if let Some(mut writer) = $crate::video::vga::get_writer() { 96 | //writer.set_color($crate::vga::writer::Color::White, $crate::vga::writer::Color::Black); 97 | writer.write_str_raw($arg); 98 | } 99 | }; 100 | ($arg:expr, $fg:expr) => { 101 | if let Some(mut writer) = $crate::video::vga::get_writer() { 102 | writer.set_color_num($fg as u8, $crate::video::vga::Color::Black as u8); 103 | writer.write_str_raw($arg); 104 | } 105 | }; 106 | ($arg:expr, $fg:expr, $bg:expr) => ({ 107 | if let Some(mut writer) = $crate::video::vga::get_writer() { 108 | writer.set_color_num($fg as u8, $bg as u8); 109 | writer.write_str_raw($arg); 110 | } 111 | }); 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/video/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod macros; 3 | pub mod mode; 4 | pub mod vga; 5 | -------------------------------------------------------------------------------- /src/video/mode.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy,Clone)] 2 | pub enum VideoMode { 3 | Framebuffer { 4 | address: *mut u8, 5 | pitch: usize, 6 | width: usize, 7 | height: usize, 8 | bpp: u8, 9 | }, 10 | TextMode, 11 | } 12 | 13 | static mut VIDEO_MODE: Option = Some(VideoMode::TextMode); 14 | 15 | pub fn init_video(fb: &crate::init::boot::FramebufferTag) { 16 | unsafe { 17 | VIDEO_MODE = Some(VideoMode::Framebuffer { 18 | address: fb.addr as *mut u8, 19 | pitch: fb.pitch as usize, 20 | width: fb.width as usize, 21 | height: fb.height as usize, 22 | bpp: fb.bpp, 23 | }); 24 | } 25 | } 26 | 27 | pub fn get_video_mode() -> Option { 28 | unsafe { 29 | VIDEO_MODE 30 | } 31 | } 32 | 33 | pub fn put_pixel(x: usize, y: usize, r: u8, g: u8, b: u8) { 34 | unsafe { 35 | match VIDEO_MODE { 36 | Some(VideoMode::Framebuffer { 37 | address, 38 | pitch, 39 | width, 40 | height, 41 | bpp, 42 | }) => { 43 | if x >= width || y >= height { 44 | return; 45 | } 46 | 47 | let offset = y * pitch + x * (bpp as usize / 8); 48 | let ptr = address.add(offset); 49 | 50 | match bpp { 51 | 32 => { 52 | let color = (r as u32) << 16 | (g as u32) << 8 | (b as u32); 53 | *(ptr as *mut u32) = color; 54 | } 55 | 16 => { 56 | let r5 = (r >> 3) as u16; 57 | let g6 = (g >> 2) as u16; 58 | let b5 = (b >> 3) as u16; 59 | let color = (r5 << 11) | (g6 << 5) | b5; 60 | *(ptr as *mut u16) = color; 61 | } 62 | _ => {} // Unsupported 63 | } 64 | } 65 | Some(VideoMode::TextMode) => { 66 | // No-op for pixel drawing in text mode 67 | } 68 | _ => {} 69 | } 70 | } 71 | } 72 | 73 | const VGA_BUFFER: *mut u8 = 0xb8000 as *mut u8; 74 | const VGA_WIDTH: usize = 80; 75 | 76 | pub fn put_char(x: usize, y: usize, ch: u8, color: u8) { 77 | unsafe { 78 | match VIDEO_MODE { 79 | Some(VideoMode::TextMode) => { 80 | let offset = 2 * (y * VGA_WIDTH + x); 81 | *VGA_BUFFER.add(offset) = ch; 82 | *VGA_BUFFER.add(offset + 1) = color; 83 | } 84 | _ => { 85 | // TODO: Render bitmap font to framebuffer 86 | } 87 | } 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/video/vga.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Write}; 2 | use crate::input::port; 3 | use spin::{mutex::Mutex}; 4 | use core::sync::atomic::{AtomicBool, Ordering}; 5 | 6 | /// VGA text mode buffer dimensions. 7 | const BUFFER_WIDTH: usize = 80; 8 | const BUFFER_HEIGHT: usize = 25; 9 | const BUFFER_ADDRESS: usize = 0xb8000; 10 | 11 | /// Wrapped Writer instance guarded by Mutex. 12 | pub static mut WRITER: Option> = None; 13 | 14 | /// Helper static boolean to ensure that the global Writer instance is created just once. 15 | static WRITER_INIT: AtomicBool = AtomicBool::new(false); 16 | 17 | /// Initializes the unique Writer instance. 18 | pub fn init_writer() { 19 | if WRITER_INIT.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_ok() { 20 | let writter = Writer::new(); 21 | unsafe { 22 | WRITER = Some(Mutex::new(writter)); 23 | } 24 | } 25 | } 26 | 27 | /// Returns a wrapped Writer instance guarded by Mutex in Option. Beware that this invocation locks 28 | /// the Writer instance and all print macros therefore can fail silently. 29 | pub fn get_writer() -> Option> { 30 | #[expect(static_mut_refs)] 31 | if WRITER_INIT.load(Ordering::Relaxed) { 32 | unsafe { WRITER.as_ref().map(|m| m.lock()) } 33 | } else { 34 | // Not initialized yet 35 | None 36 | } 37 | } 38 | 39 | /// VGA text mode colors (16 colors). 40 | #[allow(dead_code)] 41 | #[derive(Copy, Clone)] 42 | #[repr(u8)] 43 | pub enum Color { 44 | Black = 0, 45 | DarkBlue = 1, 46 | DarkGreen = 2, 47 | DarkCyan = 3, 48 | DarkRed = 4, 49 | DarkMagenta = 5, 50 | DarkYellow = 6, 51 | LightGrey = 7, 52 | // 53 | Grey = 8, 54 | Blue = 9, 55 | Green = 10, 56 | Cyan = 11, 57 | Red = 12, 58 | Magenta = 13, 59 | Yellow = 14, 60 | White = 15, 61 | } 62 | 63 | /// Structure to abstract and combine the foreground and background color usage. 64 | #[derive(Copy, Clone)] 65 | #[repr(transparent)] 66 | struct ColorCode(u8); 67 | 68 | impl ColorCode { 69 | /// Creates a new ColorCode instance to be used in text video mode implementations. 70 | fn new(fg: Color, bg: Color) -> Self { 71 | Self((bg as u8) << 4 | (fg as u8)) 72 | } 73 | 74 | /// Creates a new ColorCode instance, while the input is numeric. 75 | fn new_from_num(fg: u8, bg: u8) -> Self { 76 | Self(bg << 4 | fg) 77 | } 78 | } 79 | 80 | /// Structure to abstract a single character on the VGA text mode screen. 81 | #[derive(Copy, Clone)] 82 | #[repr(C)] 83 | struct ScreenChar { 84 | ascii_character: u8, 85 | color_code: ColorCode, 86 | } 87 | 88 | /// Buffer abstracts the whole VGA text mode screen with the 2D array to hold 80x25 ScreenChars. 89 | #[repr(transparent)] 90 | struct Buffer { 91 | chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], 92 | } 93 | 94 | /// Writer encapsulates the VGA text mode abstractions with proper video operations as 95 | /// implementations. 96 | pub struct Writer { 97 | col_pos: usize, 98 | row_pos: usize, 99 | color_code: ColorCode, 100 | buffer: *mut Buffer, 101 | } 102 | 103 | impl Write for Writer { 104 | /// Writes an input string to VGA text mode screen. 105 | fn write_str(&mut self, s: &str) -> fmt::Result { 106 | self.write_str_raw(s); 107 | Ok(()) 108 | } 109 | } 110 | 111 | impl Writer { 112 | /// Initializes a new Writer instance and returns it right away. 113 | pub fn new() -> Self { 114 | Writer { 115 | col_pos: 0, 116 | row_pos: 0, 117 | color_code: ColorCode::new(Color::White, Color::Black), 118 | buffer: BUFFER_ADDRESS as *mut _, 119 | } 120 | } 121 | 122 | /// Clears the screen with the current ColorCode. 123 | pub fn clear_screen(&mut self) { 124 | for row in 0..BUFFER_HEIGHT { 125 | self.clear_row(row); 126 | 127 | self.col_pos = 0; 128 | self.row_pos = 0; 129 | 130 | self.move_cursor(); 131 | } 132 | } 133 | 134 | /// Sets the specified ColorCode from provided foreground and background colors. 135 | pub fn set_color(&mut self, fg: Color, bg: Color) { 136 | self.color_code = ColorCode::new(fg, bg) 137 | } 138 | 139 | /// Sets the specified ColorCode from provided fg and bg colors, defined as u8. 140 | pub fn set_color_num(&mut self, fg: u8, bg: u8) { 141 | self.color_code = ColorCode::new_from_num(fg, bg) 142 | } 143 | 144 | /// Meta function to support printing static strings. 145 | pub fn write_str_raw(&mut self, s: &str) { 146 | for &byte in s.as_bytes() { 147 | self.write_byte(byte); 148 | } 149 | } 150 | /// Write one (1) byte to the display. 151 | pub fn write_byte(&mut self, byte: u8) { 152 | match byte { 153 | b'\n' => self.new_line(), 154 | b'\r' => { 155 | // Backspace = move cursor back and overwrite the ScreenChar on that position 156 | let mut row = self.row_pos; 157 | let mut col = self.col_pos; 158 | 159 | // Decrement the row position if we hit the left boundary of screen 160 | if col == 0 { 161 | row -= 1; 162 | col = BUFFER_WIDTH; 163 | } else { 164 | col -= 1; 165 | } 166 | 167 | let color_code = self.color_code; 168 | let buf = self.buffer_mut(); 169 | 170 | // Let's safely put the character to screen 171 | if let Some(row_buf) = buf.chars.get_mut(row) { 172 | if let Some(cell) = row_buf.get_mut(col) { 173 | *cell = ScreenChar { 174 | ascii_character: b' ', 175 | color_code, 176 | }; 177 | self.col_pos = col; 178 | } 179 | } 180 | } 181 | byte => { 182 | if self.col_pos >= BUFFER_WIDTH { 183 | self.new_line(); 184 | } 185 | 186 | let row = self.row_pos; 187 | let col = self.col_pos; 188 | 189 | if row >= BUFFER_HEIGHT || col >= BUFFER_WIDTH { 190 | return; 191 | } 192 | 193 | let color_code = self.color_code; 194 | let buf = self.buffer_mut(); 195 | 196 | // Write the character to screen 197 | if let Some(row_buf) = buf.chars.get_mut(row) { 198 | if let Some(cell) = row_buf.get_mut(col) { 199 | *cell = ScreenChar { 200 | ascii_character: byte, 201 | color_code, 202 | }; 203 | self.col_pos += 1; 204 | } 205 | } 206 | } 207 | } 208 | self.move_cursor(); 209 | } 210 | 211 | /// Move the hardware cursor to (row, col) 212 | fn move_cursor(&mut self) { 213 | let pos: u16 = (self.row_pos * BUFFER_WIDTH + self.col_pos) as u16; 214 | 215 | // Set high byte 216 | port::write(0x3D4, 0x0E); 217 | port::write(0x3D5, (pos >> 8) as u8); 218 | 219 | // Set low byte 220 | port::write(0x3D4, 0x0F); 221 | port::write(0x3D5, (pos & 0xFF) as u8); 222 | } 223 | 224 | 225 | /// Returns a mutable reference to the video buffer. 226 | fn buffer_mut(&mut self) -> &mut Buffer { 227 | unsafe { &mut *self.buffer } 228 | } 229 | 230 | /// Does the CRLF type of magic with the positional coordinates of a Writer instance. 231 | fn new_line(&mut self) { 232 | if self.row_pos < BUFFER_HEIGHT - 1 { 233 | self.row_pos += 1; 234 | } else { 235 | // scroll up 236 | for row in 1..BUFFER_HEIGHT { 237 | let buffer = self.buffer_mut(); 238 | for col in 0..BUFFER_WIDTH { 239 | buffer.chars[row - 1][col] = buffer.chars[row][col]; 240 | } 241 | } 242 | self.clear_row(BUFFER_HEIGHT - 1); 243 | } 244 | self.col_pos = 0; 245 | self.move_cursor(); 246 | } 247 | 248 | /// Clears the whole text row with the current ColorCode. 249 | fn clear_row(&mut self, row: usize) { 250 | let blank = ScreenChar { 251 | ascii_character: b' ', 252 | color_code: self.color_code, 253 | }; 254 | let buffer = self.buffer_mut(); 255 | for col in 0..BUFFER_WIDTH { 256 | buffer.chars[row][col] = blank; 257 | } 258 | } 259 | } 260 | 261 | -------------------------------------------------------------------------------- /terminus-font.psf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krustowski/rou2exOS/f5801dc8a39f14b3c100237784590363b9c20fe2/terminus-font.psf -------------------------------------------------------------------------------- /utils/midi.py: -------------------------------------------------------------------------------- 1 | from mido import MidiFile 2 | 3 | DEFAULT_TEMPO = 500000 # microseconds per beat (120 BPM) 4 | 5 | def midi_to_rust_array(path): 6 | mid = MidiFile(path) 7 | ticks_per_beat = mid.ticks_per_beat or 480 8 | tempo = DEFAULT_TEMPO 9 | events = [] 10 | 11 | for track in mid.tracks: 12 | time = 0 13 | for i, msg in enumerate(track): 14 | time += msg.time 15 | if msg.type == 'set_tempo': 16 | tempo = msg.tempo 17 | if msg.type == 'note_on' and msg.velocity > 0: 18 | note = msg.note 19 | dur_ticks = 0 20 | for m in track[i+1:]: 21 | dur_ticks += m.time 22 | if (m.type == 'note_off' and m.note == note) or \ 23 | (m.type == 'note_on' and m.note == note and m.velocity == 0): 24 | break 25 | duration_ms = int(dur_ticks * (tempo / 1000) / ticks_per_beat) 26 | events.append((note, duration_ms)) 27 | 28 | print("pub static MELODY: &[(u8, u16)] = &[") 29 | for note, dur in events: 30 | print(f" ({note}, {dur}),") 31 | print("];") 32 | 33 | # Replace with your local file path 34 | midi_to_rust_array("egg.midi") 35 | 36 | -------------------------------------------------------------------------------- /x86_64-r2.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", 4 | "arch": "x86_64", 5 | "target-endian": "little", 6 | "target-pointer-width": "64", 7 | "target-c-int-width": 32, 8 | "os": "none", 9 | "executables": true, 10 | "exe-suffix": ".elf", 11 | "panic-strategy": "abort", 12 | "disable-redzone": true, 13 | "features": "-mmx,-sse,+soft-float", 14 | "rustc-abi": "x86-softfloat", 15 | "linker-flavor": "ld.lld", 16 | "linker": "rust-lld", 17 | "relocation-model": "static" 18 | } 19 | --------------------------------------------------------------------------------